veryfront 0.0.38 → 0.0.40
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/dist/ai/components.js +48 -47
- package/dist/ai/components.js.map +2 -2
- package/dist/ai/dev.js +3 -3
- package/dist/ai/dev.js.map +2 -2
- package/dist/ai/index.js +39 -13
- package/dist/ai/index.js.map +3 -3
- package/dist/cli.js +3775 -2924
- package/dist/components.js +1 -1
- package/dist/components.js.map +1 -1
- package/dist/config.js +2 -2
- package/dist/config.js.map +2 -2
- package/dist/data.js +1 -1
- package/dist/data.js.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +2 -2
- package/dist/templates/ai/ai/agents/assistant.ts +5 -3
- package/dist/templates/ai/ai/prompts/assistant.ts +7 -6
- package/dist/templates/ai/ai/tools/.gitkeep +0 -0
- package/dist/templates/ai/app/layout.tsx +3 -10
- package/dist/templates/ai/app/page.tsx +7 -13
- package/dist/templates/app/app/dashboard/page.tsx +5 -5
- package/dist/templates/app/app/layout.tsx +7 -29
- package/dist/templates/app/app/login/page.tsx +53 -125
- package/dist/templates/app/components/DashboardLayout.tsx +29 -95
- package/dist/templates/app/components/FeatureGrid.tsx +24 -30
- package/dist/templates/app/components/Header.tsx +13 -16
- package/dist/templates/app/components/HeroSection.tsx +19 -29
- package/dist/templates/blog/app/layout.tsx +28 -37
- package/dist/templates/blog/app/page.tsx +2 -2
- package/dist/templates/blog/components/BlogPostList.tsx +15 -24
- package/dist/templates/docs/app/layout.tsx +12 -25
- package/dist/templates/docs/components/Header.tsx +25 -22
- package/dist/templates/docs/components/Sidebar.tsx +8 -8
- package/dist/templates/minimal/app/layout.tsx +4 -11
- package/dist/templates/minimal/app/page.tsx +8 -8
- package/package.json +1 -1
- package/dist/templates/ai/ai/tools/get-weather.ts +0 -29
|
@@ -13,8 +13,10 @@ export default agent({
|
|
|
13
13
|
id: "assistant",
|
|
14
14
|
model: "openai/gpt-4o",
|
|
15
15
|
system: getSystemPrompt,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
16
|
+
|
|
17
|
+
// Use all discovered tools from ai/tools/
|
|
18
|
+
// To select specific tools, change to: tools: { toolName: true, anotherTool: true }
|
|
19
|
+
tools: true,
|
|
20
|
+
|
|
19
21
|
maxSteps: 10,
|
|
20
22
|
});
|
|
@@ -3,12 +3,13 @@ import { prompt } from "veryfront/ai";
|
|
|
3
3
|
export default prompt({
|
|
4
4
|
id: "assistant",
|
|
5
5
|
description: "System prompt for the AI assistant",
|
|
6
|
-
content: `You are a helpful AI assistant
|
|
6
|
+
content: `You are a helpful AI assistant.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
1. Use the getWeather tool to fetch current conditions
|
|
10
|
-
2. Provide a friendly summary of the weather
|
|
11
|
-
3. Suggest appropriate activities based on conditions
|
|
8
|
+
You have access to tools that let you interact with external services. Use them when relevant to help users accomplish their tasks.
|
|
12
9
|
|
|
13
|
-
|
|
10
|
+
Guidelines:
|
|
11
|
+
- Be conversational and helpful
|
|
12
|
+
- Use available tools proactively when they can help
|
|
13
|
+
- Summarize results clearly and suggest next steps
|
|
14
|
+
- If you can't do something, explain why and suggest alternatives`,
|
|
14
15
|
});
|
|
File without changes
|
|
@@ -4,15 +4,8 @@ export default function RootLayout({
|
|
|
4
4
|
children: React.ReactNode;
|
|
5
5
|
}) {
|
|
6
6
|
return (
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
11
|
-
<script src="https://cdn.tailwindcss.com"></script>
|
|
12
|
-
</head>
|
|
13
|
-
<body className="h-full bg-slate-50 dark:bg-slate-900">
|
|
14
|
-
{children}
|
|
15
|
-
</body>
|
|
16
|
-
</html>
|
|
7
|
+
<div className="h-full bg-slate-50 dark:bg-slate-900">
|
|
8
|
+
{children}
|
|
9
|
+
</div>
|
|
17
10
|
);
|
|
18
11
|
}
|
|
@@ -7,22 +7,16 @@ export default function ChatPage() {
|
|
|
7
7
|
const chat = useChat({ api: '/api/chat' })
|
|
8
8
|
|
|
9
9
|
return (
|
|
10
|
-
<div className="flex flex-col h-screen bg-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
<path d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456z" />
|
|
16
|
-
</svg>
|
|
17
|
-
</div>
|
|
18
|
-
<div>
|
|
19
|
-
<h1 className="font-semibold text-slate-800 dark:text-white">Veryfront AI</h1>
|
|
20
|
-
<p className="text-xs text-slate-500 dark:text-slate-400">Powered by Agents & Tools</p>
|
|
21
|
-
</div>
|
|
10
|
+
<div className="flex flex-col h-screen bg-white dark:bg-neutral-900">
|
|
11
|
+
{/* Header - minimal */}
|
|
12
|
+
<header className="flex-shrink-0 border-b border-neutral-200 dark:border-neutral-800">
|
|
13
|
+
<div className="max-w-2xl mx-auto px-4 py-3 flex items-center justify-center">
|
|
14
|
+
<h1 className="font-medium text-neutral-900 dark:text-white">AI Assistant</h1>
|
|
22
15
|
</div>
|
|
23
16
|
</header>
|
|
24
17
|
|
|
25
|
-
|
|
18
|
+
{/* Chat */}
|
|
19
|
+
<Chat {...chat} className="flex-1" placeholder="Message" />
|
|
26
20
|
</div>
|
|
27
21
|
)
|
|
28
22
|
}
|
|
@@ -14,10 +14,10 @@ export default async function DashboardPage() {
|
|
|
14
14
|
return (
|
|
15
15
|
<DashboardLayout>
|
|
16
16
|
<div className="space-y-8">
|
|
17
|
-
<div
|
|
18
|
-
<h1 className="text-
|
|
19
|
-
<p className="text-
|
|
20
|
-
Welcome back,
|
|
17
|
+
<div>
|
|
18
|
+
<h1 className="text-2xl font-bold text-neutral-900 dark:text-white">Dashboard</h1>
|
|
19
|
+
<p className="text-neutral-600 dark:text-neutral-400 mt-1">
|
|
20
|
+
Welcome back, {session.user.name}
|
|
21
21
|
</p>
|
|
22
22
|
</div>
|
|
23
23
|
|
|
@@ -26,4 +26,4 @@ export default async function DashboardPage() {
|
|
|
26
26
|
</div>
|
|
27
27
|
</DashboardLayout>
|
|
28
28
|
);
|
|
29
|
-
}
|
|
29
|
+
}
|
|
@@ -13,33 +13,11 @@ export default function RootLayout({
|
|
|
13
13
|
children: React.ReactNode
|
|
14
14
|
}) {
|
|
15
15
|
return (
|
|
16
|
-
<
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
/>
|
|
23
|
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
24
|
-
<style>{`
|
|
25
|
-
body { font-family: 'Inter', sans-serif; }
|
|
26
|
-
.animate-blob { animation: blob 7s infinite; }
|
|
27
|
-
.animation-delay-2000 { animation-delay: 2s; }
|
|
28
|
-
.animation-delay-4000 { animation-delay: 4s; }
|
|
29
|
-
@keyframes blob {
|
|
30
|
-
0% { transform: translate(0px, 0px) scale(1); }
|
|
31
|
-
33% { transform: translate(30px, -50px) scale(1.1); }
|
|
32
|
-
66% { transform: translate(-20px, 20px) scale(0.9); }
|
|
33
|
-
100% { transform: translate(0px, 0px) scale(1); }
|
|
34
|
-
}
|
|
35
|
-
`}</style>
|
|
36
|
-
</head>
|
|
37
|
-
<body className="bg-slate-50 text-slate-900 antialiased">
|
|
38
|
-
<AuthProvider>
|
|
39
|
-
{children}
|
|
40
|
-
<Toaster />
|
|
41
|
-
</AuthProvider>
|
|
42
|
-
</body>
|
|
43
|
-
</html>
|
|
16
|
+
<AuthProvider>
|
|
17
|
+
<div className="min-h-screen bg-white dark:bg-neutral-900 text-neutral-900 dark:text-neutral-100 antialiased">
|
|
18
|
+
{children}
|
|
19
|
+
<Toaster />
|
|
20
|
+
</div>
|
|
21
|
+
</AuthProvider>
|
|
44
22
|
);
|
|
45
|
-
}
|
|
23
|
+
}
|
|
@@ -3,51 +3,6 @@
|
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { login } from "../../lib/auth-client.ts";
|
|
5
5
|
|
|
6
|
-
// Custom SVG Icons
|
|
7
|
-
function LockIcon({ className }: { className?: string }) {
|
|
8
|
-
return (
|
|
9
|
-
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
10
|
-
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
|
|
11
|
-
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
|
|
12
|
-
</svg>
|
|
13
|
-
);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function MailIcon({ className }: { className?: string }) {
|
|
17
|
-
return (
|
|
18
|
-
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
19
|
-
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
|
|
20
|
-
<polyline points="22,6 12,13 2,6"/>
|
|
21
|
-
</svg>
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function EyeIcon({ className }: { className?: string }) {
|
|
26
|
-
return (
|
|
27
|
-
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
28
|
-
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
|
|
29
|
-
<circle cx="12" cy="12" r="3"/>
|
|
30
|
-
</svg>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function EyeOffIcon({ className }: { className?: string }) {
|
|
35
|
-
return (
|
|
36
|
-
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
37
|
-
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/>
|
|
38
|
-
<line x1="1" y1="1" x2="23" y2="23"/>
|
|
39
|
-
</svg>
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function SparklesIcon({ className }: { className?: string }) {
|
|
44
|
-
return (
|
|
45
|
-
<svg className={className} viewBox="0 0 24 24" fill="currentColor">
|
|
46
|
-
<path d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09 3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456z"/>
|
|
47
|
-
</svg>
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
6
|
export default function LoginPage() {
|
|
52
7
|
const [email, setEmail] = useState("");
|
|
53
8
|
const [password, setPassword] = useState("");
|
|
@@ -71,128 +26,102 @@ export default function LoginPage() {
|
|
|
71
26
|
};
|
|
72
27
|
|
|
73
28
|
return (
|
|
74
|
-
<div className="min-h-screen flex items-center justify-center bg-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
<div className="absolute -top-[20%] -left-[10%] w-[70%] h-[70%] rounded-full bg-purple-500/20 blur-3xl animate-blob"></div>
|
|
78
|
-
<div className="absolute top-[20%] -right-[10%] w-[70%] h-[70%] rounded-full bg-indigo-500/20 blur-3xl animate-blob animation-delay-2000"></div>
|
|
79
|
-
<div className="absolute -bottom-[20%] left-[20%] w-[70%] h-[70%] rounded-full bg-pink-500/20 blur-3xl animate-blob animation-delay-4000"></div>
|
|
80
|
-
</div>
|
|
81
|
-
|
|
82
|
-
<div className="w-full max-w-md relative z-10">
|
|
83
|
-
{/* Logo & Header */}
|
|
29
|
+
<div className="min-h-screen flex items-center justify-center bg-neutral-50 dark:bg-neutral-900 p-4">
|
|
30
|
+
<div className="w-full max-w-sm">
|
|
31
|
+
{/* Header */}
|
|
84
32
|
<div className="text-center mb-8">
|
|
85
|
-
<div className="w-
|
|
86
|
-
<
|
|
33
|
+
<div className="w-12 h-12 mx-auto mb-4 rounded-2xl bg-blue-500 flex items-center justify-center">
|
|
34
|
+
<svg className="w-6 h-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
35
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z" />
|
|
36
|
+
</svg>
|
|
87
37
|
</div>
|
|
88
|
-
<h1 className="text-
|
|
89
|
-
<p className="text-
|
|
38
|
+
<h1 className="text-2xl font-bold text-neutral-900 dark:text-white">Welcome back</h1>
|
|
39
|
+
<p className="text-sm text-neutral-500 dark:text-neutral-400 mt-1">Sign in to continue</p>
|
|
90
40
|
</div>
|
|
91
41
|
|
|
92
|
-
{/*
|
|
93
|
-
<div className="bg-white
|
|
94
|
-
<form onSubmit={handleSubmit} className="space-y-
|
|
42
|
+
{/* Form */}
|
|
43
|
+
<div className="bg-white dark:bg-neutral-800 rounded-2xl border border-neutral-200 dark:border-neutral-700 p-6">
|
|
44
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
95
45
|
{error && (
|
|
96
|
-
<div className="bg-red-50
|
|
97
|
-
<svg className="w-5 h-5 flex-shrink-0" viewBox="0 0 24 24" fill="currentColor">
|
|
98
|
-
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>
|
|
99
|
-
</svg>
|
|
46
|
+
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 text-red-600 dark:text-red-400 px-4 py-3 rounded-xl text-sm">
|
|
100
47
|
{error}
|
|
101
48
|
</div>
|
|
102
49
|
)}
|
|
103
50
|
|
|
104
|
-
{/* Email Field */}
|
|
105
51
|
<div>
|
|
106
|
-
<label htmlFor="email" className="block text-sm font-medium text-
|
|
107
|
-
Email
|
|
52
|
+
<label htmlFor="email" className="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-1.5">
|
|
53
|
+
Email
|
|
108
54
|
</label>
|
|
109
|
-
<
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
119
|
-
placeholder="you@example.com"
|
|
120
|
-
className="block w-full pl-10 pr-4 py-3 bg-slate-50 dark:bg-slate-900/50 border border-slate-200 dark:border-slate-700 rounded-xl text-slate-800 dark:text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 transition-all"
|
|
121
|
-
/>
|
|
122
|
-
</div>
|
|
55
|
+
<input
|
|
56
|
+
id="email"
|
|
57
|
+
type="email"
|
|
58
|
+
required
|
|
59
|
+
value={email}
|
|
60
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
61
|
+
placeholder="you@example.com"
|
|
62
|
+
className="w-full px-4 py-2.5 bg-neutral-50 dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700 rounded-xl text-neutral-900 dark:text-white placeholder-neutral-400 focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500 transition-colors"
|
|
63
|
+
/>
|
|
123
64
|
</div>
|
|
124
65
|
|
|
125
|
-
{/* Password Field */}
|
|
126
66
|
<div>
|
|
127
|
-
<div className="flex items-center justify-between mb-
|
|
128
|
-
<label htmlFor="password" className="block text-sm font-medium text-
|
|
67
|
+
<div className="flex items-center justify-between mb-1.5">
|
|
68
|
+
<label htmlFor="password" className="block text-sm font-medium text-neutral-700 dark:text-neutral-300">
|
|
129
69
|
Password
|
|
130
70
|
</label>
|
|
131
|
-
<a href="/forgot-password" className="text-sm text-
|
|
132
|
-
Forgot
|
|
71
|
+
<a href="/forgot-password" className="text-sm text-blue-500 hover:text-blue-600">
|
|
72
|
+
Forgot?
|
|
133
73
|
</a>
|
|
134
74
|
</div>
|
|
135
|
-
<div className="relative
|
|
136
|
-
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
137
|
-
<LockIcon className="w-5 h-5 text-slate-400 group-focus-within:text-indigo-500 transition-colors" />
|
|
138
|
-
</div>
|
|
75
|
+
<div className="relative">
|
|
139
76
|
<input
|
|
140
77
|
id="password"
|
|
141
78
|
type={showPassword ? "text" : "password"}
|
|
142
79
|
required
|
|
143
80
|
value={password}
|
|
144
81
|
onChange={(e) => setPassword(e.target.value)}
|
|
145
|
-
placeholder="Enter
|
|
146
|
-
className="
|
|
82
|
+
placeholder="Enter password"
|
|
83
|
+
className="w-full px-4 py-2.5 pr-10 bg-neutral-50 dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700 rounded-xl text-neutral-900 dark:text-white placeholder-neutral-400 focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500 transition-colors"
|
|
147
84
|
/>
|
|
148
85
|
<button
|
|
149
86
|
type="button"
|
|
150
87
|
onClick={() => setShowPassword(!showPassword)}
|
|
151
|
-
className="absolute
|
|
88
|
+
className="absolute right-3 top-1/2 -translate-y-1/2 text-neutral-400 hover:text-neutral-600 dark:hover:text-neutral-300"
|
|
152
89
|
>
|
|
153
90
|
{showPassword ? (
|
|
154
|
-
<
|
|
91
|
+
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
92
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88" />
|
|
93
|
+
</svg>
|
|
155
94
|
) : (
|
|
156
|
-
<
|
|
95
|
+
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
96
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
|
|
97
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
98
|
+
</svg>
|
|
157
99
|
)}
|
|
158
100
|
</button>
|
|
159
101
|
</div>
|
|
160
102
|
</div>
|
|
161
103
|
|
|
162
|
-
{/* Submit Button */}
|
|
163
104
|
<button
|
|
164
105
|
type="submit"
|
|
165
106
|
disabled={loading}
|
|
166
|
-
className="w-full py-
|
|
107
|
+
className="w-full py-2.5 px-4 bg-blue-500 text-white font-medium rounded-xl hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500/30 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
167
108
|
>
|
|
168
|
-
{loading ?
|
|
169
|
-
<span className="flex items-center justify-center gap-2">
|
|
170
|
-
<svg className="w-5 h-5 animate-spin" viewBox="0 0 24 24" fill="none">
|
|
171
|
-
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"/>
|
|
172
|
-
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"/>
|
|
173
|
-
</svg>
|
|
174
|
-
Signing in...
|
|
175
|
-
</span>
|
|
176
|
-
) : (
|
|
177
|
-
"Sign in"
|
|
178
|
-
)}
|
|
109
|
+
{loading ? "Signing in..." : "Sign in"}
|
|
179
110
|
</button>
|
|
180
111
|
</form>
|
|
181
112
|
|
|
182
|
-
|
|
183
|
-
<div className="relative my-8">
|
|
113
|
+
<div className="relative my-6">
|
|
184
114
|
<div className="absolute inset-0 flex items-center">
|
|
185
|
-
<div className="w-full border-t border-
|
|
115
|
+
<div className="w-full border-t border-neutral-200 dark:border-neutral-700"></div>
|
|
186
116
|
</div>
|
|
187
117
|
<div className="relative flex justify-center text-sm">
|
|
188
|
-
<span className="px-
|
|
118
|
+
<span className="px-3 bg-white dark:bg-neutral-800 text-neutral-500">or</span>
|
|
189
119
|
</div>
|
|
190
120
|
</div>
|
|
191
121
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
<svg className="w-5 h-5" viewBox="0 0 24 24">
|
|
122
|
+
<div className="grid grid-cols-2 gap-3">
|
|
123
|
+
<button className="flex items-center justify-center gap-2 px-4 py-2.5 border border-neutral-200 dark:border-neutral-700 rounded-xl text-sm text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-700/50 transition-colors">
|
|
124
|
+
<svg className="w-4 h-4" viewBox="0 0 24 24">
|
|
196
125
|
<path fill="currentColor" 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"/>
|
|
197
126
|
<path fill="currentColor" 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"/>
|
|
198
127
|
<path fill="currentColor" 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"/>
|
|
@@ -200,8 +129,8 @@ export default function LoginPage() {
|
|
|
200
129
|
</svg>
|
|
201
130
|
Google
|
|
202
131
|
</button>
|
|
203
|
-
<button className="flex items-center justify-center gap-2 px-4 py-
|
|
204
|
-
<svg className="w-
|
|
132
|
+
<button className="flex items-center justify-center gap-2 px-4 py-2.5 border border-neutral-200 dark:border-neutral-700 rounded-xl text-sm text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-700/50 transition-colors">
|
|
133
|
+
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
|
|
205
134
|
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
|
206
135
|
</svg>
|
|
207
136
|
GitHub
|
|
@@ -209,14 +138,13 @@ export default function LoginPage() {
|
|
|
209
138
|
</div>
|
|
210
139
|
</div>
|
|
211
140
|
|
|
212
|
-
|
|
213
|
-
<p className="mt-8 text-center text-slate-600 dark:text-slate-400">
|
|
141
|
+
<p className="mt-6 text-center text-sm text-neutral-600 dark:text-neutral-400">
|
|
214
142
|
Don't have an account?{" "}
|
|
215
|
-
<a href="/register" className="
|
|
216
|
-
Sign up
|
|
143
|
+
<a href="/register" className="text-blue-500 hover:text-blue-600 font-medium">
|
|
144
|
+
Sign up
|
|
217
145
|
</a>
|
|
218
146
|
</p>
|
|
219
147
|
</div>
|
|
220
148
|
</div>
|
|
221
149
|
);
|
|
222
|
-
}
|
|
150
|
+
}
|
|
@@ -3,128 +3,62 @@
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { useAuth } from "./AuthProvider.tsx";
|
|
5
5
|
|
|
6
|
-
// Navigation Icons
|
|
7
|
-
function HomeIcon({ className }: { className?: string }) {
|
|
8
|
-
return (
|
|
9
|
-
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
10
|
-
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
|
11
|
-
<polyline points="9 22 9 12 15 12 15 22"/>
|
|
12
|
-
</svg>
|
|
13
|
-
);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function UsersIcon({ className }: { className?: string }) {
|
|
17
|
-
return (
|
|
18
|
-
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
19
|
-
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
|
|
20
|
-
<circle cx="9" cy="7" r="4"/>
|
|
21
|
-
<path d="M22 21v-2a4 4 0 0 0-3-3.87"/>
|
|
22
|
-
<path d="M16 3.13a4 4 0 0 1 0 7.75"/>
|
|
23
|
-
</svg>
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function ChartIcon({ className }: { className?: string }) {
|
|
28
|
-
return (
|
|
29
|
-
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
30
|
-
<line x1="18" y1="20" x2="18" y2="10"/>
|
|
31
|
-
<line x1="12" y1="20" x2="12" y2="4"/>
|
|
32
|
-
<line x1="6" y1="20" x2="6" y2="14"/>
|
|
33
|
-
</svg>
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function SettingsIcon({ className }: { className?: string }) {
|
|
38
|
-
return (
|
|
39
|
-
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
40
|
-
<circle cx="12" cy="12" r="3"/>
|
|
41
|
-
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
|
42
|
-
</svg>
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function LogoutIcon({ className }: { className?: string }) {
|
|
47
|
-
return (
|
|
48
|
-
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
49
|
-
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
|
|
50
|
-
<polyline points="16 17 21 12 16 7"/>
|
|
51
|
-
<line x1="21" y1="12" x2="9" y2="12"/>
|
|
52
|
-
</svg>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function SparklesIcon({ className }: { className?: string }) {
|
|
57
|
-
return (
|
|
58
|
-
<svg className={className} viewBox="0 0 24 24" fill="currentColor">
|
|
59
|
-
<path d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z"/>
|
|
60
|
-
</svg>
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
6
|
const navigation = [
|
|
65
|
-
{ name: "Overview", href: "/dashboard",
|
|
66
|
-
{ name: "Users", href: "/dashboard/users",
|
|
67
|
-
{ name: "Analytics", href: "/dashboard/analytics",
|
|
68
|
-
{ name: "Settings", href: "/dashboard/settings",
|
|
7
|
+
{ name: "Overview", href: "/dashboard", icon: "M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" },
|
|
8
|
+
{ name: "Users", href: "/dashboard/users", icon: "M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" },
|
|
9
|
+
{ name: "Analytics", href: "/dashboard/analytics", icon: "M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z" },
|
|
10
|
+
{ name: "Settings", href: "/dashboard/settings", icon: "M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z M15 12a3 3 0 11-6 0 3 3 0 016 0z" },
|
|
69
11
|
];
|
|
70
12
|
|
|
71
13
|
export function DashboardLayout({ children }: { children: React.ReactNode }) {
|
|
72
14
|
const { user, logout } = useAuth();
|
|
73
15
|
|
|
74
16
|
return (
|
|
75
|
-
<div className="min-h-screen bg-
|
|
76
|
-
|
|
77
|
-
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
|
78
|
-
<div className="absolute -top-[20%] -left-[10%] w-[70%] h-[70%] rounded-full bg-purple-500/10 blur-3xl animate-blob"></div>
|
|
79
|
-
<div className="absolute top-[20%] -right-[10%] w-[70%] h-[70%] rounded-full bg-indigo-500/10 blur-3xl animate-blob animation-delay-2000"></div>
|
|
80
|
-
</div>
|
|
81
|
-
|
|
82
|
-
<div className="flex h-screen relative z-10">
|
|
17
|
+
<div className="min-h-screen bg-neutral-50 dark:bg-neutral-900">
|
|
18
|
+
<div className="flex h-screen">
|
|
83
19
|
{/* Sidebar */}
|
|
84
|
-
<div className="w-
|
|
20
|
+
<div className="w-64 bg-white dark:bg-neutral-800 border-r border-neutral-200 dark:border-neutral-700 flex flex-col">
|
|
85
21
|
{/* Logo */}
|
|
86
|
-
<div className="p-
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
</div>
|
|
91
|
-
<div>
|
|
92
|
-
<h1 className="text-xl font-bold text-slate-900 dark:text-white tracking-tight">My App</h1>
|
|
93
|
-
<p className="text-xs font-medium text-slate-500 dark:text-slate-400">Dashboard</p>
|
|
94
|
-
</div>
|
|
95
|
-
</div>
|
|
22
|
+
<div className="p-6 border-b border-neutral-200 dark:border-neutral-700">
|
|
23
|
+
<a href="/" className="text-lg font-semibold text-neutral-900 dark:text-white">
|
|
24
|
+
My App
|
|
25
|
+
</a>
|
|
96
26
|
</div>
|
|
97
27
|
|
|
98
28
|
{/* Navigation */}
|
|
99
|
-
<nav className="flex-1
|
|
29
|
+
<nav className="flex-1 p-4 space-y-1">
|
|
100
30
|
{navigation.map((item) => (
|
|
101
31
|
<a
|
|
102
32
|
key={item.name}
|
|
103
33
|
href={item.href}
|
|
104
|
-
className="flex items-center gap-3 px-
|
|
34
|
+
className="flex items-center gap-3 px-3 py-2.5 text-sm font-medium rounded-xl text-neutral-600 dark:text-neutral-400 hover:bg-neutral-100 dark:hover:bg-neutral-700/50 hover:text-neutral-900 dark:hover:text-white transition-colors"
|
|
105
35
|
>
|
|
106
|
-
<
|
|
36
|
+
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
37
|
+
<path strokeLinecap="round" strokeLinejoin="round" d={item.icon} />
|
|
38
|
+
</svg>
|
|
107
39
|
{item.name}
|
|
108
40
|
</a>
|
|
109
41
|
))}
|
|
110
42
|
</nav>
|
|
111
43
|
|
|
112
|
-
{/* User
|
|
113
|
-
<div className="p-4 border-t border-
|
|
114
|
-
<div className="flex items-center gap-3 p-3 rounded-xl bg-
|
|
115
|
-
<div className="w-
|
|
44
|
+
{/* User */}
|
|
45
|
+
<div className="p-4 border-t border-neutral-200 dark:border-neutral-700">
|
|
46
|
+
<div className="flex items-center gap-3 p-3 rounded-xl bg-neutral-50 dark:bg-neutral-900">
|
|
47
|
+
<div className="w-9 h-9 rounded-full bg-blue-500 flex items-center justify-center text-white text-sm font-medium">
|
|
116
48
|
{user?.name?.[0]?.toUpperCase() || "U"}
|
|
117
49
|
</div>
|
|
118
50
|
<div className="flex-1 min-w-0">
|
|
119
|
-
<p className="text-sm font-
|
|
120
|
-
<p className="text-xs text-
|
|
51
|
+
<p className="text-sm font-medium text-neutral-900 dark:text-white truncate">{user?.name}</p>
|
|
52
|
+
<p className="text-xs text-neutral-500 truncate">{user?.email}</p>
|
|
121
53
|
</div>
|
|
122
54
|
<button
|
|
123
55
|
onClick={logout}
|
|
124
|
-
className="p-2 rounded-lg text-
|
|
125
|
-
title="
|
|
56
|
+
className="p-2 rounded-lg text-neutral-400 hover:text-neutral-600 dark:hover:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-neutral-700/50 transition-colors"
|
|
57
|
+
title="Sign out"
|
|
126
58
|
>
|
|
127
|
-
<
|
|
59
|
+
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
60
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" />
|
|
61
|
+
</svg>
|
|
128
62
|
</button>
|
|
129
63
|
</div>
|
|
130
64
|
</div>
|
|
@@ -132,11 +66,11 @@ export function DashboardLayout({ children }: { children: React.ReactNode }) {
|
|
|
132
66
|
|
|
133
67
|
{/* Main content */}
|
|
134
68
|
<div className="flex-1 overflow-y-auto">
|
|
135
|
-
<main className="p-8
|
|
69
|
+
<main className="p-8 max-w-5xl">
|
|
136
70
|
{children}
|
|
137
71
|
</main>
|
|
138
72
|
</div>
|
|
139
73
|
</div>
|
|
140
74
|
</div>
|
|
141
75
|
);
|
|
142
|
-
}
|
|
76
|
+
}
|