Backed by Y Combinator

Turn Designers into Design Engineers.

Input your design and watch it get built. Tailor made for your design system and codebase.

Start Shipping Faster
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

Somewhere along the way, turning designs into code got too complicated.
We reimagined it.

With ion, designers take projects further and free up engineers to focus on the big stuff.

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

Agree Ahmed
Cofounder, NUMI (YC W20)

Our shipping cadence has been unleashed since we started using Ion. And surprisingly, it's improved the quality of our codebase and our final product. That's because we can spend more time thinking about design and architecture and less time on UI and CSS. It's hard for me to imagine going back.

Amar Singh
CEO, Nexavision Odds

I loved using ion, it saved me hours of time converting all my figma frames to my actual production site. It helped put our design and engineering in sync, even when our figma files changed. I was able to get very far before having to involve my engineering team at all.

Akhil Chandan
CTO, Jumpshot Live

ion unlocked the productivity of our team. We were able to knock out all of our backlog design fixes in our scheduling app without any extra development resources.

Frequently Asked Questions

Why use ion?

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.

How does ion work?

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.

What stacks does ion support?

We support React, TypeScript, and Tailwind CSS. We also accommodate most component libraries—contact us for specifics.

What happens if designs get out of sync?

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.

Do I need to design with ion in mind?

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.

Can this replace my frontend engineer?

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.

How does ion handle Figma variables?

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.

What models does ion use?

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.

How are you different than the competition?

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.

Experience the speed of ion

Get started for free. Turn your designs into code in seconds and unleash the full power of your team.

Start Shipping Faster