@authdog/react-elements 0.0.50 → 0.2.0

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.
Files changed (101) hide show
  1. package/dist/components/ui/alert.d.mts +12 -0
  2. package/dist/components/ui/alert.d.ts +12 -0
  3. package/dist/components/ui/alert.js.map +1 -1
  4. package/dist/components/ui/alert.mjs.map +1 -1
  5. package/dist/components/ui/avatar.d.mts +8 -0
  6. package/dist/components/ui/avatar.d.ts +8 -0
  7. package/dist/components/ui/avatar.js.map +1 -1
  8. package/dist/components/ui/avatar.mjs.map +1 -1
  9. package/dist/components/ui/badge.d.mts +12 -0
  10. package/dist/components/ui/badge.d.ts +12 -0
  11. package/dist/components/ui/badge.js.map +1 -1
  12. package/dist/components/ui/badge.mjs.map +1 -1
  13. package/dist/components/ui/button.d.mts +14 -0
  14. package/dist/components/ui/button.d.ts +14 -0
  15. package/dist/components/ui/button.js.map +1 -1
  16. package/dist/components/ui/button.mjs.map +1 -1
  17. package/dist/components/ui/card.d.mts +11 -0
  18. package/dist/components/ui/card.d.ts +11 -0
  19. package/dist/components/ui/card.js.map +1 -1
  20. package/dist/components/ui/card.mjs.map +1 -1
  21. package/dist/components/ui/dropdown-menu.d.mts +27 -0
  22. package/dist/components/ui/dropdown-menu.d.ts +27 -0
  23. package/dist/components/ui/dropdown-menu.js.map +1 -1
  24. package/dist/components/ui/dropdown-menu.mjs.map +1 -1
  25. package/dist/components/ui/input.d.mts +5 -0
  26. package/dist/components/ui/input.d.ts +5 -0
  27. package/dist/components/ui/input.js.map +1 -1
  28. package/dist/components/ui/input.mjs.map +1 -1
  29. package/dist/components/ui/label.d.mts +6 -0
  30. package/dist/components/ui/label.d.ts +6 -0
  31. package/dist/components/ui/label.js.map +1 -1
  32. package/dist/components/ui/label.mjs.map +1 -1
  33. package/dist/components/ui/separator.d.mts +6 -0
  34. package/dist/components/ui/separator.d.ts +6 -0
  35. package/dist/components/ui/separator.js.map +1 -1
  36. package/dist/components/ui/separator.mjs.map +1 -1
  37. package/dist/components/ui/sheet.d.mts +15 -0
  38. package/dist/components/ui/sheet.d.ts +15 -0
  39. package/dist/components/ui/sheet.js.map +1 -1
  40. package/dist/components/ui/sheet.mjs.map +1 -1
  41. package/dist/components/ui/theme-toggle.d.mts +5 -0
  42. package/dist/components/ui/theme-toggle.d.ts +5 -0
  43. package/dist/components/ui/theme-toggle.js.map +1 -1
  44. package/dist/components/ui/theme-toggle.mjs.map +1 -1
  45. package/dist/index.d.mts +12 -21
  46. package/dist/index.d.ts +12 -21
  47. package/dist/index.js +1 -1
  48. package/dist/index.js.map +1 -1
  49. package/dist/index.mjs +1 -1
  50. package/dist/index.mjs.map +1 -1
  51. package/dist/lib/utils.d.mts +5 -0
  52. package/dist/lib/utils.d.ts +5 -0
  53. package/dist/lib/utils.js.map +1 -1
  54. package/dist/lib/utils.mjs.map +1 -1
  55. package/dist/styles.css +1 -4
  56. package/package.json +40 -25
  57. package/.eslintrc.js +0 -9
  58. package/.storybook/main.ts +0 -21
  59. package/.storybook/preview.ts +0 -17
  60. package/.storybook/vitest.setup.ts +0 -7
  61. package/.turbo/turbo-build.log +0 -77
  62. package/CHANGELOG.md +0 -292
  63. package/components.json +0 -20
  64. package/postcss.config.mjs +0 -11
  65. package/src/components/core/client-only.tsx +0 -15
  66. package/src/components/core/navbar.tsx +0 -312
  67. package/src/components/core/placeholder-alert.tsx +0 -23
  68. package/src/components/core/user-dropdown.tsx +0 -160
  69. package/src/components/core/user-profile.tsx +0 -521
  70. package/src/components/flow/login.tsx +0 -167
  71. package/src/components/flow/totp-validator.tsx +0 -252
  72. package/src/components/icons.tsx +0 -30
  73. package/src/components/ui/alert.tsx +0 -66
  74. package/src/components/ui/avatar.tsx +0 -53
  75. package/src/components/ui/badge.tsx +0 -46
  76. package/src/components/ui/button.tsx +0 -56
  77. package/src/components/ui/card.tsx +0 -92
  78. package/src/components/ui/dropdown-menu.tsx +0 -265
  79. package/src/components/ui/input.tsx +0 -21
  80. package/src/components/ui/label.tsx +0 -24
  81. package/src/components/ui/separator.tsx +0 -28
  82. package/src/components/ui/sheet.tsx +0 -142
  83. package/src/components/ui/theme-toggle.tsx +0 -56
  84. package/src/global.css +0 -81
  85. package/src/index.ts +0 -8
  86. package/src/lib/utils.ts +0 -6
  87. package/src/stories/core/Navbar.stories.tsx +0 -93
  88. package/src/stories/core/PlaceholderAlert.stories.tsx +0 -23
  89. package/src/stories/core/UserDropdown.stories.tsx +0 -56
  90. package/src/stories/core/UserProfile.stories.tsx +0 -47
  91. package/src/stories/flow/LoginForm.stories.tsx +0 -20
  92. package/src/stories/flow/TotpValidator.stories.tsx +0 -23
  93. package/src/stories/showcase/Landing.stories.tsx +0 -376
  94. package/src/stories/ui/Button.stories.tsx +0 -45
  95. package/src/types.ts +0 -0
  96. package/tailwind.config.ts +0 -82
  97. package/tsconfig.json +0 -11
  98. package/tsup.config.ts +0 -31
  99. package/vitest.config.ts +0 -39
  100. package/vitest.shims.d.ts +0 -1
  101. package/wrangler.prod.toml +0 -4
