@authdog/react-elements 0.0.39 → 0.0.42
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/.eslintrc.js +1 -1
- package/.storybook/main.ts +21 -0
- package/.storybook/preview.ts +17 -0
- package/.storybook/vitest.setup.ts +7 -0
- package/.turbo/turbo-build.log +45 -42
- package/CHANGELOG.md +18 -0
- package/dist/components/ui/alert.js.map +1 -1
- package/dist/components/ui/alert.mjs.map +1 -1
- package/dist/components/ui/avatar.js.map +1 -1
- package/dist/components/ui/avatar.mjs.map +1 -1
- package/dist/components/ui/badge.js.map +1 -1
- package/dist/components/ui/badge.mjs.map +1 -1
- package/dist/components/ui/card.js.map +1 -1
- package/dist/components/ui/card.mjs.map +1 -1
- package/dist/components/ui/dropdown-menu.js +1 -1
- package/dist/components/ui/dropdown-menu.js.map +1 -1
- package/dist/components/ui/dropdown-menu.mjs +1 -1
- package/dist/components/ui/dropdown-menu.mjs.map +1 -1
- package/dist/components/ui/input.js.map +1 -1
- package/dist/components/ui/input.mjs.map +1 -1
- package/dist/components/ui/label.js.map +1 -1
- package/dist/components/ui/label.mjs.map +1 -1
- package/dist/components/ui/separator.js.map +1 -1
- package/dist/components/ui/separator.mjs.map +1 -1
- package/dist/components/ui/sheet.js.map +1 -1
- package/dist/components/ui/sheet.mjs.map +1 -1
- package/dist/index.d.mts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +218 -8
- package/package.json +25 -14
- package/src/components/core/client-only.tsx +10 -15
- package/src/components/core/navbar.tsx +81 -50
- package/src/components/core/placeholder-alert.tsx +7 -9
- package/src/components/core/user-dropdown.tsx +117 -57
- package/src/components/core/user-profile.tsx +180 -86
- package/src/components/flow/login.tsx +42 -29
- package/src/components/flow/totp-validator.tsx +94 -73
- package/src/components/icons.tsx +15 -21
- package/src/components/ui/alert.tsx +11 -11
- package/src/components/ui/avatar.tsx +10 -10
- package/src/components/ui/badge.tsx +9 -9
- package/src/components/ui/card.tsx +13 -13
- package/src/components/ui/dropdown-menu.tsx +214 -211
- package/src/components/ui/input.tsx +5 -5
- package/src/components/ui/label.tsx +7 -7
- package/src/components/ui/separator.tsx +7 -7
- package/src/components/ui/sheet.tsx +21 -21
- package/src/index.ts +6 -6
- package/src/stories/core/Navbar.stories.tsx +45 -0
- package/src/stories/core/PlaceholderAlert.stories.tsx +23 -0
- package/src/stories/core/UserDropdown.stories.tsx +56 -0
- package/src/stories/core/UserProfile.stories.tsx +47 -0
- package/src/stories/flow/LoginForm.stories.tsx +20 -0
- package/src/stories/flow/TotpValidator.stories.tsx +23 -0
- package/src/stories/showcase/Landing.stories.tsx +376 -0
- package/src/stories/ui/Button.stories.tsx +45 -0
- package/tsup.config.ts +6 -9
- package/vitest.config.ts +39 -0
- package/vitest.shims.d.ts +1 -0
- package/wrangler.prod.toml +4 -0
- package/ladle.config.mjs +0 -21
- package/src/main.tsx +0 -11
- package/src/preview.tsx +0 -11
- package/src/stories/Button._stories.tsx +0 -24
- package/src/stories/LoginForm.stories.tsx +0 -29
- package/src/stories/Navbar._stories.tsx +0 -28
- package/src/stories/PlaceholderAlert._stories.tsx +0 -13
- package/src/stories/TotpValidator.stories.tsx +0 -14
- package/src/stories/UserDropdown.stories.tsx +0 -36
- package/src/stories/UserProfile.stories.tsx +0 -46
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
"use client"
|
|
1
|
+
"use client";
|
|
2
2
|
|
|
3
|
-
import { useEffect, useState } from "react"
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import {
|
|
5
|
+
Avatar,
|
|
6
|
+
AvatarFallback,
|
|
7
|
+
AvatarImage,
|
|
8
|
+
} from "../../components/ui/avatar";
|
|
9
|
+
import { Button } from "../../components/ui/button";
|
|
10
|
+
import { Badge } from "../../components/ui/badge";
|
|
7
11
|
import {
|
|
8
12
|
Card,
|
|
9
13
|
CardHeader,
|
|
@@ -11,19 +15,26 @@ import {
|
|
|
11
15
|
CardDescription,
|
|
12
16
|
CardContent,
|
|
13
17
|
CardFooter,
|
|
14
|
-
} from "../../components/ui/card"
|
|
15
|
-
import { Input } from "../../components/ui/input"
|
|
16
|
-
import { Label } from "../../components/ui/label"
|
|
17
|
-
import { User, Shield, SlidersHorizontal, LucideProps } from "lucide-react"
|
|
18
|
+
} from "../../components/ui/card";
|
|
19
|
+
import { Input } from "../../components/ui/input";
|
|
20
|
+
import { Label } from "../../components/ui/label";
|
|
21
|
+
import { User, Shield, SlidersHorizontal, LucideProps } from "lucide-react";
|
|
18
22
|
|
|
19
23
|
export interface UserProfileProps {
|
|
20
24
|
loading: boolean;
|
|
21
25
|
user: any;
|
|
22
26
|
emails?: { address: string; isPrimary?: boolean }[];
|
|
23
27
|
handleAuthenticated?: () => void;
|
|
24
|
-
onRequestEmailVerification?: (
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
onRequestEmailVerification?: (
|
|
29
|
+
email: string,
|
|
30
|
+
) => Promise<{ success: boolean; message?: string } | void>;
|
|
31
|
+
onVerifyEmail?: (
|
|
32
|
+
email: string,
|
|
33
|
+
code: string,
|
|
34
|
+
) => Promise<{ success: boolean; message?: string } | void>;
|
|
35
|
+
onAddEmail?: (
|
|
36
|
+
email: string,
|
|
37
|
+
) => Promise<{ success: boolean; message?: string } | void>;
|
|
27
38
|
}
|
|
28
39
|
|
|
29
40
|
export const UserProfile = ({
|
|
@@ -34,12 +45,14 @@ export const UserProfile = ({
|
|
|
34
45
|
onVerifyEmail,
|
|
35
46
|
onAddEmail,
|
|
36
47
|
}: UserProfileProps) => {
|
|
37
|
-
const [isMounted, setIsMounted] = useState(false)
|
|
38
|
-
const [activeTab, setActiveTab] = useState<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const [
|
|
42
|
-
const [
|
|
48
|
+
const [isMounted, setIsMounted] = useState(false);
|
|
49
|
+
const [activeTab, setActiveTab] = useState<
|
|
50
|
+
"profile" | "security" | "preferences"
|
|
51
|
+
>("profile");
|
|
52
|
+
const [verifyingEmail, setVerifyingEmail] = useState<string | null>(null);
|
|
53
|
+
const [codeByEmail, setCodeByEmail] = useState<Record<string, string>>({});
|
|
54
|
+
const [addingEmail, setAddingEmail] = useState<boolean>(false);
|
|
55
|
+
const [newEmail, setNewEmail] = useState<string>("");
|
|
43
56
|
|
|
44
57
|
useEffect(() => {
|
|
45
58
|
setIsMounted(true);
|
|
@@ -53,28 +66,30 @@ export const UserProfile = ({
|
|
|
53
66
|
|
|
54
67
|
const iconProps: LucideProps = {
|
|
55
68
|
className: "mr-2 h-4 w-4",
|
|
56
|
-
"aria-hidden": "true"
|
|
57
|
-
}
|
|
69
|
+
"aria-hidden": "true",
|
|
70
|
+
};
|
|
58
71
|
|
|
59
72
|
const renderIcon = (Icon: any) => {
|
|
60
|
-
if (!isMounted) return null
|
|
61
|
-
return <Icon {...iconProps}
|
|
62
|
-
}
|
|
73
|
+
if (!isMounted) return null;
|
|
74
|
+
return <Icon {...iconProps} />;
|
|
75
|
+
};
|
|
63
76
|
|
|
64
77
|
if (!isMounted || loading) {
|
|
65
|
-
return <div>Loading...</div
|
|
78
|
+
return <div>Loading...</div>;
|
|
66
79
|
}
|
|
67
80
|
|
|
68
81
|
if (!user) {
|
|
69
|
-
return <div>No user</div
|
|
82
|
+
return <div>No user</div>;
|
|
70
83
|
}
|
|
71
|
-
|
|
84
|
+
|
|
72
85
|
return (
|
|
73
86
|
<div className="grid grid-cols-[14rem,1fr] w-full bg-transparent">
|
|
74
87
|
<div className="h-full border-r border-border p-3 md:p-4 bg-transparent flex flex-col min-w-0">
|
|
75
88
|
<div className="mb-3 md:mb-4">
|
|
76
89
|
<h1 className="text-xl font-bold text-foreground">Account</h1>
|
|
77
|
-
<p className="text-sm text-muted-foreground">
|
|
90
|
+
<p className="text-sm text-muted-foreground">
|
|
91
|
+
Manage your account info.
|
|
92
|
+
</p>
|
|
78
93
|
</div>
|
|
79
94
|
|
|
80
95
|
<nav className="space-y-1 flex-1">
|
|
@@ -89,7 +104,7 @@ export const UserProfile = ({
|
|
|
89
104
|
{renderIcon(User)}
|
|
90
105
|
Profile
|
|
91
106
|
</button>
|
|
92
|
-
|
|
107
|
+
|
|
93
108
|
{/* <button
|
|
94
109
|
onClick={() => setActiveTab("security")}
|
|
95
110
|
className={`flex items-center w-full px-3 py-2 text-sm rounded-md ${
|
|
@@ -121,8 +136,8 @@ export const UserProfile = ({
|
|
|
121
136
|
{activeTab === "profile"
|
|
122
137
|
? "Profile details"
|
|
123
138
|
: activeTab === "security"
|
|
124
|
-
|
|
125
|
-
|
|
139
|
+
? "Security settings"
|
|
140
|
+
: "Preferences"}
|
|
126
141
|
</h2>
|
|
127
142
|
{/* <button className="text-gray-500 hover:text-gray-700">
|
|
128
143
|
{renderIcon(X)}
|
|
@@ -133,14 +148,26 @@ export const UserProfile = ({
|
|
|
133
148
|
<div className="space-y-5 md:space-y-6">
|
|
134
149
|
{/* Profile Section */}
|
|
135
150
|
<div>
|
|
136
|
-
<h3 className="text-sm font-medium mb-3 text-foreground">
|
|
151
|
+
<h3 className="text-sm font-medium mb-3 text-foreground">
|
|
152
|
+
Profile
|
|
153
|
+
</h3>
|
|
137
154
|
<div className="flex items-center justify-between">
|
|
138
155
|
<div className="flex items-center">
|
|
139
156
|
<Avatar className="h-12 w-12 mr-4 border">
|
|
140
|
-
<AvatarImage
|
|
141
|
-
|
|
157
|
+
<AvatarImage
|
|
158
|
+
src={user.photos?.[0]?.value}
|
|
159
|
+
alt="Profile picture"
|
|
160
|
+
/>
|
|
161
|
+
<AvatarFallback>
|
|
162
|
+
{user.displayName
|
|
163
|
+
?.split(" ")
|
|
164
|
+
.map((n: string) => n[0])
|
|
165
|
+
.join("")}
|
|
166
|
+
</AvatarFallback>
|
|
142
167
|
</Avatar>
|
|
143
|
-
<span className="font-medium text-foreground">
|
|
168
|
+
<span className="font-medium text-foreground">
|
|
169
|
+
{user.displayName}
|
|
170
|
+
</span>
|
|
144
171
|
</div>
|
|
145
172
|
{/* <Button variant="outline" size="sm">
|
|
146
173
|
Edit profile
|
|
@@ -150,9 +177,10 @@ export const UserProfile = ({
|
|
|
150
177
|
|
|
151
178
|
{/* Email Addresses Section */}
|
|
152
179
|
<div>
|
|
153
|
-
<h3 className="text-sm font-medium mb-3 text-foreground">
|
|
180
|
+
<h3 className="text-sm font-medium mb-3 text-foreground">
|
|
181
|
+
Email addresses
|
|
182
|
+
</h3>
|
|
154
183
|
<div className="space-y-2.5">
|
|
155
|
-
|
|
156
184
|
{/* {JSON.stringify(user)} */}
|
|
157
185
|
|
|
158
186
|
{/* {(emails.length > 0 ? emails : [{ address: user.email, isPrimary: true }]).map((email, i) => (
|
|
@@ -167,11 +195,16 @@ export const UserProfile = ({
|
|
|
167
195
|
))} */}
|
|
168
196
|
|
|
169
197
|
{user.emails.map((email: any, idx: number) => {
|
|
170
|
-
const v = (user?.verifications || []).find(
|
|
171
|
-
|
|
172
|
-
|
|
198
|
+
const v = (user?.verifications || []).find(
|
|
199
|
+
(ve: any) => ve.email === email.value,
|
|
200
|
+
);
|
|
201
|
+
const isVerified = v?.verified === true;
|
|
202
|
+
const codeInput = codeByEmail[email.value] || "";
|
|
173
203
|
return (
|
|
174
|
-
<div
|
|
204
|
+
<div
|
|
205
|
+
className="flex items-start justify-between gap-2"
|
|
206
|
+
key={email.value}
|
|
207
|
+
>
|
|
175
208
|
<div className="flex flex-col">
|
|
176
209
|
<span className="text-foreground">{email.value}</span>
|
|
177
210
|
<div className="mt-1">
|
|
@@ -203,13 +236,18 @@ export const UserProfile = ({
|
|
|
203
236
|
className="h-7 w-24 text-sm rounded-md border border-border bg-background px-2 text-foreground"
|
|
204
237
|
placeholder="Code"
|
|
205
238
|
value={codeInput}
|
|
206
|
-
onChange={(e) =>
|
|
239
|
+
onChange={(e) =>
|
|
240
|
+
setCodeByEmail((m) => ({
|
|
241
|
+
...m,
|
|
242
|
+
[email.value]: e.target.value,
|
|
243
|
+
}))
|
|
244
|
+
}
|
|
207
245
|
/>
|
|
208
246
|
<button
|
|
209
247
|
className="h-7 rounded-md border border-border px-2 text-xs"
|
|
210
248
|
onClick={async () => {
|
|
211
|
-
if (!onVerifyEmail) return
|
|
212
|
-
await onVerifyEmail(email.value, codeInput)
|
|
249
|
+
if (!onVerifyEmail) return;
|
|
250
|
+
await onVerifyEmail(email.value, codeInput);
|
|
213
251
|
}}
|
|
214
252
|
>
|
|
215
253
|
Verify
|
|
@@ -226,8 +264,11 @@ export const UserProfile = ({
|
|
|
226
264
|
<button
|
|
227
265
|
className="h-7 rounded-md border border-border px-2 text-xs"
|
|
228
266
|
onClick={async () => {
|
|
229
|
-
if (onRequestEmailVerification)
|
|
230
|
-
|
|
267
|
+
if (onRequestEmailVerification)
|
|
268
|
+
await onRequestEmailVerification(
|
|
269
|
+
email.value,
|
|
270
|
+
);
|
|
271
|
+
setVerifyingEmail(email.value);
|
|
231
272
|
}}
|
|
232
273
|
>
|
|
233
274
|
Send code
|
|
@@ -238,7 +279,7 @@ export const UserProfile = ({
|
|
|
238
279
|
)}
|
|
239
280
|
</div>
|
|
240
281
|
</div>
|
|
241
|
-
)
|
|
282
|
+
);
|
|
242
283
|
})}
|
|
243
284
|
<div className="mt-2">
|
|
244
285
|
{addingEmail ? (
|
|
@@ -247,7 +288,8 @@ export const UserProfile = ({
|
|
|
247
288
|
<CardHeader>
|
|
248
289
|
<CardTitle>Add email address</CardTitle>
|
|
249
290
|
<CardDescription>
|
|
250
|
-
You'll need to verify this email address before it
|
|
291
|
+
You'll need to verify this email address before it
|
|
292
|
+
can be added to your account.
|
|
251
293
|
</CardDescription>
|
|
252
294
|
</CardHeader>
|
|
253
295
|
<CardContent>
|
|
@@ -260,11 +302,13 @@ export const UserProfile = ({
|
|
|
260
302
|
onChange={(e) => setNewEmail(e.target.value)}
|
|
261
303
|
onKeyDown={async (e) => {
|
|
262
304
|
if (e.key === "Enter") {
|
|
263
|
-
const v = String(newEmail || "")
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
305
|
+
const v = String(newEmail || "")
|
|
306
|
+
.trim()
|
|
307
|
+
.toLowerCase();
|
|
308
|
+
if (!v) return;
|
|
309
|
+
if (onAddEmail) await onAddEmail(v);
|
|
310
|
+
setAddingEmail(false);
|
|
311
|
+
setNewEmail("");
|
|
268
312
|
}
|
|
269
313
|
}}
|
|
270
314
|
/>
|
|
@@ -274,19 +318,21 @@ export const UserProfile = ({
|
|
|
274
318
|
<Button
|
|
275
319
|
variant="ghost"
|
|
276
320
|
onClick={() => {
|
|
277
|
-
setAddingEmail(false)
|
|
278
|
-
setNewEmail("")
|
|
321
|
+
setAddingEmail(false);
|
|
322
|
+
setNewEmail("");
|
|
279
323
|
}}
|
|
280
324
|
>
|
|
281
325
|
Cancel
|
|
282
326
|
</Button>
|
|
283
327
|
<Button
|
|
284
328
|
onClick={async () => {
|
|
285
|
-
const v = String(newEmail || "")
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
329
|
+
const v = String(newEmail || "")
|
|
330
|
+
.trim()
|
|
331
|
+
.toLowerCase();
|
|
332
|
+
if (!v) return;
|
|
333
|
+
if (onAddEmail) await onAddEmail(v);
|
|
334
|
+
setAddingEmail(false);
|
|
335
|
+
setNewEmail("");
|
|
290
336
|
}}
|
|
291
337
|
>
|
|
292
338
|
Add
|
|
@@ -295,7 +341,12 @@ export const UserProfile = ({
|
|
|
295
341
|
</Card>
|
|
296
342
|
</div>
|
|
297
343
|
) : (
|
|
298
|
-
<Button
|
|
344
|
+
<Button
|
|
345
|
+
variant="outline"
|
|
346
|
+
size="sm"
|
|
347
|
+
className="text-xs"
|
|
348
|
+
onClick={() => setAddingEmail(true)}
|
|
349
|
+
>
|
|
299
350
|
Add email
|
|
300
351
|
</Button>
|
|
301
352
|
)}
|
|
@@ -322,16 +373,25 @@ export const UserProfile = ({
|
|
|
322
373
|
|
|
323
374
|
{/* Connected Accounts Section */}
|
|
324
375
|
<div>
|
|
325
|
-
<h3 className="text-sm font-medium mb-3 text-gray-900 dark:text-gray-100">
|
|
376
|
+
<h3 className="text-sm font-medium mb-3 text-gray-900 dark:text-gray-100">
|
|
377
|
+
Connected accounts
|
|
378
|
+
</h3>
|
|
326
379
|
<div className="space-y-2.5">
|
|
327
|
-
<div
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
380
|
+
<div
|
|
381
|
+
className="flex items-center justify-between"
|
|
382
|
+
key={user.provider}
|
|
383
|
+
>
|
|
384
|
+
<div className="flex items-center">
|
|
385
|
+
<div className="mr-2">
|
|
386
|
+
<span className="text-gray-900 dark:text-gray-100">
|
|
387
|
+
{user.provider}
|
|
388
|
+
</span>
|
|
332
389
|
</div>
|
|
333
|
-
<span className="text-sm text-gray-500 dark:text-gray-400">{user?.emails?.[0]?.value}</span>
|
|
334
390
|
</div>
|
|
391
|
+
<span className="text-sm text-gray-500 dark:text-gray-400">
|
|
392
|
+
{user?.emails?.[0]?.value}
|
|
393
|
+
</span>
|
|
394
|
+
</div>
|
|
335
395
|
</div>
|
|
336
396
|
</div>
|
|
337
397
|
</div>
|
|
@@ -340,40 +400,62 @@ export const UserProfile = ({
|
|
|
340
400
|
{/* Password row */}
|
|
341
401
|
<div className="border rounded-md overflow-hidden">
|
|
342
402
|
<div className="flex items-center justify-between px-4 py-3">
|
|
343
|
-
<div className="text-sm text-gray-700 dark:text-gray-300">
|
|
344
|
-
|
|
403
|
+
<div className="text-sm text-gray-700 dark:text-gray-300">
|
|
404
|
+
Password
|
|
405
|
+
</div>
|
|
406
|
+
<button className="text-sm text-indigo-600 hover:underline">
|
|
407
|
+
Set password
|
|
408
|
+
</button>
|
|
345
409
|
</div>
|
|
346
410
|
</div>
|
|
347
411
|
|
|
348
412
|
{/* Passkeys row */}
|
|
349
413
|
<div className="border rounded-md overflow-hidden">
|
|
350
414
|
<div className="flex items-center justify-between px-4 py-3">
|
|
351
|
-
<div className="text-sm text-gray-700 dark:text-gray-300">
|
|
352
|
-
|
|
415
|
+
<div className="text-sm text-gray-700 dark:text-gray-300">
|
|
416
|
+
Passkeys
|
|
417
|
+
</div>
|
|
418
|
+
<button className="text-sm text-indigo-600 hover:underline">
|
|
419
|
+
+ Add a passkey
|
|
420
|
+
</button>
|
|
353
421
|
</div>
|
|
354
422
|
</div>
|
|
355
423
|
|
|
356
424
|
{/* Two-step verification row */}
|
|
357
425
|
<div className="border rounded-md overflow-hidden">
|
|
358
426
|
<div className="flex items-center justify-between px-4 py-3">
|
|
359
|
-
<div className="text-sm text-gray-700 dark:text-gray-300">
|
|
360
|
-
|
|
427
|
+
<div className="text-sm text-gray-700 dark:text-gray-300">
|
|
428
|
+
Two-step verification
|
|
429
|
+
</div>
|
|
430
|
+
<button className="text-sm text-indigo-600 hover:underline">
|
|
431
|
+
+ Add two-step verification
|
|
432
|
+
</button>
|
|
361
433
|
</div>
|
|
362
434
|
</div>
|
|
363
435
|
|
|
364
436
|
{/* Active devices list (scaffold) */}
|
|
365
437
|
<div className="border rounded-md overflow-hidden">
|
|
366
|
-
<div className="px-4 py-3 border-b text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
438
|
+
<div className="px-4 py-3 border-b text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
439
|
+
Active devices
|
|
440
|
+
</div>
|
|
367
441
|
<div className="p-4 space-y-3">
|
|
368
442
|
<div className="text-sm">
|
|
369
443
|
<div className="flex items-center gap-2">
|
|
370
444
|
<span className="inline-block h-5 w-5 rounded-sm bg-gray-900 dark:bg-white" />
|
|
371
445
|
<span className="font-medium">X11</span>
|
|
372
|
-
<span className="text-xs rounded-md border px-2 py-0.5 text-gray-600 dark:text-gray-300">
|
|
446
|
+
<span className="text-xs rounded-md border px-2 py-0.5 text-gray-600 dark:text-gray-300">
|
|
447
|
+
This device
|
|
448
|
+
</span>
|
|
449
|
+
</div>
|
|
450
|
+
<div className="text-gray-600 dark:text-gray-400 mt-1">
|
|
451
|
+
Firefox 142.0
|
|
452
|
+
</div>
|
|
453
|
+
<div className="text-gray-600 dark:text-gray-400">
|
|
454
|
+
127.0.0.1 (Local), (Your City)
|
|
455
|
+
</div>
|
|
456
|
+
<div className="text-gray-600 dark:text-gray-400">
|
|
457
|
+
Today at 7:08 PM
|
|
373
458
|
</div>
|
|
374
|
-
<div className="text-gray-600 dark:text-gray-400 mt-1">Firefox 142.0</div>
|
|
375
|
-
<div className="text-gray-600 dark:text-gray-400">127.0.0.1 (Local), (Your City)</div>
|
|
376
|
-
<div className="text-gray-600 dark:text-gray-400">Today at 7:08 PM</div>
|
|
377
459
|
</div>
|
|
378
460
|
</div>
|
|
379
461
|
</div>
|
|
@@ -381,8 +463,12 @@ export const UserProfile = ({
|
|
|
381
463
|
{/* Delete account */}
|
|
382
464
|
<div className="border rounded-md overflow-hidden">
|
|
383
465
|
<div className="flex items-center justify-between px-4 py-3">
|
|
384
|
-
<div className="text-sm text-gray-700 dark:text-gray-300">
|
|
385
|
-
|
|
466
|
+
<div className="text-sm text-gray-700 dark:text-gray-300">
|
|
467
|
+
Delete account
|
|
468
|
+
</div>
|
|
469
|
+
<button className="text-sm text-red-600 hover:underline">
|
|
470
|
+
Delete account
|
|
471
|
+
</button>
|
|
386
472
|
</div>
|
|
387
473
|
</div>
|
|
388
474
|
</div>
|
|
@@ -390,15 +476,23 @@ export const UserProfile = ({
|
|
|
390
476
|
<div className="space-y-5 md:space-y-6">
|
|
391
477
|
{/* Preferences */}
|
|
392
478
|
<div>
|
|
393
|
-
<h3 className="text-sm font-medium mb-3 text-gray-900 dark:text-gray-100">
|
|
479
|
+
<h3 className="text-sm font-medium mb-3 text-gray-900 dark:text-gray-100">
|
|
480
|
+
Preferences
|
|
481
|
+
</h3>
|
|
394
482
|
<div className="space-y-3 text-sm">
|
|
395
483
|
<div className="flex items-center justify-between">
|
|
396
|
-
<span className="text-gray-700 dark:text-gray-300">
|
|
484
|
+
<span className="text-gray-700 dark:text-gray-300">
|
|
485
|
+
Locale
|
|
486
|
+
</span>
|
|
397
487
|
<span className="text-gray-500 dark:text-gray-400">Auto</span>
|
|
398
488
|
</div>
|
|
399
489
|
<div className="flex items-center justify-between">
|
|
400
|
-
<span className="text-gray-700 dark:text-gray-300">
|
|
401
|
-
|
|
490
|
+
<span className="text-gray-700 dark:text-gray-300">
|
|
491
|
+
Theme
|
|
492
|
+
</span>
|
|
493
|
+
<span className="text-gray-500 dark:text-gray-400">
|
|
494
|
+
System
|
|
495
|
+
</span>
|
|
402
496
|
</div>
|
|
403
497
|
</div>
|
|
404
498
|
</div>
|
|
@@ -423,5 +517,5 @@ export const UserProfile = ({
|
|
|
423
517
|
</span>
|
|
424
518
|
</div> */}
|
|
425
519
|
</div>
|
|
426
|
-
)
|
|
427
|
-
}
|
|
520
|
+
);
|
|
521
|
+
};
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
"use client"
|
|
1
|
+
"use client";
|
|
2
2
|
|
|
3
|
-
import type React from "react"
|
|
3
|
+
import type React from "react";
|
|
4
4
|
|
|
5
|
-
import { useState } from "react"
|
|
6
|
-
import { Button } from "../../components/ui/button"
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { Button } from "../../components/ui/button";
|
|
7
|
+
import {
|
|
8
|
+
Card,
|
|
9
|
+
CardContent,
|
|
10
|
+
CardDescription,
|
|
11
|
+
CardFooter,
|
|
12
|
+
CardHeader,
|
|
13
|
+
CardTitle,
|
|
14
|
+
} from "../../components/ui/card";
|
|
15
|
+
import { Input } from "../../components/ui/input";
|
|
16
|
+
import { Label } from "../../components/ui/label";
|
|
17
|
+
import { Separator } from "../../components/ui/separator";
|
|
18
|
+
import { Github, AlertCircle } from "lucide-react";
|
|
19
|
+
import { Alert, AlertDescription } from "../../components/ui/alert";
|
|
13
20
|
|
|
14
21
|
export const LoginForm = () => {
|
|
15
22
|
const [email, setEmail] = useState("");
|
|
@@ -18,39 +25,42 @@ export const LoginForm = () => {
|
|
|
18
25
|
const [isLoading, setIsLoading] = useState(false);
|
|
19
26
|
|
|
20
27
|
const handleEmailLogin = async (e: React.FormEvent) => {
|
|
21
|
-
e.preventDefault()
|
|
22
|
-
setIsLoading(true)
|
|
23
|
-
setError("")
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
setIsLoading(true);
|
|
30
|
+
setError("");
|
|
24
31
|
|
|
25
32
|
try {
|
|
26
|
-
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
33
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
27
34
|
} catch (err) {
|
|
28
|
-
setError("Invalid email or password")
|
|
35
|
+
setError("Invalid email or password");
|
|
29
36
|
} finally {
|
|
30
|
-
setIsLoading(false)
|
|
37
|
+
setIsLoading(false);
|
|
31
38
|
}
|
|
32
|
-
}
|
|
39
|
+
};
|
|
33
40
|
|
|
34
41
|
const handleOAuthLogin = async (provider: string) => {
|
|
35
|
-
setIsLoading(true)
|
|
36
|
-
setError("")
|
|
42
|
+
setIsLoading(true);
|
|
43
|
+
setError("");
|
|
37
44
|
|
|
38
45
|
try {
|
|
39
|
-
|
|
40
|
-
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
46
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
41
47
|
} catch (err) {
|
|
42
|
-
setError(`Failed to login with ${provider}`)
|
|
48
|
+
setError(`Failed to login with ${provider}`);
|
|
43
49
|
} finally {
|
|
44
|
-
setIsLoading(false)
|
|
50
|
+
setIsLoading(false);
|
|
45
51
|
}
|
|
46
|
-
}
|
|
52
|
+
};
|
|
47
53
|
|
|
48
54
|
return (
|
|
49
55
|
<div className="flex min-h-screen items-center justify-center bg-gray-50 px-4 py-12 sm:px-6 lg:px-8">
|
|
50
56
|
<Card className="w-full max-w-md">
|
|
51
57
|
<CardHeader className="space-y-1">
|
|
52
|
-
<CardTitle className="text-2xl font-bold text-center">
|
|
53
|
-
|
|
58
|
+
<CardTitle className="text-2xl font-bold text-center">
|
|
59
|
+
Login
|
|
60
|
+
</CardTitle>
|
|
61
|
+
<CardDescription className="text-center">
|
|
62
|
+
Choose your preferred login method
|
|
63
|
+
</CardDescription>
|
|
54
64
|
</CardHeader>
|
|
55
65
|
<CardContent className="space-y-4">
|
|
56
66
|
{error && (
|
|
@@ -122,7 +132,10 @@ export const LoginForm = () => {
|
|
|
122
132
|
<div className="space-y-2">
|
|
123
133
|
<div className="flex items-center justify-between">
|
|
124
134
|
<Label htmlFor="password">Password</Label>
|
|
125
|
-
<a
|
|
135
|
+
<a
|
|
136
|
+
href="/forgot-password"
|
|
137
|
+
className="text-xs text-primary hover:underline"
|
|
138
|
+
>
|
|
126
139
|
Forgot password?
|
|
127
140
|
</a>
|
|
128
141
|
</div>
|
|
@@ -150,5 +163,5 @@ export const LoginForm = () => {
|
|
|
150
163
|
</CardFooter>
|
|
151
164
|
</Card>
|
|
152
165
|
</div>
|
|
153
|
-
)
|
|
154
|
-
}
|
|
166
|
+
);
|
|
167
|
+
};
|