@authdog/react-elements 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/.turbo/turbo-build.log +44 -41
- package/CHANGELOG.md +12 -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.map +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 +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +3 -3
- 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 +97 -55
- package/src/components/core/user-profile.tsx +180 -85
- package/src/components/flow/login.tsx +42 -29
- package/src/components/flow/totp-validator.tsx +94 -73
- package/src/components/icons.tsx +13 -13
- 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 +39 -37
- 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/main.tsx +4 -6
- package/src/preview.tsx +4 -8
- package/src/stories/Button._stories.tsx +15 -11
- package/src/stories/LoginForm.stories.tsx +6 -6
- package/src/stories/Navbar._stories.tsx +57 -19
- package/src/stories/PlaceholderAlert._stories.tsx +8 -8
- package/src/stories/TotpValidator.stories.tsx +10 -8
- package/src/stories/UserDropdown.stories.tsx +7 -9
- package/src/stories/UserProfile.stories.tsx +12 -12
- package/tsup.config.ts +6 -9
|
@@ -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,6 +104,7 @@ export const UserProfile = ({
|
|
|
89
104
|
{renderIcon(User)}
|
|
90
105
|
Profile
|
|
91
106
|
</button>
|
|
107
|
+
|
|
92
108
|
{/* <button
|
|
93
109
|
onClick={() => setActiveTab("security")}
|
|
94
110
|
className={`flex items-center w-full px-3 py-2 text-sm rounded-md ${
|
|
@@ -120,8 +136,8 @@ export const UserProfile = ({
|
|
|
120
136
|
{activeTab === "profile"
|
|
121
137
|
? "Profile details"
|
|
122
138
|
: activeTab === "security"
|
|
123
|
-
|
|
124
|
-
|
|
139
|
+
? "Security settings"
|
|
140
|
+
: "Preferences"}
|
|
125
141
|
</h2>
|
|
126
142
|
{/* <button className="text-gray-500 hover:text-gray-700">
|
|
127
143
|
{renderIcon(X)}
|
|
@@ -132,14 +148,26 @@ export const UserProfile = ({
|
|
|
132
148
|
<div className="space-y-5 md:space-y-6">
|
|
133
149
|
{/* Profile Section */}
|
|
134
150
|
<div>
|
|
135
|
-
<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>
|
|
136
154
|
<div className="flex items-center justify-between">
|
|
137
155
|
<div className="flex items-center">
|
|
138
156
|
<Avatar className="h-12 w-12 mr-4 border">
|
|
139
|
-
<AvatarImage
|
|
140
|
-
|
|
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>
|
|
141
167
|
</Avatar>
|
|
142
|
-
<span className="font-medium text-foreground">
|
|
168
|
+
<span className="font-medium text-foreground">
|
|
169
|
+
{user.displayName}
|
|
170
|
+
</span>
|
|
143
171
|
</div>
|
|
144
172
|
{/* <Button variant="outline" size="sm">
|
|
145
173
|
Edit profile
|
|
@@ -149,9 +177,10 @@ export const UserProfile = ({
|
|
|
149
177
|
|
|
150
178
|
{/* Email Addresses Section */}
|
|
151
179
|
<div>
|
|
152
|
-
<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>
|
|
153
183
|
<div className="space-y-2.5">
|
|
154
|
-
|
|
155
184
|
{/* {JSON.stringify(user)} */}
|
|
156
185
|
|
|
157
186
|
{/* {(emails.length > 0 ? emails : [{ address: user.email, isPrimary: true }]).map((email, i) => (
|
|
@@ -166,11 +195,16 @@ export const UserProfile = ({
|
|
|
166
195
|
))} */}
|
|
167
196
|
|
|
168
197
|
{user.emails.map((email: any, idx: number) => {
|
|
169
|
-
const v = (user?.verifications || []).find(
|
|
170
|
-
|
|
171
|
-
|
|
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] || "";
|
|
172
203
|
return (
|
|
173
|
-
<div
|
|
204
|
+
<div
|
|
205
|
+
className="flex items-start justify-between gap-2"
|
|
206
|
+
key={email.value}
|
|
207
|
+
>
|
|
174
208
|
<div className="flex flex-col">
|
|
175
209
|
<span className="text-foreground">{email.value}</span>
|
|
176
210
|
<div className="mt-1">
|
|
@@ -202,13 +236,18 @@ export const UserProfile = ({
|
|
|
202
236
|
className="h-7 w-24 text-sm rounded-md border border-border bg-background px-2 text-foreground"
|
|
203
237
|
placeholder="Code"
|
|
204
238
|
value={codeInput}
|
|
205
|
-
onChange={(e) =>
|
|
239
|
+
onChange={(e) =>
|
|
240
|
+
setCodeByEmail((m) => ({
|
|
241
|
+
...m,
|
|
242
|
+
[email.value]: e.target.value,
|
|
243
|
+
}))
|
|
244
|
+
}
|
|
206
245
|
/>
|
|
207
246
|
<button
|
|
208
247
|
className="h-7 rounded-md border border-border px-2 text-xs"
|
|
209
248
|
onClick={async () => {
|
|
210
|
-
if (!onVerifyEmail) return
|
|
211
|
-
await onVerifyEmail(email.value, codeInput)
|
|
249
|
+
if (!onVerifyEmail) return;
|
|
250
|
+
await onVerifyEmail(email.value, codeInput);
|
|
212
251
|
}}
|
|
213
252
|
>
|
|
214
253
|
Verify
|
|
@@ -225,8 +264,11 @@ export const UserProfile = ({
|
|
|
225
264
|
<button
|
|
226
265
|
className="h-7 rounded-md border border-border px-2 text-xs"
|
|
227
266
|
onClick={async () => {
|
|
228
|
-
if (onRequestEmailVerification)
|
|
229
|
-
|
|
267
|
+
if (onRequestEmailVerification)
|
|
268
|
+
await onRequestEmailVerification(
|
|
269
|
+
email.value,
|
|
270
|
+
);
|
|
271
|
+
setVerifyingEmail(email.value);
|
|
230
272
|
}}
|
|
231
273
|
>
|
|
232
274
|
Send code
|
|
@@ -237,7 +279,7 @@ export const UserProfile = ({
|
|
|
237
279
|
)}
|
|
238
280
|
</div>
|
|
239
281
|
</div>
|
|
240
|
-
)
|
|
282
|
+
);
|
|
241
283
|
})}
|
|
242
284
|
<div className="mt-2">
|
|
243
285
|
{addingEmail ? (
|
|
@@ -246,7 +288,8 @@ export const UserProfile = ({
|
|
|
246
288
|
<CardHeader>
|
|
247
289
|
<CardTitle>Add email address</CardTitle>
|
|
248
290
|
<CardDescription>
|
|
249
|
-
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.
|
|
250
293
|
</CardDescription>
|
|
251
294
|
</CardHeader>
|
|
252
295
|
<CardContent>
|
|
@@ -259,11 +302,13 @@ export const UserProfile = ({
|
|
|
259
302
|
onChange={(e) => setNewEmail(e.target.value)}
|
|
260
303
|
onKeyDown={async (e) => {
|
|
261
304
|
if (e.key === "Enter") {
|
|
262
|
-
const v = String(newEmail || "")
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
305
|
+
const v = String(newEmail || "")
|
|
306
|
+
.trim()
|
|
307
|
+
.toLowerCase();
|
|
308
|
+
if (!v) return;
|
|
309
|
+
if (onAddEmail) await onAddEmail(v);
|
|
310
|
+
setAddingEmail(false);
|
|
311
|
+
setNewEmail("");
|
|
267
312
|
}
|
|
268
313
|
}}
|
|
269
314
|
/>
|
|
@@ -273,19 +318,21 @@ export const UserProfile = ({
|
|
|
273
318
|
<Button
|
|
274
319
|
variant="ghost"
|
|
275
320
|
onClick={() => {
|
|
276
|
-
setAddingEmail(false)
|
|
277
|
-
setNewEmail("")
|
|
321
|
+
setAddingEmail(false);
|
|
322
|
+
setNewEmail("");
|
|
278
323
|
}}
|
|
279
324
|
>
|
|
280
325
|
Cancel
|
|
281
326
|
</Button>
|
|
282
327
|
<Button
|
|
283
328
|
onClick={async () => {
|
|
284
|
-
const v = String(newEmail || "")
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
329
|
+
const v = String(newEmail || "")
|
|
330
|
+
.trim()
|
|
331
|
+
.toLowerCase();
|
|
332
|
+
if (!v) return;
|
|
333
|
+
if (onAddEmail) await onAddEmail(v);
|
|
334
|
+
setAddingEmail(false);
|
|
335
|
+
setNewEmail("");
|
|
289
336
|
}}
|
|
290
337
|
>
|
|
291
338
|
Add
|
|
@@ -294,7 +341,12 @@ export const UserProfile = ({
|
|
|
294
341
|
</Card>
|
|
295
342
|
</div>
|
|
296
343
|
) : (
|
|
297
|
-
<Button
|
|
344
|
+
<Button
|
|
345
|
+
variant="outline"
|
|
346
|
+
size="sm"
|
|
347
|
+
className="text-xs"
|
|
348
|
+
onClick={() => setAddingEmail(true)}
|
|
349
|
+
>
|
|
298
350
|
Add email
|
|
299
351
|
</Button>
|
|
300
352
|
)}
|
|
@@ -321,16 +373,25 @@ export const UserProfile = ({
|
|
|
321
373
|
|
|
322
374
|
{/* Connected Accounts Section */}
|
|
323
375
|
<div>
|
|
324
|
-
<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>
|
|
325
379
|
<div className="space-y-2.5">
|
|
326
|
-
<div
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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>
|
|
331
389
|
</div>
|
|
332
|
-
<span className="text-sm text-gray-500 dark:text-gray-400">{user?.emails?.[0]?.value}</span>
|
|
333
390
|
</div>
|
|
391
|
+
<span className="text-sm text-gray-500 dark:text-gray-400">
|
|
392
|
+
{user?.emails?.[0]?.value}
|
|
393
|
+
</span>
|
|
394
|
+
</div>
|
|
334
395
|
</div>
|
|
335
396
|
</div>
|
|
336
397
|
</div>
|
|
@@ -339,40 +400,62 @@ export const UserProfile = ({
|
|
|
339
400
|
{/* Password row */}
|
|
340
401
|
<div className="border rounded-md overflow-hidden">
|
|
341
402
|
<div className="flex items-center justify-between px-4 py-3">
|
|
342
|
-
<div className="text-sm text-gray-700 dark:text-gray-300">
|
|
343
|
-
|
|
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>
|
|
344
409
|
</div>
|
|
345
410
|
</div>
|
|
346
411
|
|
|
347
412
|
{/* Passkeys row */}
|
|
348
413
|
<div className="border rounded-md overflow-hidden">
|
|
349
414
|
<div className="flex items-center justify-between px-4 py-3">
|
|
350
|
-
<div className="text-sm text-gray-700 dark:text-gray-300">
|
|
351
|
-
|
|
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>
|
|
352
421
|
</div>
|
|
353
422
|
</div>
|
|
354
423
|
|
|
355
424
|
{/* Two-step verification row */}
|
|
356
425
|
<div className="border rounded-md overflow-hidden">
|
|
357
426
|
<div className="flex items-center justify-between px-4 py-3">
|
|
358
|
-
<div className="text-sm text-gray-700 dark:text-gray-300">
|
|
359
|
-
|
|
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>
|
|
360
433
|
</div>
|
|
361
434
|
</div>
|
|
362
435
|
|
|
363
436
|
{/* Active devices list (scaffold) */}
|
|
364
437
|
<div className="border rounded-md overflow-hidden">
|
|
365
|
-
<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>
|
|
366
441
|
<div className="p-4 space-y-3">
|
|
367
442
|
<div className="text-sm">
|
|
368
443
|
<div className="flex items-center gap-2">
|
|
369
444
|
<span className="inline-block h-5 w-5 rounded-sm bg-gray-900 dark:bg-white" />
|
|
370
445
|
<span className="font-medium">X11</span>
|
|
371
|
-
<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
|
|
372
458
|
</div>
|
|
373
|
-
<div className="text-gray-600 dark:text-gray-400 mt-1">Firefox 142.0</div>
|
|
374
|
-
<div className="text-gray-600 dark:text-gray-400">127.0.0.1 (Local), (Your City)</div>
|
|
375
|
-
<div className="text-gray-600 dark:text-gray-400">Today at 7:08 PM</div>
|
|
376
459
|
</div>
|
|
377
460
|
</div>
|
|
378
461
|
</div>
|
|
@@ -380,8 +463,12 @@ export const UserProfile = ({
|
|
|
380
463
|
{/* Delete account */}
|
|
381
464
|
<div className="border rounded-md overflow-hidden">
|
|
382
465
|
<div className="flex items-center justify-between px-4 py-3">
|
|
383
|
-
<div className="text-sm text-gray-700 dark:text-gray-300">
|
|
384
|
-
|
|
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>
|
|
385
472
|
</div>
|
|
386
473
|
</div>
|
|
387
474
|
</div>
|
|
@@ -389,15 +476,23 @@ export const UserProfile = ({
|
|
|
389
476
|
<div className="space-y-5 md:space-y-6">
|
|
390
477
|
{/* Preferences */}
|
|
391
478
|
<div>
|
|
392
|
-
<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>
|
|
393
482
|
<div className="space-y-3 text-sm">
|
|
394
483
|
<div className="flex items-center justify-between">
|
|
395
|
-
<span className="text-gray-700 dark:text-gray-300">
|
|
484
|
+
<span className="text-gray-700 dark:text-gray-300">
|
|
485
|
+
Locale
|
|
486
|
+
</span>
|
|
396
487
|
<span className="text-gray-500 dark:text-gray-400">Auto</span>
|
|
397
488
|
</div>
|
|
398
489
|
<div className="flex items-center justify-between">
|
|
399
|
-
<span className="text-gray-700 dark:text-gray-300">
|
|
400
|
-
|
|
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>
|
|
401
496
|
</div>
|
|
402
497
|
</div>
|
|
403
498
|
</div>
|
|
@@ -422,5 +517,5 @@ export const UserProfile = ({
|
|
|
422
517
|
</span>
|
|
423
518
|
</div> */}
|
|
424
519
|
</div>
|
|
425
|
-
)
|
|
426
|
-
}
|
|
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
|
+
};
|