@@ -1,521 +0,0 @@
1
- "use client";
2
-
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";
11
- import {
12
- Card,
13
- CardHeader,
14
- CardTitle,
15
- CardDescription,
16
- CardContent,
17
- CardFooter,
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";
22
-
23
- export interface UserProfileProps {
24
- loading: boolean;
25
- user: any;
26
- emails?: { address: string; isPrimary?: boolean }[];
27
- handleAuthenticated?: () => void;
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>;
38
- }
39
-
40
- export const UserProfile = ({
41
- loading,
42
- user,
43
- handleAuthenticated,
44
- onRequestEmailVerification,
45
- onVerifyEmail,
46
- onAddEmail,
47
- }: UserProfileProps) => {
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>("");
56
-
57
- useEffect(() => {
58
- setIsMounted(true);
59
- }, []);
60
-
61
- useEffect(() => {
62
- if (!loading && handleAuthenticated) {
63
- handleAuthenticated();
64
- }
65
- }, [loading, user, handleAuthenticated]);
66
-
67
- const iconProps: LucideProps = {
68
- className: "mr-2 h-4 w-4",
69
- "aria-hidden": "true",
70
- };
71
-
72
- const renderIcon = (Icon: any) => {
73
- if (!isMounted) return null;
74
- return <Icon {...iconProps} />;
75
- };
76
-
77
- if (!isMounted || loading) {
78
- return <div>Loading...</div>;
79
- }
80
-
81
- if (!user) {
82
- return <div>No user</div>;
83
- }
84
-
85
- return (
86
- <div className="grid grid-cols-[14rem,1fr] w-full bg-transparent">
87
- <div className="h-full border-r border-border p-3 md:p-4 bg-transparent flex flex-col min-w-0">
88
- <div className="mb-3 md:mb-4">
89
- <h1 className="text-xl font-bold text-foreground">Account</h1>
90
- <p className="text-sm text-muted-foreground">
91
- Manage your account info.
92
- </p>
93
- </div>
94
-
95
- <nav className="space-y-1 flex-1">
96
- <button
97
- onClick={() => setActiveTab("profile")}
98
- className={`flex items-center w-full px-3 py-2 text-sm rounded-md ${
99
- activeTab === "profile"
100
- ? "bg-muted text-foreground"
101
- : "text-muted-foreground hover:bg-muted/50"
102
- }`}
103
- >
104
- {renderIcon(User)}
105
- Profile
106
- </button>
107
-
108
- {/* <button
109
- onClick={() => setActiveTab("security")}
110
- className={`flex items-center w-full px-3 py-2 text-sm rounded-md ${
111
- activeTab === "security"
112
- ? "bg-muted text-foreground"
113
- : "text-muted-foreground hover:bg-muted/50"
114
- }`}
115
- >
116
- {renderIcon(Shield)}
117
- Security
118
- </button>
119
- <button
120
- onClick={() => setActiveTab("preferences")}
121
- className={`flex items-center w-full px-3 py-2 text-sm rounded-md ${
122
- activeTab === "preferences"
123
- ? "bg-muted text-foreground"
124
- : "text-muted-foreground hover:bg-muted/50"
125
- }`}
126
- >
127
- {renderIcon(SlidersHorizontal)}
128
- Preferences
129
- </button> */}
130
- </nav>
131
- </div>
132
-
133
- <div className="h-full p-3 md:p-5 min-w-0 bg-transparent">
134
- <div className="flex justify-between items-center mb-3 md:mb-4">
135
- <h2 className="text-xl font-semibold text-foreground">
136
- {activeTab === "profile"
137
- ? "Profile details"
138
- : activeTab === "security"
139
- ? "Security settings"
140
- : "Preferences"}
141
- </h2>
142
- {/* <button className="text-gray-500 hover:text-gray-700">
143
- {renderIcon(X)}
144
- </button> */}
145
- </div>
146
-
147
- {activeTab === "profile" ? (
148
- <div className="space-y-5 md:space-y-6">
149
- {/* Profile Section */}
150
- <div>
151
- <h3 className="text-sm font-medium mb-3 text-foreground">
152
- Profile
153
- </h3>
154
- <div className="flex items-center justify-between">
155
- <div className="flex items-center">
156
- <Avatar className="h-12 w-12 mr-4 border">
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>
167
- </Avatar>
168
- <span className="font-medium text-foreground">
169
- {user.displayName}
170
- </span>
171
- </div>
172
- {/* <Button variant="outline" size="sm">
173
- Edit profile
174
- </Button> */}
175
- </div>
176
- </div>
177
-
178
- {/* Email Addresses Section */}
179
- <div>
180
- <h3 className="text-sm font-medium mb-3 text-foreground">
181
- Email addresses
182
- </h3>
183
- <div className="space-y-2.5">
184
- {/* {JSON.stringify(user)} */}
185
-
186
- {/* {(emails.length > 0 ? emails : [{ address: user.email, isPrimary: true }]).map((email, i) => (
187
- <div className="flex items-center justify-between" key={email.address}>
188
- <span>{email.address}</span>
189
- {email.isPrimary && (
190
- <Badge variant="outline" className="text-xs bg-gray-100 text-gray-700 hover:bg-gray-100">
191
- Primary
192
- </Badge>
193
- )}
194
- </div>
195
- ))} */}
196
-
197
- {user.emails.map((email: any, idx: number) => {
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] || "";
203
- return (
204
- <div
205
- className="flex items-start justify-between gap-2"
206
- key={email.value}
207
- >
208
- <div className="flex flex-col">
209
- <span className="text-foreground">{email.value}</span>
210
- <div className="mt-1">
211
- {isVerified ? (
212
- <Badge className="text-xs rounded-full px-2.5 py-0.5 bg-green-100 text-green-800 border border-green-300 dark:bg-green-500/20 dark:text-green-200 dark:border-green-400/40">
213
- Verified
214
- </Badge>
215
- ) : (
216
- <Badge className="text-xs rounded-full px-2.5 py-0.5 bg-amber-100 text-amber-800 border border-amber-300 dark:bg-amber-500/20 dark:text-amber-200 dark:border-amber-400/40">
217
- Not verified
218
- </Badge>
219
- )}
220
- </div>
221
- </div>
222
- <div className="flex items-center gap-2">
223
- {idx === 0 && (
224
- <Badge
225
- variant="outline"
226
- className="text-xs bg-muted text-foreground hover:bg-muted"
227
- >
228
- Primary
229
- </Badge>
230
- )}
231
- {!isVerified && (
232
- <>
233
- {verifyingEmail === email.value ? (
234
- <div className="flex items-center gap-1">
235
- <input
236
- className="h-7 w-24 text-sm rounded-md border border-border bg-background px-2 text-foreground"
237
- placeholder="Code"
238
- value={codeInput}
239
- onChange={(e) =>
240
- setCodeByEmail((m) => ({
241
- ...m,
242
- [email.value]: e.target.value,
243
- }))
244
- }
245
- />
246
- <button
247
- className="h-7 rounded-md border border-border px-2 text-xs"
248
- onClick={async () => {
249
- if (!onVerifyEmail) return;
250
- await onVerifyEmail(email.value, codeInput);
251
- }}
252
- >
253
- Verify
254
- </button>
255
- <button
256
- className="h-7 rounded-md border border-border px-2 text-xs"
257
- onClick={() => setVerifyingEmail(null)}
258
- >
259
- Cancel
260
- </button>
261
- </div>
262
- ) : (
263
- <>
264
- <button
265
- className="h-7 rounded-md border border-border px-2 text-xs"
266
- onClick={async () => {
267
- if (onRequestEmailVerification)
268
- await onRequestEmailVerification(
269
- email.value,
270
- );
271
- setVerifyingEmail(email.value);
272
- }}
273
- >
274
- Send code
275
- </button>
276
- </>
277
- )}
278
- </>
279
- )}
280
- </div>
281
- </div>
282
- );
283
- })}
284
- <div className="mt-2">
285
- {addingEmail ? (
286
- <div className="max-w-md">
287
- <Card>
288
- <CardHeader>
289
- <CardTitle>Add email address</CardTitle>
290
- <CardDescription>
291
- You'll need to verify this email address before it
292
- can be added to your account.
293
- </CardDescription>
294
- </CardHeader>
295
- <CardContent>
296
- <div className="space-y-2">
297
- <Label htmlFor="new-email">Email address</Label>
298
- <Input
299
- id="new-email"
300
- placeholder="Enter your email address"
301
- value={newEmail}
302
- onChange={(e) => setNewEmail(e.target.value)}
303
- onKeyDown={async (e) => {
304
- if (e.key === "Enter") {
305
- const v = String(newEmail || "")
306
- .trim()
307
- .toLowerCase();
308
- if (!v) return;
309
- if (onAddEmail) await onAddEmail(v);
310
- setAddingEmail(false);
311
- setNewEmail("");
312
- }
313
- }}
314
- />
315
- </div>
316
- </CardContent>
317
- <CardFooter className="justify-end gap-2">
318
- <Button
319
- variant="ghost"
320
- onClick={() => {
321
- setAddingEmail(false);
322
- setNewEmail("");
323
- }}
324
- >
325
- Cancel
326
- </Button>
327
- <Button
328
- onClick={async () => {
329
- const v = String(newEmail || "")
330
- .trim()
331
- .toLowerCase();
332
- if (!v) return;
333
- if (onAddEmail) await onAddEmail(v);
334
- setAddingEmail(false);
335
- setNewEmail("");
336
- }}
337
- >
338
- Add
339
- </Button>
340
- </CardFooter>
341
- </Card>
342
- </div>
343
- ) : (
344
- <Button
345
- variant="outline"
346
- size="sm"
347
- className="text-xs"
348
- onClick={() => setAddingEmail(true)}
349
- >
350
- Add email
351
- </Button>
352
- )}
353
- </div>
354
- </div>
355
- </div>
356
-
357
- {/* Phone Number Section */}
358
- {/* <div>
359
- <h3 className="text-sm font-medium mb-3">Phone number</h3>
360
- <div className="space-y-2.5">
361
- <div className="flex items-center justify-between">
362
- <span>+1 (555) 123-4567</span>
363
- <Badge variant="outline" className="text-xs bg-gray-100 text-gray-700 hover:bg-gray-100">
364
- Primary
365
- </Badge>
366
- </div>
367
- <Button variant="ghost" size="sm" className="flex items-center text-gray-700">
368
- {renderIcon(PlusCircle)}
369
- Add phone number
370
- </Button>
371
- </div>
372
- </div> */}
373
-
374
- {/* Connected Accounts Section */}
375
- <div>
376
- <h3 className="text-sm font-medium mb-3 text-gray-900 dark:text-gray-100">
377
- Connected accounts
378
- </h3>
379
- <div className="space-y-2.5">
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>
389
- </div>
390
- </div>
391
- <span className="text-sm text-gray-500 dark:text-gray-400">
392
- {user?.emails?.[0]?.value}
393
- </span>
394
- </div>
395
- </div>
396
- </div>
397
- </div>
398
- ) : activeTab === "security" ? (
399
- <div className="space-y-5 md:space-y-6">
400
- {/* Password row */}
401
- <div className="border rounded-md overflow-hidden">
402
- <div className="flex items-center justify-between px-4 py-3">
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>
409
- </div>
410
- </div>
411
-
412
- {/* Passkeys row */}
413
- <div className="border rounded-md overflow-hidden">
414
- <div className="flex items-center justify-between px-4 py-3">
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
- +&nbsp;Add a passkey
420
- </button>
421
- </div>
422
- </div>
423
-
424
- {/* Two-step verification row */}
425
- <div className="border rounded-md overflow-hidden">
426
- <div className="flex items-center justify-between px-4 py-3">
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
- +&nbsp;Add two-step verification
432
- </button>
433
- </div>
434
- </div>
435
-
436
- {/* Active devices list (scaffold) */}
437
- <div className="border rounded-md overflow-hidden">
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>
441
- <div className="p-4 space-y-3">
442
- <div className="text-sm">
443
- <div className="flex items-center gap-2">
444
- <span className="inline-block h-5 w-5 rounded-sm bg-gray-900 dark:bg-white" />
445
- <span className="font-medium">X11</span>
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
458
- </div>
459
- </div>
460
- </div>
461
- </div>
462
-
463
- {/* Delete account */}
464
- <div className="border rounded-md overflow-hidden">
465
- <div className="flex items-center justify-between px-4 py-3">
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>
472
- </div>
473
- </div>
474
- </div>
475
- ) : (
476
- <div className="space-y-5 md:space-y-6">
477
- {/* Preferences */}
478
- <div>
479
- <h3 className="text-sm font-medium mb-3 text-gray-900 dark:text-gray-100">
480
- Preferences
481
- </h3>
482
- <div className="space-y-3 text-sm">
483
- <div className="flex items-center justify-between">
484
- <span className="text-gray-700 dark:text-gray-300">
485
- Locale
486
- </span>
487
- <span className="text-gray-500 dark:text-gray-400">Auto</span>
488
- </div>
489
- <div className="flex items-center justify-between">
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>
496
- </div>
497
- </div>
498
- </div>
499
- </div>
500
- )}
501
- </div>
502
-
503
- {/* <div className="absolute bottom-4 text-xs text-gray-500 flex items-center">
504
- Secured by
505
- <span className="ml-1 font-medium flex items-center">
506
- <svg
507
- width="14"
508
- height="14"
509
- viewBox="0 0 16 16"
510
- fill="none"
511
- xmlns="http://www.w3.org/2000/svg"
512
- className="mr-1"
513
- >
514
- <path d="M8 0L14.9282 4V12L8 16L1.07179 12V4L8 0Z" fill="#6C47FF" />
515
- </svg>
516
- Authdog
517
- </span>
518
- </div> */}
519
- </div>
520
- );
521
- };
@@ -1,167 +0,0 @@
1
- "use client";
2
-
3
- import type React from "react";
4
-
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";
20
-
21
- export const LoginForm = () => {
22
- const [email, setEmail] = useState("");
23
- const [password, setPassword] = useState("");
24
- const [error, setError] = useState("");
25
- const [isLoading, setIsLoading] = useState(false);
26
-
27
- const handleEmailLogin = async (e: React.FormEvent) => {
28
- e.preventDefault();
29
- setIsLoading(true);
30
- setError("");
31
-
32
- try {
33
- await new Promise((resolve) => setTimeout(resolve, 1000));
34
- } catch (err) {
35
- setError("Invalid email or password");
36
- } finally {
37
- setIsLoading(false);
38
- }
39
- };
40
-
41
- const handleOAuthLogin = async (provider: string) => {
42
- setIsLoading(true);
43
- setError("");
44
-
45
- try {
46
- await new Promise((resolve) => setTimeout(resolve, 1000));
47
- } catch (err) {
48
- setError(`Failed to login with ${provider}`);
49
- } finally {
50
- setIsLoading(false);
51
- }
52
- };
53
-
54
- return (
55
- <div className="flex min-h-screen items-center justify-center bg-gray-50 px-4 py-12 sm:px-6 lg:px-8">
56
- <Card className="w-full max-w-md">
57
- <CardHeader className="space-y-1">
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>
64
- </CardHeader>
65
- <CardContent className="space-y-4">
66
- {error && (
67
- <Alert variant="destructive">
68
- <AlertCircle className="h-4 w-4" />
69
- <AlertDescription>{error}</AlertDescription>
70
- </Alert>
71
- )}
72
-
73
- <div className="space-y-2">
74
- <Button
75
- variant="outline"
76
- className="w-full"
77
- onClick={() => handleOAuthLogin("google")}
78
- disabled={isLoading}
79
- >
80
- <svg className="mr-2 h-4 w-4" viewBox="0 0 24 24">
81
- <path
82
- 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"
83
- fill="#4285F4"
84
- />
85
- <path
86
- 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"
87
- fill="#34A853"
88
- />
89
- <path
90
- 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"
91
- fill="#FBBC05"
92
- />
93
- <path
94
- 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"
95
- fill="#EA4335"
96
- />
97
- <path d="M1 1h22v22H1z" fill="none" />
98
- </svg>
99
- Continue with Google
100
- </Button>
101
-
102
- <Button
103
- variant="outline"
104
- className="w-full"
105
- onClick={() => handleOAuthLogin("github")}
106
- disabled={isLoading}
107
- >
108
- <Github className="mr-2 h-4 w-4" />
109
- Continue with GitHub
110
- </Button>
111
- </div>
112
-
113
- <div className="flex items-center">
114
- <Separator className="flex-1" />
115
- <span className="mx-2 text-xs text-muted-foreground">OR</span>
116
- <Separator className="flex-1" />
117
- </div>
118
-
119
- <form onSubmit={handleEmailLogin} className="space-y-4">
120
- <div className="space-y-2">
121
- <Label htmlFor="email">Email</Label>
122
- <Input
123
- id="email"
124
- type="email"
125
- placeholder="m@example.com"
126
- value={email}
127
- onChange={(e) => setEmail(e.target.value)}
128
- required
129
- disabled={isLoading}
130
- />
131
- </div>
132
- <div className="space-y-2">
133
- <div className="flex items-center justify-between">
134
- <Label htmlFor="password">Password</Label>
135
- <a
136
- href="/forgot-password"
137
- className="text-xs text-primary hover:underline"
138
- >
139
- Forgot password?
140
- </a>
141
- </div>
142
- <Input
143
- id="password"
144
- type="password"
145
- value={password}
146
- onChange={(e) => setPassword(e.target.value)}
147
- required
148
- disabled={isLoading}
149
- />
150
- </div>
151
- <Button type="submit" className="w-full" disabled={isLoading}>
152
- {isLoading ? "Loading..." : "Sign in with Email"}
153
- </Button>
154
- </form>
155
- </CardContent>
156
- <CardFooter className="flex justify-center">
157
- <p className="text-xs text-muted-foreground">
158
- Don't have an account?{" "}
159
- <a href="/signup" className="text-primary hover:underline">
160
- Sign up
161
- </a>
162
- </p>
163
- </CardFooter>
164
- </Card>
165
- </div>
166
- );
167
- };