Input your design and watch it get built. Tailor made for your design system and codebase.
1'use client';
2
3import { BarChart, DonutChart } from "@tremor/react";
4import React from "react";
5import {
6 createColumnHelper,
7 flexRender,
8 getCoreRowModel,
9 useReactTable,
10} from "@tanstack/react-table";
11import Badge from "@/components/ion/Badge";
12import {
13 ArrowDown,
14 ArrowsCounterClockwise,
15 Users,
16 Wallet,
17} from "@phosphor-icons/react/dist/ssr";
18import Button from "@/components/ion/Button";
19import clsx from "clsx";
20import Divider from "@/components/ion/Divider";
21import Checkbox from "@/components/ion/Checkbox";
22import Avatar from "@/components/ion/Avatar";
23
24export function Homepage() {
25 const handleWithdraw = () => {
26 // Handle withdraw action
27 console.log("Withdraw clicked");
28 };
29
30 const handleDeposit = () => {
31 // Handle deposit action
32 console.log("Deposit clicked");
33 };
34
35 return (
36 <div className="min-h-screen bg-neutral-50">
37 <Topbar className="sticky top-0 z-50" />
38
39 <MainContent>
40 <PageHeader
41 title="Overview"
42 id="8474720274"
43 onWithdraw={handleWithdraw}
44 onDeposit={handleDeposit}
45 />
46
47 <WidgetGrid>
48 {/* Main content area - spans full width */}
49 <div className="col-span-12">
50 <OverviewWidget />
51 </div>
52
53 {/* Two-column layout for remaining widgets */}
54 <div className="col-span-12 grid grid-cols-12 gap-5">
55 {/* Left column - Registration Widget */}
56 <div className="col-span-6">
57 <RegistrationWidget />
58 </div>
59
60 {/* Right column - Finance and Transactions */}
61 <div className="col-span-4 flex flex-col gap-5">
62 <FinanceWidget />
63 <LatestTransactionsWidget />
64 </div>
65
66 {/* Calendar Widget */}
67 <div className="col-span-2">
68 <CalendarWidget scheduleData={scheduleData} />
69 </div>
70 </div>
71 </WidgetGrid>
72 </MainContent>
73 </div>
74 );
75}
76
77Homepage.displayName = "Homepage";
78
79export default Homepage;
80
81interface MainContentProps {
82 children: React.ReactNode;
83 className?: string;
84}
85
86const MainContent = ({ children, className }: MainContentProps) => (
87 <main className={clsx("flex-1 flex flex-col gap-5 px-10 py-5", className)}>
88 {children}
89 </main>
90);
91
92const WidgetGrid = ({ children }: { children: React.ReactNode }) => (
93 <div className="flex-1 grid grid-cols-12 gap-5">{children}</div>
94);
95
96interface HeaderProps {
97 title: string;
98 id: string;
99 onWithdraw: () => void;
100 onDeposit: () => void;
101}
102
103const PageHeader = ({ title, id, onWithdraw, onDeposit }: HeaderProps) => (
104 <div className="flex justify-between items-center">
105 <div className="flex items-center gap-2">
106 <h1 className="text-4xl font-medium text-neutral-900">{title}</h1>
107 <Badge color="blue" variant="soft" className="text-xs">
108 ID: {id}
109 <Copy className="h-3 w-3 ml-1" />
110 </Badge>
111 </div>
112 <div className="flex items-center gap-3">
113 <Button variant="outline" size="sm" onClick={onWithdraw}>
114 <ArrowDown className="h-4 w-4 mr-2" />
115 Withdraw
116 </Button>
117 <Button size="sm" onClick={onDeposit}>
118 <ArrowUp className="h-4 w-4 mr-2" />
119 Deposit
120 </Button>
121 </div>
122 </div>
123);
124
125interface TopbarProps {
126 className?: string;
127}
128
129type NavItem = {
130 label: string;
131 icon: React.ReactNode;
132 isActive?: boolean;
133 hasDropdown?: boolean;
134};
135
136const NavButton = ({ item }: { item: NavItem }) => (
137 <Button
138 variant="ghost"
139 size="sm"
140 className={clsx(
141 "gap-2 font-medium",
142 item.isActive && "bg-blue-50 text-blue-700",
143 !item.isActive && "text-neutral-600",
144 )}
145 >
146 {item.icon}
147 <span>{item.label}</span>
148 {item.hasDropdown && <CaretDown className="h-4 w-4" />}
149 </Button>
150);
151
152const ActionButton = ({
153 icon: Icon,
154 className,
155}: {
156 icon: React.ElementType;
157 className?: string;
158}) => (
159 <Button
160 variant="ghost"
161 color="neutral"
162 className={clsx("text-neutral-600", className)}
163 >
164 <Icon className="h-5 w-5" />
165 </Button>
166);
167
168export function Topbar({ className }: TopbarProps) {
169 return (
170 <div
171 className={clsx(
172 "flex justify-between items-center px-10 py-5",
173 "bg-white border-b border-neutral-200",
174 className,
175 )}
176 >
177 <div className="flex items-center gap-10">
178 {/* Logo */}
179 <div className="flex items-center gap-2 text-2xl font-medium">
180 <div className="h-10 w-10 bg-blue-600 rounded-lg flex items-center justify-center text-white">
181 Sx
182 </div>
183 <span>SparX</span>
184 </div>
185
186 {/* Navigation */}
187 <nav className="flex items-center gap-1">
188 {navItems.map((item, index) => (
189 <NavButton key={index} item={item} />
190 ))}
191 </nav>
192 </div>
193
194 <div className="flex items-center gap-4">
195 {/* User Profile */}
196 <Button variant="outline" className="gap-2 font-medium">
197 <Avatar className="h-8 w-8" />
198 <span>John Doe</span>
199 <CaretDown className="h-4 w-4" />
200 </Button>
201 </div>
202 </div>
203 );
204}
205
206type OverviewStatCardData = {
207 title: string;
208 icon: React.ReactNode;
209 value: string;
210 change?: string;
211 valueColor?: string;
212};
213
214interface OverviewWidgetProps {
215 className?: string;
216}
217
218const OverviewStatsCard = ({ data }: { data: OverviewStatCardData }) => (
219 <div
220 className={clsx(
221 "flex flex-col gap-6 p-4 rounded-md",
222 "bg-white border border-neutral-200",
223 "shadow-sm",
224 )}
225 >
226 <div className="flex items-center justify-between text-base text-neutral-600">
227 <span>{data.title}</span>
228 {data.icon}
229 </div>
230
231 <div className="flex items-end gap-4">
232 <span
233 className={clsx(
234 "text-3xl leading-10",
235 data.valueColor || "text-neutral-900",
236 )}
237 >
238 {data.value}
239 </span>
240 {data.change && (
241 <div
242 className={clsx(
243 "px-2 py-0.5 rounded-full text-xs",
244 "bg-green-50 text-green-900",
245 "border border-green-200",
246 )}
247 >
248 {data.change}
249 </div>
250 )}
251 </div>
252 </div>
253);
254
255export function OverviewWidget({ className }: OverviewWidgetProps) {
256 return (
257 <div
258 className={clsx(
259 "flex flex-col gap-4 p-4 rounded-lg",
260 "border border-neutral-200 shadow-sm",
261 "bg-gradient-to-br from-blue-50 to-white",
262 className,
263 )}
264 >
265 <div className="flex items-center justify-between">
266 <h2 className="text-base font-medium py-1">Overview</h2>
267 <Button variant="outline" size="sm">
268 Today
269 <CaretDown className="h-4 w-4 ml-1" />
270 </Button>
271 </div>
272
273 <Divider />
274
275 <div className="grid grid-cols-4 gap-4">
276 {overviewData.map((data, index) => (
277 <OverviewStatsCard key={index} data={data} />
278 ))}
279 </div>
280 </div>
281 );
282}
283
284type AppointmentType = {
285 time: string;
286 userId: string;
287 type: "calendar" | "phone";
288 hasReminder?: boolean;
289};
290
291type DaySchedule = {
292 date: string;
293 dayName: string;
294 appointments: AppointmentType[];
295};
296
297interface CalendarWidgetProps {
298 className?: string;
299 scheduleData: DaySchedule[];
300 selectedDateRange?: string;
301 onAddFollowUp?: () => void;
302 onSearch?: () => void;
303 onDateRangeSelect?: () => void;
304}
305
306const AppointmentIcon = ({ type }: { type: "calendar" | "phone" }) => (
307 <div className="flex items-center gap-2.5 p-1 rounded-full border border-neutral-200 shadow-sm">
308 {type === "calendar" ? (
309 <Calendar className="h-4 w-4" />
310 ) : (
311 <Phone className="h-4 w-4" />
312 )}
313 </div>
314);
315
316const UserBadge = ({ userId }: { userId: string }) => (
317 <div className="flex items-center gap-1 px-1.5 py-1 rounded-full border border-neutral-200 shadow-sm text-xs text-neutral-600">
318 <User className="h-4 w-4" />
319 <span>{userId}</span>
320 </div>
321);
322
323const ReminderBadge = () => (
324 <div className="flex items-center p-1 rounded-sm bg-blue-50 border border-blue-200">
325 <Bell className="h-4 w-4 text-blue-500" />
326 </div>
327);
328
329const AppointmentRow = ({ appointment }: { appointment: AppointmentType }) => (
330 <div className="flex justify-between items-center py-2">
331 <div className="flex items-center gap-2">
332 <Checkbox />
333 <span className="text-xs">{appointment.time}</span>
334 </div>
335 <div className="flex items-center gap-2">
336 <div className="flex items-center gap-2 p-2">
337 <AppointmentIcon type={appointment.type} />
338 <UserBadge userId={appointment.userId} />
339 </div>
340 {appointment.hasReminder && <ReminderBadge />}
341 </div>
342 </div>
343);
344
345const DaySection = ({ day }: { day: DaySchedule }) => (
346 <div className="flex flex-col gap-1">
347 <div className="flex justify-between items-center p-3 bg-neutral-50 rounded-sm">
348 <span className="text-sm font-medium">{day.dayName}</span>
349 <span className="text-xs text-neutral-500">{day.date}</span>
350 </div>
351 <div className="flex flex-col">
352 {day.appointments.map((appointment, index) => (
353 <AppointmentRow key={index} appointment={appointment} />
354 ))}
355 </div>
356 </div>
357);
358
359export function CalendarWidget({
360 className,
361 scheduleData,
362 selectedDateRange = "May 26 - May 30",
363 onAddFollowUp,
364 onSearch,
365 onDateRangeSelect,
366}: CalendarWidgetProps) {
367 return (
368 <div
369 className={clsx(
370 "bg-background flex flex-col gap-3 rounded-lg border border-neutral-200 shadow-sm",
371 className,
372 )}
373 >
374 <div className="flex justify-between items-center p-4">
375 <div className="flex items-center gap-2 text-base font-medium">
376 <Calendar />
377 <span>Calendar</span>
378 </div>
379 <div className="flex items-center gap-2">
380 <Button
381 variant="outline"
382 color="neutral"
383 onClick={onSearch}
384 >
385 <MagnifyingGlass className="h-4 w-4" />
386 </Button>
387 <Button variant="outline" onClick={onDateRangeSelect}>
388 <Calendar className="h-4 w-4 mr-2" />
389 {selectedDateRange}
390 </Button>
391 </div>
392 </div>
393
394 <Divider />
395
396 <div className="flex flex-col gap-4 p-4">
397 {scheduleData.map((day, index) => (
398 <DaySection key={index} day={day} />
399 ))}
400 </div>
401
402 <Button
403 variant="ghost"
404 className="mx-4 mb-4 bg-primary-50 text-primary hover:bg-primary-100"
405 onClick={onAddFollowUp}
406 >
407 <Plus className="h-4 w-4 mr-2" />
408 Add follow up
409 </Button>
410 </div>
411 );
412}
413
414type Transaction = {
415 id: string;
416 type: "deposit" | "trade";
417 description: string;
418 amount: {
419 currency: "USD" | "EUR";
420 value: number;
421 };
422 date: string;
423 time: string;
424 status: "completed";
425};
426
427interface LatestTransactionsWidgetProps {
428 className?: string;
429}
430
431const columnHelper = createColumnHelper<Transaction>();
432
433const columns = [
434 columnHelper.accessor("id", {
435 header: "#",
436 cell: (info) => info.getValue(),
437 }),
438 columnHelper.accessor("description", {
439 header: "Type",
440 cell: (info) => (
441 <div className="flex items-center gap-1">
442 {info.row.original.type === "deposit" ? (
443 <ArrowUp className="h-4 w-4 text-green-500" />
444 ) : (
445 <ArrowDown className="h-4 w-4 text-neutral-500" />
446 )}
447 <span className="font-medium">{info.getValue()}</span>
448 </div>
449 ),
450 }),
451 columnHelper.accessor("amount", {
452 header: "Amount",
453 cell: (info) => (
454 <div className="flex items-center gap-1">
455 <span className="font-medium">
456 {info.getValue().currency}{" "}
457 {info.getValue().value.toFixed(2)}
458 </span>
459 <CheckCircle className="h-4 w-4 text-green-500" />
460 </div>
461 ),
462 }),
463 columnHelper.accessor("date", {
464 header: "Date",
465 cell: (info) => (
466 <div className="flex flex-col">
467 <span>{info.getValue()}</span>
468 <span className="text-neutral-500">
469 {info.row.original.time}
470 </span>
471 </div>
472 ),
473 }),
474];
475
476export function LatestTransactionsWidget({
477 className,
478}: LatestTransactionsWidgetProps) {
479 const table = useReactTable({
480 data: transactionData,
481 columns,
482 getCoreRowModel: getCoreRowModel(),
483 });
484
485 return (
486 <div
487 className={clsx(
488 "flex flex-col gap-3 p-4 rounded-sm border border-neutral-200",
489 "bg-gradient-to-br from-blue-50 to-white",
490 className,
491 )}
492 >
493 <div className="flex items-center justify-between">
494 <div className="flex items-center gap-2 text-base font-medium">
495 <ArrowsInCardinal className="h-6 w-6" />
496 <span>Latest Transactions</span>
497 </div>
498 <div className="flex items-center gap-2">
499 <Button variant="outline" color="neutral">
500 <List className="h-5 w-5" />
501 </Button>
502 <Button variant="outline" color="neutral">
503 <ArrowsCounterClockwise className="h-5 w-5" />
504 </Button>
505 </div>
506 </div>
507
508 <Divider />
509
510 <div className="rounded-sm overflow-hidden">
511 <table className="w-full">
512 <thead className="bg-neutral-50">
513 {table.getHeaderGroups().map((headerGroup) => (
514 <tr key={headerGroup.id}>
515 {headerGroup.headers.map((header) => (
516 <th
517 key={header.id}
518 className="px-3 py-2 text-sm text-neutral-600 text-left font-medium"
519 >
520 {header.isPlaceholder
521 ? null
522 : flexRender(
523 header.column.columnDef
524 .header,
525 header.getContext(),
526 )}
527 </th>
528 ))}
529 </tr>
530 ))}
531 </thead>
532 <tbody>
533 {table.getRowModel().rows.map((row, i) => (
534 <tr
535 key={row.id}
536 className={clsx(
537 "hover:bg-neutral-50",
538 i % 2 === 1 && "bg-neutral-50/50",
539 )}
540 >
541 {row.getVisibleCells().map((cell) => (
542 <td
543 key={cell.id}
544 className="px-3 py-4 text-xs"
545 >
546 {flexRender(
547 cell.column.columnDef.cell,
548 cell.getContext(),
549 )}
550 </td>
551 ))}
552 </tr>
553 ))}
554 </tbody>
555 </table>
556 </div>
557 </div>
558 );
559}
560type FinanceData = {
561 name: string;
562 value: number;
563 color: string;
564};
565
566interface FinanceWidgetProps {
567 className?: string;
568}
569
570const colorMap: Record<string, string> = {
571 "Deposit PSP3": "bg-emerald-500",
572 "Deposit PSP2": "bg-indigo-500",
573 "Deposit PSP1": "bg-sky-500",
574 Withdrawal: "bg-red-500",
575};
576
577const StatCard = ({
578 title,
579 amount,
580 color,
581}: {
582 title: string;
583 amount: number;
584 color: string;
585}) => (
586 <div className="flex flex-col items-center gap-3 px-4">
587 <div
588 className={clsx(
589 "w-8 h-8 rounded-full flex items-center justify-center",
590 `bg-${color}/10`,
591 )}
592 >
593 <div className={clsx("w-2 h-2 rounded-full", colorMap[title])} />
594 </div>
595 <div className="flex flex-col items-center gap-1">
596 <span className="text-xs text-neutral-600">{title}</span>
597 <span className="text-sm font-medium">
598 ${amount.toLocaleString("en-US", { minimumFractionDigits: 2 })}
599 </span>
600 </div>
601 </div>
602);
603
604export function FinanceWidget({ className }: FinanceWidgetProps) {
605 const totalAmount = financeData.reduce((sum, item) => sum + item.value, 0);
606
607 return (
608 <div
609 className={clsx(
610 "flex flex-col gap-4 p-4 rounded-lg border border-neutral-200 shadow-sm",
611 "bg-gradient-to-br from-green-50 to-white",
612 className,
613 )}
614 >
615 <div className="flex items-center justify-between">
616 <div className="flex items-center gap-2 text-base font-medium">
617 <Pinwheel className="h-6 w-6" />
618 <span>Finances distribution</span>
619 </div>
620 <Button variant="outline" size="sm">
621 Last Week
622 <CaretDown className="h-4 w-4 ml-1" />
623 </Button>
624 </div>
625
626 <Divider />
627
628 <div className="relative flex flex-col items-center justify-center">
629 {/* Center text overlay */}
630 <div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-10 flex flex-col items-center">
631 <span className="text-xs font-medium text-neutral-600 uppercase">
632 Overall Transitions
633 </span>
634 <span className="text-2xl font-medium mt-2">
635 $
636 {totalAmount.toLocaleString("en-US", {
637 minimumFractionDigits: 2,
638 })}
639 </span>
640 </div>
641
642 {/* Donut Chart */}
643 <DonutChart
644 data={financeData}
645 category="value"
646 index="name"
647 valueFormatter={(number) =>
648 `$${Intl.NumberFormat("us").format(number).toString()}`
649 }
650 colors={["emerald", "indigo", "sky", "red"]}
651 showAnimation={true}
652 className="h-[248px] w-[248px]"
653 showLabel={false}
654 />
655 </div>
656
657 <Divider />
658
659 <div className="grid grid-cols-4 gap-2">
660 {financeData.map((item, index) => (
661 <React.Fragment key={item.name}>
662 <StatCard
663 title={item.name}
664 amount={item.value}
665 color={item.color}
666 />
667 {index < financeData.length - 1 && (
668 <Divider
669 orientation="vertical"
670 className="h-[84px]"
671 />
672 )}
673 </React.Fragment>
674 ))}
675 </div>
676 </div>
677 );
678}
679
680type DistributionData = {
681 country: string;
682 value: number;
683 color: string;
684};
685
686type RegistrationRecord = {
687 id: string;
688 country: string;
689 name: string;
690 status: "active" | "pending" | "waiting";
691 date: string;
692 time: string;
693 hasExternalLink?: boolean;
694};
695
696interface RegistrationWidgetProps {
697 className?: string;
698}
699
700const StatusIcon = ({ status }: { status: RegistrationRecord["status"] }) => {
701 const statusColors = {
702 active: "text-blue-500 bg-blue-50",
703 pending: "text-green-500 bg-green-50",
704 waiting: "text-amber-500 bg-amber-50",
705 };
706
707 return (
708 <div className={clsx("p-2 rounded-full", statusColors[status])}>
709 <User className="h-4 w-4" />
710 </div>
711 );
712};
713
714const registrationColumnHelper = createColumnHelper<RegistrationRecord>();
715
716const registrationColumns = [
717 registrationColumnHelper.accessor("id", {
718 header: "#",
719 cell: (info) => info.getValue(),
720 }),
721 registrationColumnHelper.accessor("country", {
722 header: "Country",
723 cell: (info) => <span className="font-medium">{info.getValue()}</span>,
724 }),
725 registrationColumnHelper.accessor("name", {
726 header: "Name",
727 cell: (info) => info.getValue(),
728 }),
729 registrationColumnHelper.accessor("status", {
730 header: "Status",
731 cell: (info) => (
732 <div className="flex gap-2">
733 <StatusIcon status={info.getValue()} />
734 {info.row.original.hasExternalLink && (
735 <div className="p-2 rounded-full text-blue-500 bg-blue-50">
736 <Link className="h-4 w-4" />
737 </div>
738 )}
739 </div>
740 ),
741 }),
742 registrationColumnHelper.accessor("date", {
743 header: "Date",
744 cell: (info) => (
745 <div className="flex flex-col">
746 <span>{info.getValue()}</span>
747 <span className="text-neutral-400">
748 {info.row.original.time}
749 </span>
750 </div>
751 ),
752 }),
753];
754
755export function RegistrationWidget({ className }: RegistrationWidgetProps) {
756 const table = useReactTable({
757 data: registrationData,
758 columns: registrationColumns,
759 getCoreRowModel: getCoreRowModel(),
760 });
761
762 return (
763 <div
764 className={clsx(
765 "flex flex-col gap-4 p-4 rounded-lg",
766 "border border-neutral-200 shadow-sm",
767 "bg-gradient-to-br from-indigo-50 to-white",
768 className,
769 )}
770 >
771 <div className="flex items-center justify-between">
772 <div className="flex items-center gap-2 text-base font-medium">
773 <UserPlus className="h-6 w-6" />
774 <span>Registration Distribution</span>
775 </div>
776 <Button variant="outline" size="sm">
777 Last week
778 <CaretDown className="h-4 w-4 ml-1" />
779 </Button>
780 </div>
781
782 <Divider />
783
784 <div className="flex flex-col gap-4">
785 <BarChart
786 data={distributionData}
787 index="country"
788 categories={["value"]}
789 colors={["blue"]}
790 showGridLines={false}
791 startEndOnly={true}
792 showAnimation={true}
793 valueFormatter={(value) => `${value}%`}
794 className="h-64"
795 />
796
797 <div className="bg-white rounded-sm border border-neutral-200 p-3">
798 <div className="flex items-center justify-between mb-3">
799 <h3 className="text-base font-medium">
800 Recent Registration
801 </h3>
802 <div className="flex items-center gap-2">
803 <Button variant="outline" color="neutral">
804 <MagnifyingGlass className="h-5 w-5" />
805 </Button>
806 <Button variant="outline" size="sm">
807 See All
808 </Button>
809 </div>
810 </div>
811
812 <div className="rounded-sm overflow-hidden">
813 <table className="w-full">
814 <thead className="bg-neutral-50">
815 {table.getHeaderGroups().map((headerGroup) => (
816 <tr key={headerGroup.id}>
817 {headerGroup.headers.map((header) => (
818 <th
819 key={header.id}
820 className="px-3 py-2 text-sm text-neutral-600 text-left"
821 >
822 {header.isPlaceholder
823 ? null
824 : flexRender(
825 header.column
826 .columnDef.header,
827 header.getContext(),
828 )}
829 </th>
830 ))}
831 </tr>
832 ))}
833 </thead>
834 <tbody>
835 {table.getRowModel().rows.map((row, i) => (
836 <tr
837 key={row.id}
838 className={clsx(
839 "hover:bg-neutral-50",
840 i % 2 === 1 && "bg-neutral-50/50",
841 )}
842 >
843 {row.getVisibleCells().map((cell) => (
844 <td
845 key={cell.id}
846 className="px-3 py-4 text-xs"
847 >
848 {flexRender(
849 cell.column.columnDef.cell,
850 cell.getContext(),
851 )}
852 </td>
853 ))}
854 </tr>
855 ))}
856 </tbody>
857 </table>
858 </div>
859 </div>
860 </div>
861 </div>
862 );
863}
864
865const distributionData: DistributionData[] = [
866 { country: "USA", value: 80, color: "blue-600" },
867 { country: "UAE", value: 40, color: "sky-400" },
868 { country: "UK", value: 32, color: "purple-500" },
869 { country: "Brazil", value: 28, color: "indigo-900" },
870 { country: "Germany", value: 20, color: "amber-500" },
871 { country: "Spain", value: 12, color: "cyan-900" },
872 { country: "Portugal", value: 8, color: "emerald-500" },
873 { country: "KSA", value: 4, color: "violet-200" },
874];
875
876const navItems: NavItem[] = [
877 {
878 label: "Dashboard",
879 icon: <Layout className="h-5 w-5" />,
880 isActive: true,
881 },
882 {
883 label: "Clients",
884 icon: <Users className="h-5 w-5" />,
885 },
886 {
887 label: "Pending",
888 icon: <Bell className="h-5 w-5" />,
889 hasDropdown: true,
890 },
891 {
892 label: "Finances",
893 icon: <Wallet className="h-5 w-5" />,
894 },
895 {
896 label: "Leads",
897 icon: <Phone className="h-5 w-5" />,
898 },
899 {
900 label: "Management",
901 icon: <Briefcase className="h-5 w-5" />,
902 },
903 {
904 label: "Settings",
905 icon: <Gear className="h-5 w-5" />,
906 hasDropdown: true,
907 },
908];
909
910const registrationData: RegistrationRecord[] = [
911 {
912 id: "7009",
913 country: "UAE",
914 name: "John Doe",
915 status: "active",
916 date: "12/05/2024",
917 time: "14:24",
918 },
919 {
920 id: "7009",
921 country: "Germany",
922 name: "Ahmed Ayad",
923 status: "pending",
924 date: "12/05/2024",
925 time: "14:24",
926 },
927 {
928 id: "7009",
929 country: "Spain",
930 name: "Salim Ahmed",
931 status: "waiting",
932 date: "12/05/2024",
933 time: "14:24",
934 hasExternalLink: true,
935 },
936 {
937 id: "7009",
938 country: "Switzerland",
939 name: "Damian Dark",
940 status: "active",
941 date: "12/05/2024",
942 time: "14:24",
943 },
944];
945
946const overviewData: OverviewStatCardData[] = [
947 {
948 title: "Clients",
949 icon: <Users className="h-5 w-5 text-neutral-600" />,
950 value: "1,532",
951 change: "+5% last mth",
952 },
953 {
954 title: "Registration",
955 icon: <UserPlus className="h-5 w-5 text-neutral-600" />,
956 value: "25",
957 change: "+2% last mth",
958 },
959 {
960 title: "Deposits",
961 icon: <ArrowUp className="h-5 w-5 text-neutral-600" />,
962 value: "+$25,000",
963 valueColor: "text-green-700",
964 },
965 {
966 title: "Withdraw",
967 icon: <ArrowDown className="h-5 w-5 text-neutral-600" />,
968 value: "-$5,000",
969 valueColor: "text-red-700",
970 },
971];
972
973const financeData: FinanceData[] = [
974 {
975 name: "Deposit PSP3",
976 value: 95000,
977 color: "emerald-500",
978 },
979 {
980 name: "Deposit PSP2",
981 value: 30000,
982 color: "indigo-500",
983 },
984 {
985 name: "Deposit PSP1",
986 value: 25000,
987 color: "sky-500",
988 },
989 {
990 name: "Withdrawal",
991 value: 50000,
992 color: "red-500",
993 },
994];
995
996const transactionData: Transaction[] = [
997 {
998 id: "7009",
999 type: "deposit",
1000 description: "Deposit cashier",
1001 amount: { currency: "EUR", value: 2.0 },
1002 date: "12/05/2024",
1003 time: "14:24",
1004 status: "completed",
1005 },
1006 {
1007 id: "7009",
1008 type: "deposit",
1009 description: "Deposit cashier",
1010 amount: { currency: "USD", value: 980.0 },
1011 date: "12/05/2024",
1012 time: "14:24",
1013 status: "completed",
1014 },
1015 {
1016 id: "7009",
1017 type: "deposit",
1018 description: "Deposit cashier card",
1019 amount: { currency: "USD", value: 80.0 },
1020 date: "12/05/2024",
1021 time: "14:24",
1022 status: "completed",
1023 },
1024 {
1025 id: "7009",
1026 type: "trade",
1027 description: "Trade adjustment",
1028 amount: { currency: "USD", value: 10.0 },
1029 date: "12/05/2024",
1030 time: "14:24",
1031 status: "completed",
1032 },
1033];
1034
1035const scheduleData: DaySchedule[] = [
1036 {
1037 date: "26th May 2024",
1038 dayName: "Sunday",
1039 appointments: [
1040 {
1041 time: "15:08 - 15:38",
1042 userId: "7004",
1043 type: "calendar",
1044 hasReminder: true,
1045 },
1046 {
1047 time: "15:08 - 15:38",
1048 userId: "7004",
1049 type: "phone",
1050 hasReminder: true,
1051 },
1052 {
1053 time: "15:08 - 15:38",
1054 userId: "7004",
1055 type: "calendar",
1056 hasReminder: true,
1057 },
1058 ],
1059 },
1060 {
1061 date: "27th May 2024",
1062 dayName: "Monday",
1063 appointments: [
1064 {
1065 time: "15:08 - 15:38",
1066 userId: "7004",
1067 type: "calendar",
1068 hasReminder: true,
1069 },
1070 {
1071 time: "15:08 - 15:38",
1072 userId: "7004",
1073 type: "calendar",
1074 hasReminder: true,
1075 },
1076 ],
1077 },
1078 {
1079 date: "28th May 2024",
1080 dayName: "Tuesday",
1081 appointments: [
1082 {
1083 time: "15:08 - 15:38",
1084 userId: "7004",
1085 type: "calendar",
1086 hasReminder: true,
1087 },
1088 {
1089 time: "15:08 - 15:38",
1090 userId: "7004",
1091 type: "calendar",
1092 hasReminder: true,
1093 },
1094 ],
1095 },
1096 {
1097 date: "29th May 2024",
1098 dayName: "Wednesday",
1099 appointments: [
1100 {
1101 time: "15:08 - 15:38",
1102 userId: "7004",
1103 type: "calendar",
1104 hasReminder: true,
1105 },
1106 {
1107 time: "15:08 - 15:38",
1108 userId: "7004",
1109 type: "calendar",
1110 hasReminder: true,
1111 },
1112 ],
1113 },
1114 {
1115 date: "30th May 2024",
1116 dayName: "Thursday",
1117 appointments: [
1118 {
1119 time: "15:08 - 15:38",
1120 userId: "7004",
1121 type: "calendar",
1122 hasReminder: true,
1123 },
1124 {
1125 time: "15:08 - 15:38",
1126 userId: "7004",
1127 type: "calendar",
1128 hasReminder: true,
1129 },
1130 ],
1131 },
1132];
ionize your designs
Learn in just 4 minutes
Trusted by the world's leading innovative teams
Build your frontend in minutes, not weeks
Whatever you're designing, ion will turn it into clean, industry-leading code. Fast.
Design in Figma
You can use your existing design system and tokens to create designs where you already feel comfortable.
Generate Code with ion
ion is connected to your company codebase. It’ll automatically use your components, style, and structure.
Pull, integrate, and Ship
Code connects directly to your developers’ github branches, so they can simply pull, integrate, and ship!
Save 10+ hours per week
Developers shouldn’t waste hours on styling. Let ion take care of the mundane for you.
Understands your Codebase
ion syncs with your existing libraries and components. No more tweaking or adjusting – just production-ready code straight from Figma.
1"use client";
2
3import Avatar from "@/components/Avatar";
4import Button from "@/components/Button";
5import Divider from "@/components/Divider";
6import Input from "@/components/Input";
7import Textarea from "@/components/Textarea";
8
9function ContactInformationCard() {
10 const [address, setAddress] = useState("");
11 const [emailAddress, setEmailAddress] = useState("");
12 const [phoneNumber, setPhoneNumber] = useState("");
13
14 function applyChangesClickHandler(e: MouseEvent<HTMLButtonElement>) {
15 e.preventDefault();
16 const requestData = {
17 address,
18 emailAddress,
19 phoneNumber,
20 };
21
22 console.log("Sending data to API:", requestData);
23
24 // Simulate an API request
25 setTimeout(() => {
26 console.log("API request successful:", requestData);
27 alert("Changes applied successfully!");
28 }, 1000);
29 }
30
31 function discardClickHandler(e: MouseEvent<HTMLButtonElement>) {
32 alert("discardClickHandler fired");
33 }
34
35 return (
36 <div className="bg-white w-[400px] flex flex-col justify-center items-center gap-5 p-5 rounded-lg shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]">
37 <div className="w-fit flex flex-col items-center gap-4">
38 <Avatar
39 subtitle="Enter your contact details for communication."
40 title="Contact Information"
41 size="md"
42 topStatus="plus"
43 />
44 <Divider />
45 <Input
46 placeholder="samraaj@ion.design"
47 iconLeading={
48 <EnvelopeSimple size={16} weight={"regular"} />
49 }
50 required
51 label="Email Address"
52 showHintIcon
53 hint="Work email"
54 value={emailAddress}
55 onChange={(e) => setEmailAddress(e.target.value)}
56 className="w-full"
57 />
58 <Input
59 placeholder="samraaj@ion.design"
60 iconLeading={<Phone size={16} weight={"bold"} />}
61 required
62 label="Phone Number"
63 value={phoneNumber}
64 onChange={(e) => setPhoneNumber(e.target.value)}
65 className="w-full"
66 />
67 <Textarea
68 placeholder="301 main st"
69 required
70 helper="(Mailing)"
71 label="Address"
72 showHintIcon
73 hint="Needed for HR records"
74 value={address}
75 onChange={(e) => setAddress(e.target.value)}
76 className="w-full"
77 />
78 <div className="w-full flex items-center gap-5">
79 <Button
80 variant="soft"
81 color="neutral"
82 size="md"
83 onClick={discardClickHandler}
84 className="w-full"
85 >
86 Discard
87 </Button>
88 <Button
89 variant="filled"
90 color="primary"
91 size="md"
92 onClick={applyChangesClickHandler}
93 className="w-full"
94 >
95 Apply Changes
96 </Button>
97 </div>
98 </div>
99 </div>
100 );
101}
102export default ContactInformationCard;
Pixel perfect, every time
Reduce your development time by 30-50%. Get features live without delays, and move faster than your competition.
Iterate with Confidence
Sync Figma updates automatically with pull requests and merge conflicts, clearing your backlog and letting the team focus on what matters.
Complex Components Made Easy
Handling conditional logic, state, and animations is now effortless. ion generates code that’s not just visually accurate but functionally sound.
What designers and engineers like you have to say
Frequently Asked Questions
Reduce Development Time - stop switching between your editor & figma - instead you can focus on shipping features faster.
Seamless Integration - ion fits into your existing stack, working with your libraries and codebase to keep everything in sync.
Effortless Scaling - as you grow, ion ensures your workflow remains fast and efficient without sacrificing quality.
ion intelligently maps your existing React components to Figma designs. It understands how to call them and recognizes your global variables, Tailwind CSS configurations, fonts, and styling setups to generate the best possible code for your use case.
We support React, TypeScript, and Tailwind CSS. We also accommodate most component libraries—contact us for specifics.
ion allows you to generate pull requests for differences and merge conflicts when generating code in VS Code. This helps you iterate without constant development resource allocation.
ion requires adherence to standard best practices like using auto-layout, avoiding fixed sizing, and maintaining consistent component structures. You can use dsLint to ensure your designs are compliant.
We're here to empower your development team, not replace it. While ion accelerates production, engineers are still essential for integrating business logic, architecture, and asynchronous data. Our AI helper can take you far, but not all the way.
With ion, you can export variables from Figma and implement them into your codebase. Once integrated, ion automatically categorizes and utilizes them appropriately in code generation.
We leverage a blend of off-the-shelf LLMs, including OpenAI's GPT-4 & o1-mini and Anthropic’s Claude Sonnet 3.5, along with custom models to generate high-quality code efficiently and cost-effectively.
ion produces the highest quality code from Figma designs, taking you further than any other tool. Our mission is to create more design engineers and transform the industry.
Other Figma-to-code tools aren't able to produce as high quality code as us because they don't have the proper codebase context to write high-quality code tailored to your use case.
Other AI-based app builders aren't built specifically for designers. We build for your tools and workflows to give you an unbeatable experience - for example we integrate with Figma to meet you where you're already doing design work.