@authdog/react-elements 0.0.49 → 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.
- package/dist/components/ui/alert.d.mts +12 -0
- package/dist/components/ui/alert.d.ts +12 -0
- package/dist/components/ui/avatar.d.mts +8 -0
- package/dist/components/ui/avatar.d.ts +8 -0
- package/dist/components/ui/badge.d.mts +12 -0
- package/dist/components/ui/badge.d.ts +12 -0
- package/dist/components/ui/button.d.mts +14 -0
- package/dist/components/ui/button.d.ts +14 -0
- package/dist/components/ui/button.js.map +1 -1
- package/dist/components/ui/button.mjs.map +1 -1
- package/dist/components/ui/card.d.mts +11 -0
- package/dist/components/ui/card.d.ts +11 -0
- package/dist/components/ui/dropdown-menu.d.mts +27 -0
- package/dist/components/ui/dropdown-menu.d.ts +27 -0
- package/dist/components/ui/input.d.mts +5 -0
- package/dist/components/ui/input.d.ts +5 -0
- package/dist/components/ui/label.d.mts +6 -0
- package/dist/components/ui/label.d.ts +6 -0
- package/dist/components/ui/separator.d.mts +6 -0
- package/dist/components/ui/separator.d.ts +6 -0
- package/dist/components/ui/sheet.d.mts +15 -0
- package/dist/components/ui/sheet.d.ts +15 -0
- package/dist/components/ui/theme-toggle.d.mts +5 -0
- package/dist/components/ui/theme-toggle.d.ts +5 -0
- package/dist/components/ui/theme-toggle.js.map +1 -1
- package/dist/components/ui/theme-toggle.mjs.map +1 -1
- package/dist/index.d.mts +12 -21
- package/dist/index.d.ts +12 -21
- 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/lib/utils.d.mts +5 -0
- package/dist/lib/utils.d.ts +5 -0
- package/dist/styles.css +1 -4
- package/package.json +40 -25
- package/.eslintrc.js +0 -9
- package/.storybook/main.ts +0 -21
- package/.storybook/preview.ts +0 -17
- package/.storybook/vitest.setup.ts +0 -7
- package/.turbo/turbo-build.log +0 -77
- package/CHANGELOG.md +0 -286
- package/components.json +0 -20
- package/postcss.config.mjs +0 -11
- package/src/components/core/client-only.tsx +0 -15
- package/src/components/core/navbar.tsx +0 -307
- package/src/components/core/placeholder-alert.tsx +0 -23
- package/src/components/core/user-dropdown.tsx +0 -160
- package/src/components/core/user-profile.tsx +0 -521
- package/src/components/flow/login.tsx +0 -167
- package/src/components/flow/totp-validator.tsx +0 -252
- package/src/components/icons.tsx +0 -30
- package/src/components/ui/alert.tsx +0 -66
- package/src/components/ui/avatar.tsx +0 -53
- package/src/components/ui/badge.tsx +0 -46
- package/src/components/ui/button.tsx +0 -56
- package/src/components/ui/card.tsx +0 -92
- package/src/components/ui/dropdown-menu.tsx +0 -265
- package/src/components/ui/input.tsx +0 -21
- package/src/components/ui/label.tsx +0 -24
- package/src/components/ui/separator.tsx +0 -28
- package/src/components/ui/sheet.tsx +0 -142
- package/src/components/ui/theme-toggle.tsx +0 -56
- package/src/global.css +0 -81
- package/src/index.ts +0 -8
- package/src/lib/utils.ts +0 -6
- package/src/stories/core/Navbar.stories.tsx +0 -51
- package/src/stories/core/PlaceholderAlert.stories.tsx +0 -23
- package/src/stories/core/UserDropdown.stories.tsx +0 -56
- package/src/stories/core/UserProfile.stories.tsx +0 -47
- package/src/stories/flow/LoginForm.stories.tsx +0 -20
- package/src/stories/flow/TotpValidator.stories.tsx +0 -23
- package/src/stories/showcase/Landing.stories.tsx +0 -376
- package/src/stories/ui/Button.stories.tsx +0 -45
- package/src/types.ts +0 -0
- package/tailwind.config.ts +0 -82
- package/tsconfig.json +0 -11
- package/tsup.config.ts +0 -31
- package/vitest.config.ts +0 -39
- package/vitest.shims.d.ts +0 -1
- 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
|
-
+ 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
|
-
+ 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
|
-
};
|