gladvn 0.2.32 → 0.2.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js CHANGED
@@ -511,6 +511,8 @@ async function main() {
511
511
 
512
512
  if (viteConfigPath) {
513
513
  let content = fs.readFileSync(viteConfigPath, 'utf8');
514
+ let modified = false;
515
+
514
516
  if (!content.includes('@gladvn')) {
515
517
  // Add `import path from "path"` if missing
516
518
  if (!content.includes('import path from') && !content.includes('require("path")')) {
@@ -538,9 +540,35 @@ async function main() {
538
540
  } else {
539
541
  content = content.replace(/defineConfig\s*\(\s*\{/, `defineConfig({${resolveAlias}`);
540
542
  }
543
+ modified = true;
544
+ }
545
+
546
+ if (!content.includes('@tailwindcss/vite')) {
547
+ if (content.includes('import ')) {
548
+ const lastImportIndex = content.lastIndexOf('import ');
549
+ const endOfLine = content.indexOf('\n', lastImportIndex);
550
+ if (endOfLine !== -1) {
551
+ content = content.slice(0, endOfLine + 1) + 'import tailwindcss from "@tailwindcss/vite"\n' + content.slice(endOfLine + 1);
552
+ } else {
553
+ content = 'import tailwindcss from "@tailwindcss/vite"\n' + content;
554
+ }
555
+ } else {
556
+ content = 'import tailwindcss from "@tailwindcss/vite"\n' + content;
557
+ }
541
558
 
559
+ if (content.includes('plugins: [')) {
560
+ content = content.replace(/plugins:\s*\[/, `plugins: [\n tailwindcss(),`);
561
+ } else if (content.match(/plugins:\s*\[/)) {
562
+ content = content.replace(/plugins:\s*\[/, `plugins: [\n tailwindcss(),`);
563
+ } else if (content.includes('defineConfig({')) {
564
+ content = content.replace(/defineConfig\s*\(\s*\{/, `defineConfig({\n plugins: [tailwindcss()],`);
565
+ }
566
+ modified = true;
567
+ }
568
+
569
+ if (modified) {
542
570
  fs.writeFileSync(viteConfigPath, content);
543
- console.log(`\x1b[32m✔ Configured resolve.alias in ${path.basename(viteConfigPath)}\x1b[0m`);
571
+ console.log(`\x1b[32m✔ Configured resolve.alias & tailwindcss plugin in ${path.basename(viteConfigPath)}\x1b[0m`);
544
572
  }
545
573
  }
546
574
 
@@ -552,15 +580,25 @@ async function main() {
552
580
 
553
581
  // Check if we need @types/node (Vite + TypeScript)
554
582
  const viteConfigExists = ['vite.config.ts', 'vite.config.mts'].some(f => fs.existsSync(path.join(process.cwd(), f)));
555
- if (viteConfigExists) {
583
+ if (viteConfigExists || viteConfigPath) {
556
584
  try {
557
585
  const userPkgPath = path.resolve(process.cwd(), 'package.json');
558
586
  if (fs.existsSync(userPkgPath)) {
559
587
  const userPkg = JSON.parse(fs.readFileSync(userPkgPath, 'utf8'));
560
588
  const allUserDeps = { ...userPkg.dependencies, ...userPkg.devDependencies };
561
- if (!allUserDeps['@types/node']) {
589
+ if (viteConfigExists && !allUserDeps['@types/node']) {
562
590
  extraDepsToInstall.push('@types/node@"^20.0.0"');
563
591
  }
592
+ if (!allUserDeps['tailwindcss']) {
593
+ extraDepsToInstall.push('tailwindcss@"^4.0.0"');
594
+ }
595
+ if (!allUserDeps['@tailwindcss/vite']) {
596
+ extraDepsToInstall.push('@tailwindcss/vite@"^4.0.0"');
597
+ }
598
+ } else {
599
+ // If no package.json, just push them to be safe
600
+ extraDepsToInstall.push('tailwindcss@"^4.0.0"');
601
+ extraDepsToInstall.push('@tailwindcss/vite@"^4.0.0"');
564
602
  }
565
603
  } catch (e) {}
566
604
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gladvn",
3
- "version": "0.2.32",
3
+ "version": "0.2.34",
4
4
  "type": "module",
5
5
  "packageManager": "pnpm@10.11.0",
6
6
  "description": "A CLI to scaffold beautiful, accessible React components into your project. Powered by Tailwind CSS v4 and Base UI.",
@@ -0,0 +1,105 @@
1
+ import * as React from "react";
2
+ import { useState } from "react";
3
+ import { Button } from "../components/micro/button";
4
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/micro/card";
5
+ import { Checkbox } from "../components/micro/checkbox";
6
+ import { Input } from "../components/micro/input";
7
+ import { Label } from "../components/micro/label";
8
+
9
+ export default function AuthCardBlock() {
10
+ const [isLoading, setIsLoading] = useState(false);
11
+ const [mode, setMode] = useState<"login" | "register">("login");
12
+
13
+ const handleSubmit = (e: React.FormEvent) => {
14
+ e.preventDefault();
15
+ setIsLoading(true);
16
+ setTimeout(() => setIsLoading(false), 1000);
17
+ };
18
+
19
+ return (
20
+ <div className="flex min-h-screen w-full items-center justify-center p-4 bg-muted/20">
21
+ <Card className="w-full max-w-sm border-0 shadow-none bg-transparent">
22
+ <CardHeader className="text-left px-0 pt-0">
23
+ <CardTitle className="text-[28px] font-bold text-[#5e5e6e]">
24
+ {mode === "login" ? "Welcome back" : "Create an account"}
25
+ </CardTitle>
26
+ <CardDescription>
27
+ {mode === "login"
28
+ ? "Enter your email below to login to your account"
29
+ : "Enter your details below to create your account"}
30
+ </CardDescription>
31
+ </CardHeader>
32
+ <CardContent className="flex flex-col gap-6 px-0 pb-0">
33
+ <form onSubmit={handleSubmit} className="flex flex-col gap-4">
34
+ {mode === "register" && (
35
+ <div className="flex flex-col gap-2">
36
+ <Label htmlFor="name">Full Name</Label>
37
+ <Input id="name" name="name" placeholder="John Doe" className="w-full" required />
38
+ </div>
39
+ )}
40
+ <div className="flex flex-col gap-2">
41
+ <Label htmlFor="email">Email</Label>
42
+ <Input id="email" name="email" type="email" placeholder="m@example.com" className="w-full" required />
43
+ </div>
44
+ <div className="flex flex-col gap-2">
45
+ <div className="flex items-center justify-between">
46
+ <Label htmlFor="password">Password</Label>
47
+ {mode === "login" && (
48
+ <Button variant="link" color="primary" className="h-auto p-0 text-xs">
49
+ Forgot password?
50
+ </Button>
51
+ )}
52
+ </div>
53
+ <Input id="password" name="password" type="password" className="w-full" required />
54
+ </div>
55
+
56
+ {mode === "register" && (
57
+ <div className="flex items-center gap-2">
58
+ <Checkbox id="terms" name="terms" required />
59
+ <Label htmlFor="terms" className="text-sm font-normal text-muted-foreground leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
60
+ I agree to the <a href="#" className="text-primary hover:underline">terms and conditions</a>
61
+ </Label>
62
+ </div>
63
+ )}
64
+
65
+ <Button type="submit" className="w-full" disabled={isLoading} aria-busy={isLoading}>
66
+ {isLoading ? "Loading..." : mode === "login" ? "Sign In" : "Create Account"}
67
+ </Button>
68
+ </form>
69
+
70
+ <div className="flex justify-center">
71
+ <span className="text-[13px] font-medium text-muted-foreground uppercase">
72
+ Or continue with
73
+ </span>
74
+ </div>
75
+
76
+ <div className="flex flex-col gap-6">
77
+ <Button variant="outline" color="primary" className="w-full bg-transparent">
78
+ <svg viewBox="0 0 24 24" className="mr-2 size-4 bg-transparent" fill="currentColor">
79
+ <path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4" />
80
+ <path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853" />
81
+ <path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05" />
82
+ <path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335" />
83
+ </svg>
84
+ Google
85
+ </Button>
86
+
87
+ <div className="text-center text-sm">
88
+ <span className="text-muted-foreground">
89
+ {mode === "login" ? "Don't have an account? " : "Already have an account? "}
90
+ </span>
91
+ <Button
92
+ variant="link"
93
+ color="primary"
94
+ className="h-auto p-0 font-semibold"
95
+ onClick={() => setMode(mode === "login" ? "register" : "login")}
96
+ >
97
+ {mode === "login" ? "Sign up" : "Sign in"}
98
+ </Button>
99
+ </div>
100
+ </div>
101
+ </CardContent>
102
+ </Card>
103
+ </div>
104
+ );
105
+ }
@@ -21,7 +21,7 @@ export default function AuthRecoveryBlock() {
21
21
  return (
22
22
  <div className="flex min-h-[50vh] w-full items-center justify-center p-4 bg-muted/20">
23
23
  <Card className="w-full max-w-sm">
24
- <CardHeader className="text-center space-y-2">
24
+ <CardHeader className="text-center">
25
25
  <CardTitle className="text-2xl font-bold">
26
26
  {isSent ? "Check your email" : "Forgot Password"}
27
27
  </CardTitle>
@@ -33,17 +33,17 @@ export default function AuthRecoveryBlock() {
33
33
  </CardHeader>
34
34
  <CardContent>
35
35
  {!isSent ? (
36
- <form onSubmit={handleSubmit} className="space-y-4">
37
- <div className="space-y-2">
36
+ <form onSubmit={handleSubmit} className="flex flex-col gap-4">
37
+ <div className="flex flex-col gap-1.5">
38
38
  <Label htmlFor="recovery-email">Email</Label>
39
- <Input id="recovery-email" type="email" placeholder="m@example.com" required />
39
+ <Input id="recovery-email" type="email" placeholder="m@example.com" className="w-full" required />
40
40
  </div>
41
41
  <Button type="submit" className="w-full" disabled={isLoading}>
42
42
  {isLoading ? "Sending link..." : "Send Reset Link"}
43
43
  </Button>
44
44
  </form>
45
45
  ) : (
46
- <div className="flex flex-col space-y-4">
46
+ <div className="flex flex-col gap-4">
47
47
  <Button type="button" variant="outline" className="w-full" onClick={() => setIsSent(false)}>
48
48
  Try another email
49
49
  </Button>
@@ -22,7 +22,7 @@ export default function AuthSplitBlock() {
22
22
  <div className="hidden lg:flex w-1/2 flex-col justify-between bg-primary p-12 text-primary-foreground relative overflow-hidden">
23
23
  {/* Subtle background pattern */}
24
24
  <div className="absolute inset-0 opacity-10 bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-white to-transparent pointer-events-none" />
25
-
25
+
26
26
  <div className="relative z-10">
27
27
  <h1 className="text-2xl font-bold tracking-tight">gladvn.</h1>
28
28
  </div>
@@ -62,16 +62,16 @@ export default function AuthSplitBlock() {
62
62
 
63
63
  <form onSubmit={handleSubmit} className="space-y-4">
64
64
  {mode === "register" && (
65
- <div className="space-y-2">
65
+ <div className="flex flex-col gap-2">
66
66
  <Label htmlFor="name">Full Name</Label>
67
- <Input id="name" placeholder="John Doe" required />
67
+ <Input id="name" placeholder="John Doe" className="w-full" required />
68
68
  </div>
69
69
  )}
70
- <div className="space-y-2">
70
+ <div className="flex flex-col gap-2">
71
71
  <Label htmlFor="email">Email</Label>
72
- <Input id="email" type="email" placeholder="m@example.com" required />
72
+ <Input id="email" type="email" placeholder="m@example.com" className="w-full" required />
73
73
  </div>
74
- <div className="space-y-2">
74
+ <div className="flex flex-col gap-2">
75
75
  <div className="flex items-center justify-between">
76
76
  <Label htmlFor="password">Password</Label>
77
77
  {mode === "login" && (
@@ -80,9 +80,9 @@ export default function AuthSplitBlock() {
80
80
  </Button>
81
81
  )}
82
82
  </div>
83
- <Input id="password" type="password" required />
83
+ <Input id="password" type="password" className="w-full" required />
84
84
  </div>
85
-
85
+
86
86
  {mode === "register" && (
87
87
  <div className="flex items-center gap-2">
88
88
  <Checkbox id="terms" required />
@@ -8,78 +8,129 @@ import {
8
8
  SettingsIcon,
9
9
  UsersIcon
10
10
  } from "lucide-react";
11
+ import * as React from "react";
12
+ import { DatePicker } from "../components/macro";
11
13
  import { Avatar, AvatarFallback, AvatarImage } from "../components/micro/avatar";
14
+ import { Badge } from "../components/micro/badge";
15
+ import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "../components/micro/breadcrumb";
12
16
  import { Button } from "../components/micro/button";
13
17
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/micro/card";
14
18
  import {
15
19
  DropdownMenu,
16
20
  DropdownMenuContent,
21
+ DropdownMenuGroup,
17
22
  DropdownMenuItem,
18
23
  DropdownMenuLabel,
19
24
  DropdownMenuSeparator,
20
25
  DropdownMenuTrigger
21
26
  } from "../components/micro/dropdown-menu";
27
+ import { HoverCard, HoverCardContent, HoverCardTrigger } from "../components/micro/hover-card";
22
28
  import { Input } from "../components/micro/input";
29
+ import { Progress } from "../components/micro/progress";
30
+ import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "../components/micro/sheet";
31
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/micro/table";
32
+
33
+ const SidebarContent = () => (
34
+ <>
35
+ <div className="flex h-16 items-center border-b border-border px-6">
36
+ <LayoutDashboardIcon className="mr-2 size-5 text-primary" />
37
+ <span className="font-bold text-lg">Gladvn App</span>
38
+ </div>
39
+ <nav className="flex-1 space-y-1 p-4">
40
+ <Button variant="soft" color="primary" className="w-full justify-start">
41
+ <HomeIcon className="mr-2 size-4" /> Overview
42
+ </Button>
43
+ <Button variant="ghost" className="w-full justify-start text-muted-foreground">
44
+ <BarChart3Icon className="mr-2 size-4" /> Analytics
45
+ </Button>
46
+ <Button variant="ghost" className="w-full justify-start text-muted-foreground">
47
+ <UsersIcon className="mr-2 size-4" /> Customers
48
+ </Button>
49
+ <Button variant="ghost" className="w-full justify-start text-muted-foreground">
50
+ <SettingsIcon className="mr-2 size-4" /> Settings
51
+ </Button>
52
+ </nav>
53
+ </>
54
+ );
23
55
 
24
56
  export default function DashboardBlock() {
57
+ const [date, setDate] = React.useState<Date | undefined>(new Date());
58
+
25
59
  return (
26
60
  <div className="flex min-h-screen w-full bg-muted/20">
27
- {/* Sidebar */}
61
+ {/* Sidebar Desktop */}
28
62
  <aside className="hidden w-64 flex-col border-r border-border bg-background md:flex">
29
- <div className="flex h-16 items-center border-b border-border px-6">
30
- <LayoutDashboardIcon className="mr-2 size-5 text-primary" />
31
- <span className="font-bold">Gladvn App</span>
32
- </div>
33
- <nav className="flex-1 space-y-1 p-4">
34
- <Button variant="soft" color="primary" className="w-full justify-start">
35
- <HomeIcon className="mr-2 size-4" /> Overview
36
- </Button>
37
- <Button variant="ghost" className="w-full justify-start text-muted-foreground">
38
- <BarChart3Icon className="mr-2 size-4" /> Analytics
39
- </Button>
40
- <Button variant="ghost" className="w-full justify-start text-muted-foreground">
41
- <UsersIcon className="mr-2 size-4" /> Customers
42
- </Button>
43
- <Button variant="ghost" className="w-full justify-start text-muted-foreground">
44
- <SettingsIcon className="mr-2 size-4" /> Settings
45
- </Button>
46
- </nav>
63
+ <SidebarContent />
47
64
  </aside>
48
65
 
49
66
  {/* Main Content */}
50
67
  <main className="flex flex-1 flex-col overflow-hidden">
51
68
  {/* Header */}
52
- <header className="flex h-16 items-center gap-4 border-b border-border bg-background px-6">
53
- <Button variant="ghost" iconOnly className="md:hidden">
54
- <MenuIcon className="size-5" />
55
- </Button>
56
-
57
- <div className="flex-1">
58
- <div className="relative w-full max-w-sm">
69
+ <header className="flex h-16 items-center gap-4 border-b border-border bg-background px-4 md:px-6">
70
+ {/* Mobile Menu */}
71
+ <Sheet>
72
+ <SheetTrigger
73
+ render={
74
+ <Button variant="ghost" iconOnly className="md:hidden">
75
+ <MenuIcon className="size-5" />
76
+ </Button>
77
+ }
78
+ />
79
+ <SheetContent side="left" className="w-64 p-0">
80
+ <SheetHeader className="sr-only">
81
+ <SheetTitle>Navigation Menu</SheetTitle>
82
+ </SheetHeader>
83
+ <SidebarContent />
84
+ </SheetContent>
85
+ </Sheet>
86
+
87
+ {/* Breadcrumb Navigation */}
88
+ <div className="hidden sm:flex flex-1">
89
+ <Breadcrumb>
90
+ <BreadcrumbList>
91
+ <BreadcrumbItem>
92
+ <BreadcrumbLink href="#">Home</BreadcrumbLink>
93
+ </BreadcrumbItem>
94
+ <BreadcrumbSeparator />
95
+ <BreadcrumbItem>
96
+ <BreadcrumbLink href="#">Dashboard</BreadcrumbLink>
97
+ </BreadcrumbItem>
98
+ <BreadcrumbSeparator />
99
+ <BreadcrumbItem>
100
+ <BreadcrumbPage>Overview</BreadcrumbPage>
101
+ </BreadcrumbItem>
102
+ </BreadcrumbList>
103
+ </Breadcrumb>
104
+ </div>
105
+
106
+ <div className="flex-1 sm:flex-none flex justify-end">
107
+ <div className="relative w-full max-w-sm sm:w-64">
59
108
  <SearchIcon className="absolute left-2.5 top-2.5 size-4 text-muted-foreground" />
60
- <Input type="search" placeholder="Search..." className="w-full pl-9 bg-muted/50" />
109
+ <Input type="search" placeholder="Search..." className="w-full pl-9 bg-muted/50 rounded-full" />
61
110
  </div>
62
111
  </div>
63
-
64
- <Button variant="ghost" iconOnly className="text-muted-foreground relative">
112
+
113
+ <Button variant="ghost" iconOnly className="text-muted-foreground relative shrink-0">
65
114
  <BellIcon className="size-5" />
66
115
  <span className="absolute top-1 right-1 size-2 rounded-full bg-destructive border-2 border-background"></span>
67
116
  </Button>
68
117
 
69
118
  <DropdownMenu>
70
- <DropdownMenuTrigger render={<button className="outline-none" />}>
119
+ <DropdownMenuTrigger render={<button className="outline-none shrink-0" />}>
71
120
  <Avatar className="size-8 ring-2 ring-primary/20 hover:ring-primary/50 transition-all cursor-pointer">
72
121
  <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
73
122
  <AvatarFallback>CN</AvatarFallback>
74
123
  </Avatar>
75
124
  </DropdownMenuTrigger>
76
125
  <DropdownMenuContent align="end" className="w-56">
77
- <DropdownMenuLabel>My Account</DropdownMenuLabel>
78
- <DropdownMenuSeparator />
79
- <DropdownMenuItem>Profile</DropdownMenuItem>
80
- <DropdownMenuItem>Billing</DropdownMenuItem>
81
- <DropdownMenuItem>Team</DropdownMenuItem>
82
- <DropdownMenuItem>Subscription</DropdownMenuItem>
126
+ <DropdownMenuGroup>
127
+ <DropdownMenuLabel>My Account</DropdownMenuLabel>
128
+ <DropdownMenuSeparator />
129
+ <DropdownMenuItem>Profile</DropdownMenuItem>
130
+ <DropdownMenuItem>Billing</DropdownMenuItem>
131
+ <DropdownMenuItem>Team</DropdownMenuItem>
132
+ <DropdownMenuItem>Subscription</DropdownMenuItem>
133
+ </DropdownMenuGroup>
83
134
  <DropdownMenuSeparator />
84
135
  <DropdownMenuItem variant="destructive">Log out</DropdownMenuItem>
85
136
  </DropdownMenuContent>
@@ -87,14 +138,19 @@ export default function DashboardBlock() {
87
138
  </header>
88
139
 
89
140
  {/* Dashboard Content */}
90
- <div className="flex-1 overflow-auto p-6 md:p-8">
91
- <div className="flex items-center justify-between mb-8">
141
+ <div className="flex-1 overflow-auto p-4 md:p-8">
142
+ <div className="flex flex-col sm:flex-row sm:items-center justify-between mb-8 gap-4">
92
143
  <div>
93
144
  <h2 className="text-3xl font-bold tracking-tight">Dashboard</h2>
94
145
  <p className="text-muted-foreground mt-1">Here's an overview of your business today.</p>
95
146
  </div>
96
147
  <div className="flex items-center gap-2">
97
- <Button variant="outline">Download Report</Button>
148
+ <DatePicker
149
+ mode="single"
150
+ value={date}
151
+ onValueChange={setDate}
152
+ />
153
+ <Button color="primary">Download Report</Button>
98
154
  </div>
99
155
  </div>
100
156
 
@@ -104,10 +160,17 @@ export default function DashboardBlock() {
104
160
  <CardDescription>Total Revenue</CardDescription>
105
161
  <CardTitle className="text-2xl">$45,231.89</CardTitle>
106
162
  </CardHeader>
107
- <CardContent>
163
+ <CardContent className="space-y-4">
108
164
  <p className="text-xs text-muted-foreground">
109
165
  <span className="text-success font-medium">+20.1%</span> from last month
110
166
  </p>
167
+ <div className="space-y-1">
168
+ <div className="flex text-xs justify-between">
169
+ <span>Monthly Goal</span>
170
+ <span className="font-medium">85%</span>
171
+ </div>
172
+ <Progress value={85} className="h-1.5" />
173
+ </div>
111
174
  </CardContent>
112
175
  </Card>
113
176
  <Card>
@@ -150,7 +213,7 @@ export default function DashboardBlock() {
150
213
  <CardHeader>
151
214
  <CardTitle>Overview</CardTitle>
152
215
  </CardHeader>
153
- <CardContent className="h-[300px] flex items-center justify-center border-t border-border/50 bg-muted/10 rounded-b-xl">
216
+ <CardContent className="h-[350px] flex items-center justify-center border-t border-border/50 bg-muted/10 rounded-b-xl">
154
217
  <p className="text-sm text-muted-foreground">Chart placeholder</p>
155
218
  </CardContent>
156
219
  </Card>
@@ -159,19 +222,64 @@ export default function DashboardBlock() {
159
222
  <CardTitle>Recent Sales</CardTitle>
160
223
  <CardDescription>You made 265 sales this month.</CardDescription>
161
224
  </CardHeader>
162
- <CardContent className="space-y-6">
163
- {[1, 2, 3, 4, 5].map((i) => (
164
- <div key={i} className="flex items-center gap-4">
165
- <Avatar className="size-9 hidden sm:flex">
166
- <AvatarFallback>OM</AvatarFallback>
167
- </Avatar>
168
- <div className="flex-1 space-y-1">
169
- <p className="text-sm font-medium leading-none">Olivia Martin</p>
170
- <p className="text-xs text-muted-foreground">olivia.martin@email.com</p>
171
- </div>
172
- <div className="font-medium text-sm">+$1,999.00</div>
173
- </div>
174
- ))}
225
+ <CardContent>
226
+ <Table>
227
+ <TableHeader>
228
+ <TableRow>
229
+ <TableHead>Customer</TableHead>
230
+ <TableHead>Status</TableHead>
231
+ <TableHead className="text-right">Amount</TableHead>
232
+ </TableRow>
233
+ </TableHeader>
234
+ <TableBody>
235
+ {[
236
+ { name: "Olivia Martin", email: "olivia.martin@email.com", amount: "$1,999.00", status: "success" },
237
+ { name: "Jackson Lee", email: "jackson.lee@email.com", amount: "$39.00", status: "pending" },
238
+ { name: "Isabella Nguyen", email: "isabella.nguyen@email.com", amount: "$299.00", status: "success" },
239
+ { name: "William Kim", email: "will@email.com", amount: "$99.00", status: "failed" },
240
+ ].map((sale, i) => (
241
+ <TableRow key={i}>
242
+ <TableCell>
243
+ <HoverCard>
244
+ <HoverCardTrigger
245
+ render={
246
+ <div className="flex items-center gap-3 cursor-pointer">
247
+ <Avatar className="size-8">
248
+ <AvatarFallback>{sale.name.substring(0, 2).toUpperCase()}</AvatarFallback>
249
+ </Avatar>
250
+ <div className="space-y-0.5">
251
+ <p className="text-sm font-medium leading-none">{sale.name}</p>
252
+ <p className="text-xs text-muted-foreground">{sale.email}</p>
253
+ </div>
254
+ </div>
255
+ }
256
+ />
257
+ <HoverCardContent className="w-80">
258
+ <div className="flex justify-between space-x-4">
259
+ <Avatar>
260
+ <AvatarFallback>{sale.name.substring(0, 2).toUpperCase()}</AvatarFallback>
261
+ </Avatar>
262
+ <div className="space-y-1">
263
+ <h4 className="text-sm font-semibold">@{sale.name.toLowerCase().replace(' ', '')}</h4>
264
+ <p className="text-sm">Premium customer since 2023.</p>
265
+ <div className="flex items-center pt-2">
266
+ <span className="text-xs text-muted-foreground">Last active 2 hours ago</span>
267
+ </div>
268
+ </div>
269
+ </div>
270
+ </HoverCardContent>
271
+ </HoverCard>
272
+ </TableCell>
273
+ <TableCell>
274
+ {sale.status === "success" && <Badge variant="soft" color="success">Paid</Badge>}
275
+ {sale.status === "pending" && <Badge variant="outline" color="warning">Pending</Badge>}
276
+ {sale.status === "failed" && <Badge variant="soft" color="destructive">Failed</Badge>}
277
+ </TableCell>
278
+ <TableCell className="text-right font-medium">{sale.amount}</TableCell>
279
+ </TableRow>
280
+ ))}
281
+ </TableBody>
282
+ </Table>
175
283
  </CardContent>
176
284
  </Card>
177
285
  </div>