@simple-auth-kit/cli 1.3.2 → 1.4.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/package.json +1 -1
- package/registry/admin-apps/nextjs/shared/.husky/commit-msg +1 -0
- package/registry/admin-apps/nextjs/shared/.husky/pre-commit +1 -0
- package/registry/admin-apps/nextjs/shared/.lintstagedrc.json +4 -0
- package/registry/admin-apps/nextjs/shared/.prettierrc.json +3 -0
- package/registry/admin-apps/nextjs/shared/commitlint.config.js +5 -0
- package/registry/admin-apps/nextjs/shared/eslint.config.js +28 -0
- package/registry/admin-apps/nextjs/variants/base/package.json +11 -2
- package/registry/admin-apps/nextjs/variants/base/src/app/(console)/audit-log/page.tsx +72 -16
- package/registry/admin-apps/nextjs/variants/base/src/app/(console)/customers/[id]/edit/page.tsx +107 -26
- package/registry/admin-apps/nextjs/variants/base/src/app/(console)/customers/[id]/page.tsx +131 -29
- package/registry/admin-apps/nextjs/variants/base/src/app/(console)/languages/[id]/edit/page.tsx +111 -25
- package/registry/admin-apps/nextjs/variants/base/src/app/(console)/languages/[id]/page.tsx +97 -22
- package/registry/admin-apps/nextjs/variants/base/src/app/(console)/permissions/[id]/edit/page.tsx +129 -46
- package/registry/admin-apps/nextjs/variants/base/src/app/(console)/permissions/[id]/page.tsx +64 -14
- package/registry/admin-apps/nextjs/variants/base/src/app/(console)/roles/[id]/page.tsx +91 -22
- package/registry/admin-apps/nextjs/variants/base/src/app/(console)/users/[id]/edit/page.tsx +94 -23
- package/registry/admin-apps/nextjs/variants/workspaces/package.json +11 -2
- package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/account/page.tsx +179 -41
- package/registry/admin-apps/react/shared/.husky/commit-msg +1 -0
- package/registry/admin-apps/react/shared/.husky/pre-commit +1 -0
- package/registry/admin-apps/react/shared/.lintstagedrc.json +4 -0
- package/registry/admin-apps/react/shared/.prettierrc.json +3 -0
- package/registry/admin-apps/react/shared/commitlint.config.js +3 -0
- package/registry/admin-apps/react/shared/eslint.config.js +38 -0
- package/registry/admin-apps/react/variants/base/package.json +15 -2
- package/registry/admin-apps/react/variants/base/src/pages/CountryDetailPage.tsx +76 -19
- package/registry/admin-apps/react/variants/base/src/pages/CustomerDetailPage.tsx +110 -26
- package/registry/admin-apps/react/variants/base/src/pages/EditCountryPage.tsx +115 -29
- package/registry/admin-apps/react/variants/base/src/pages/EditCustomerPage.tsx +123 -31
- package/registry/admin-apps/react/variants/base/src/pages/EditLanguagePage.tsx +100 -24
- package/registry/admin-apps/react/variants/base/src/pages/EditRolePage.tsx +99 -26
- package/registry/admin-apps/react/variants/base/src/pages/EditUserPage.tsx +126 -31
- package/registry/admin-apps/react/variants/base/src/pages/LanguageDetailPage.tsx +75 -19
- package/registry/admin-apps/react/variants/base/src/pages/PermissionDetailPage.tsx +58 -13
- package/registry/admin-apps/react/variants/base/src/pages/PermissionsPage.tsx +132 -29
- package/registry/admin-apps/react/variants/base/src/pages/RoleDetailPage.tsx +70 -17
- package/registry/admin-apps/react/variants/base/src/pages/RolesPage.tsx +277 -59
- package/registry/admin-apps/react/variants/workspaces/package.json +15 -2
- package/registry/combos/express-drizzle/eslint.config.js +18 -0
- package/registry/combos/express-drizzle/package.json +5 -1
- package/registry/combos/express-drizzle/shared/src/request-context.ts +3 -0
- package/registry/combos/express-prisma/eslint.config.js +18 -0
- package/registry/combos/express-prisma/package.json +5 -1
- package/registry/combos/express-prisma/shared/src/request-context.ts +3 -0
- package/registry/combos/nestjs-drizzle/eslint.config.js +18 -0
- package/registry/combos/nestjs-drizzle/package.json +5 -1
- package/registry/combos/nestjs-drizzle/shared/src/auth.controller.ts +138 -33
- package/registry/combos/nestjs-drizzle/shared/src/route-tiers.ts +71 -18
- package/registry/combos/nestjs-prisma/eslint.config.js +18 -0
- package/registry/combos/nestjs-prisma/package.json +5 -1
- package/registry/combos/nestjs-prisma/shared/src/auth.controller.ts +148 -35
- package/registry/combos/nestjs-prisma/shared/src/route-tiers.ts +72 -18
- package/registry/mobile-apps/bare-rn/shared/.husky/commit-msg +1 -0
- package/registry/mobile-apps/bare-rn/shared/.husky/pre-commit +1 -0
- package/registry/mobile-apps/bare-rn/shared/.lintstagedrc.json +4 -0
- package/registry/mobile-apps/bare-rn/shared/.prettierrc.json +5 -0
- package/registry/mobile-apps/bare-rn/shared/commitlint.config.js +3 -0
- package/registry/mobile-apps/bare-rn/shared/eslint.config.js +19 -0
- package/registry/mobile-apps/bare-rn/variants/base/package.json +17 -12
- package/registry/mobile-apps/bare-rn/variants/workspaces/package.json +17 -12
- package/registry/mobile-apps/expo/shared/.husky/commit-msg +1 -0
- package/registry/mobile-apps/expo/shared/.husky/pre-commit +1 -0
- package/registry/mobile-apps/expo/shared/.lintstagedrc.json +4 -0
- package/registry/mobile-apps/expo/shared/.prettierrc.json +5 -0
- package/registry/mobile-apps/expo/shared/commitlint.config.js +3 -0
- package/registry/mobile-apps/expo/shared/eslint.config.js +9 -0
- package/registry/mobile-apps/expo/shared/src/screens/SessionsScreen.tsx +6 -17
- package/registry/mobile-apps/expo/shared/src/store/sessionsStore.ts +35 -0
- package/registry/mobile-apps/expo/variants/base/package.json +12 -3
- package/registry/mobile-apps/expo/variants/workspaces/package.json +12 -3
- package/registry/mobile-apps/expo/variants/workspaces/src/screens/WorkspaceSelectScreen.tsx +1 -1
- package/registry.json +13 -36
- package/registry/mobile-apps/bare-rn/shared/.eslintrc.js +0 -4
- package/registry/mobile-apps/bare-rn/shared/.prettierrc.js +0 -5
|
@@ -3,17 +3,33 @@
|
|
|
3
3
|
import { observer } from "mobx-react-lite";
|
|
4
4
|
import { QRCodeSVG } from "qrcode.react";
|
|
5
5
|
import { useEffect, useState } from "react";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
AuthApiError,
|
|
8
|
+
type SessionSummary,
|
|
9
|
+
} from "@simple-auth-kit/auth-client";
|
|
7
10
|
import { toast } from "sonner";
|
|
8
11
|
import { ChangePasswordDialog } from "@/components/change-password-dialog";
|
|
9
12
|
import { PhotoUpload } from "@/components/photo-upload";
|
|
10
13
|
import { Alert } from "@/components/ui/alert";
|
|
11
14
|
import { Badge } from "@/components/ui/badge";
|
|
12
15
|
import { Button } from "@/components/ui/button";
|
|
13
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
Card,
|
|
18
|
+
CardContent,
|
|
19
|
+
CardDescription,
|
|
20
|
+
CardHeader,
|
|
21
|
+
CardTitle,
|
|
22
|
+
} from "@/components/ui/card";
|
|
14
23
|
import { Input } from "@/components/ui/input";
|
|
15
24
|
import { Label } from "@/components/ui/label";
|
|
16
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
Table,
|
|
27
|
+
TableBody,
|
|
28
|
+
TableCell,
|
|
29
|
+
TableHead,
|
|
30
|
+
TableHeader,
|
|
31
|
+
TableRow,
|
|
32
|
+
} from "@/components/ui/table";
|
|
17
33
|
import { authClient } from "@/lib/auth-client";
|
|
18
34
|
import { useAuthStore, useWorkspaceStore } from "@/lib/stores/store-context";
|
|
19
35
|
|
|
@@ -32,9 +48,21 @@ function initialsOf(label: string): string {
|
|
|
32
48
|
);
|
|
33
49
|
}
|
|
34
50
|
|
|
35
|
-
type ProfileForm = {
|
|
51
|
+
type ProfileForm = {
|
|
52
|
+
firstName: string;
|
|
53
|
+
lastName: string;
|
|
54
|
+
displayName: string;
|
|
55
|
+
phone: string;
|
|
56
|
+
username: string;
|
|
57
|
+
};
|
|
36
58
|
|
|
37
|
-
function toForm(user: {
|
|
59
|
+
function toForm(user: {
|
|
60
|
+
firstName: string | null;
|
|
61
|
+
lastName: string | null;
|
|
62
|
+
displayName: string | null;
|
|
63
|
+
phone: string | null;
|
|
64
|
+
username: string | null;
|
|
65
|
+
}): ProfileForm {
|
|
38
66
|
return {
|
|
39
67
|
firstName: user.firstName ?? "",
|
|
40
68
|
lastName: user.lastName ?? "",
|
|
@@ -50,13 +78,18 @@ export default observer(function AccountPage() {
|
|
|
50
78
|
const user = store.currentUser;
|
|
51
79
|
const workspaceName = workspaces.activeWorkspace?.name ?? null;
|
|
52
80
|
|
|
53
|
-
const [form, setForm] = useState<ProfileForm | null>(
|
|
81
|
+
const [form, setForm] = useState<ProfileForm | null>(
|
|
82
|
+
user ? toForm(user) : null,
|
|
83
|
+
);
|
|
54
84
|
const [profileSaving, setProfileSaving] = useState(false);
|
|
55
85
|
const [changePasswordOpen, setChangePasswordOpen] = useState(false);
|
|
56
86
|
|
|
57
87
|
const [sessions, setSessions] = useState<SessionSummary[]>([]);
|
|
58
88
|
|
|
59
|
-
const [enrollment, setEnrollment] = useState<{
|
|
89
|
+
const [enrollment, setEnrollment] = useState<{
|
|
90
|
+
secret: string;
|
|
91
|
+
provisioningUri: string;
|
|
92
|
+
} | null>(null);
|
|
60
93
|
const [enrollCode, setEnrollCode] = useState("");
|
|
61
94
|
const [backupCodes, setBackupCodes] = useState<string[] | null>(null);
|
|
62
95
|
const [disableCode, setDisableCode] = useState("");
|
|
@@ -67,7 +100,9 @@ export default observer(function AccountPage() {
|
|
|
67
100
|
store
|
|
68
101
|
.listSessions()
|
|
69
102
|
.then(setSessions)
|
|
70
|
-
.catch((err) =>
|
|
103
|
+
.catch((err) =>
|
|
104
|
+
toast.error(apiErrorMessage(err, "Couldn't load active sessions.")),
|
|
105
|
+
);
|
|
71
106
|
}, [store]);
|
|
72
107
|
|
|
73
108
|
async function handleEnroll() {
|
|
@@ -76,7 +111,9 @@ export default observer(function AccountPage() {
|
|
|
76
111
|
const result = await store.enrollTwoFactor();
|
|
77
112
|
setEnrollment(result);
|
|
78
113
|
} catch (err) {
|
|
79
|
-
toast.error(
|
|
114
|
+
toast.error(
|
|
115
|
+
apiErrorMessage(err, "Couldn't start 2FA enrollment. Try again."),
|
|
116
|
+
);
|
|
80
117
|
} finally {
|
|
81
118
|
setTwoFactorBusy(false);
|
|
82
119
|
}
|
|
@@ -91,7 +128,9 @@ export default observer(function AccountPage() {
|
|
|
91
128
|
setEnrollCode("");
|
|
92
129
|
toast.success("Two-factor authentication enabled.");
|
|
93
130
|
} catch (err) {
|
|
94
|
-
toast.error(
|
|
131
|
+
toast.error(
|
|
132
|
+
apiErrorMessage(err, "Couldn't confirm this code. Try again."),
|
|
133
|
+
);
|
|
95
134
|
} finally {
|
|
96
135
|
setTwoFactorBusy(false);
|
|
97
136
|
}
|
|
@@ -115,9 +154,13 @@ export default observer(function AccountPage() {
|
|
|
115
154
|
setLogoutAllBusy(true);
|
|
116
155
|
try {
|
|
117
156
|
await store.logoutAll();
|
|
118
|
-
toast.success(
|
|
157
|
+
toast.success(
|
|
158
|
+
"Signed out of all sessions. You'll need to sign in again.",
|
|
159
|
+
);
|
|
119
160
|
} catch (err) {
|
|
120
|
-
toast.error(
|
|
161
|
+
toast.error(
|
|
162
|
+
apiErrorMessage(err, "Couldn't sign out of all sessions. Try again."),
|
|
163
|
+
);
|
|
121
164
|
} finally {
|
|
122
165
|
setLogoutAllBusy(false);
|
|
123
166
|
}
|
|
@@ -130,7 +173,9 @@ export default observer(function AccountPage() {
|
|
|
130
173
|
await store.refreshCurrentUser();
|
|
131
174
|
toast.success(photo ? "Photo updated." : "Photo removed.");
|
|
132
175
|
} catch (err) {
|
|
133
|
-
toast.error(
|
|
176
|
+
toast.error(
|
|
177
|
+
apiErrorMessage(err, "Couldn't update the photo. Try again."),
|
|
178
|
+
);
|
|
134
179
|
}
|
|
135
180
|
}
|
|
136
181
|
|
|
@@ -150,7 +195,9 @@ export default observer(function AccountPage() {
|
|
|
150
195
|
await store.refreshCurrentUser();
|
|
151
196
|
toast.success("Profile saved.");
|
|
152
197
|
} catch (err) {
|
|
153
|
-
toast.error(
|
|
198
|
+
toast.error(
|
|
199
|
+
apiErrorMessage(err, "Couldn't save this profile. Try again."),
|
|
200
|
+
);
|
|
154
201
|
} finally {
|
|
155
202
|
setProfileSaving(false);
|
|
156
203
|
}
|
|
@@ -163,37 +210,74 @@ export default observer(function AccountPage() {
|
|
|
163
210
|
<Card>
|
|
164
211
|
<CardHeader>
|
|
165
212
|
<CardTitle>Profile</CardTitle>
|
|
166
|
-
<CardDescription>
|
|
213
|
+
<CardDescription>
|
|
214
|
+
Your own info — no admin permission needed to change any of this.
|
|
215
|
+
</CardDescription>
|
|
167
216
|
</CardHeader>
|
|
168
217
|
<CardContent className="flex flex-col gap-4">
|
|
169
|
-
<PhotoUpload
|
|
218
|
+
<PhotoUpload
|
|
219
|
+
photo={user.photo}
|
|
220
|
+
fallback={initialsOf(user.displayName || user.email)}
|
|
221
|
+
onChange={handlePhotoChange}
|
|
222
|
+
/>
|
|
170
223
|
|
|
171
|
-
<form
|
|
224
|
+
<form
|
|
225
|
+
onSubmit={handleProfileSave}
|
|
226
|
+
className="grid grid-cols-1 gap-4 sm:grid-cols-2"
|
|
227
|
+
>
|
|
172
228
|
<div className="flex flex-col gap-1.5">
|
|
173
229
|
<Label htmlFor="firstName">First name</Label>
|
|
174
|
-
<Input
|
|
230
|
+
<Input
|
|
231
|
+
id="firstName"
|
|
232
|
+
value={form.firstName}
|
|
233
|
+
onChange={(e) =>
|
|
234
|
+
setForm({ ...form, firstName: e.target.value })
|
|
235
|
+
}
|
|
236
|
+
/>
|
|
175
237
|
</div>
|
|
176
238
|
<div className="flex flex-col gap-1.5">
|
|
177
239
|
<Label htmlFor="lastName">Last name</Label>
|
|
178
|
-
<Input
|
|
240
|
+
<Input
|
|
241
|
+
id="lastName"
|
|
242
|
+
value={form.lastName}
|
|
243
|
+
onChange={(e) => setForm({ ...form, lastName: e.target.value })}
|
|
244
|
+
/>
|
|
179
245
|
</div>
|
|
180
246
|
<div className="flex flex-col gap-1.5">
|
|
181
247
|
<Label htmlFor="displayName">Display name</Label>
|
|
182
|
-
<Input
|
|
248
|
+
<Input
|
|
249
|
+
id="displayName"
|
|
250
|
+
value={form.displayName}
|
|
251
|
+
onChange={(e) =>
|
|
252
|
+
setForm({ ...form, displayName: e.target.value })
|
|
253
|
+
}
|
|
254
|
+
/>
|
|
183
255
|
</div>
|
|
184
256
|
<div className="flex flex-col gap-1.5">
|
|
185
257
|
<Label htmlFor="username">Username</Label>
|
|
186
|
-
<Input
|
|
258
|
+
<Input
|
|
259
|
+
id="username"
|
|
260
|
+
value={form.username}
|
|
261
|
+
onChange={(e) => setForm({ ...form, username: e.target.value })}
|
|
262
|
+
/>
|
|
187
263
|
</div>
|
|
188
264
|
<div className="flex flex-col gap-1.5">
|
|
189
265
|
<Label htmlFor="phone">Phone</Label>
|
|
190
|
-
<Input
|
|
266
|
+
<Input
|
|
267
|
+
id="phone"
|
|
268
|
+
value={form.phone}
|
|
269
|
+
onChange={(e) => setForm({ ...form, phone: e.target.value })}
|
|
270
|
+
/>
|
|
191
271
|
</div>
|
|
192
272
|
<div className="flex items-end gap-2">
|
|
193
273
|
<Button type="submit" disabled={profileSaving}>
|
|
194
274
|
{profileSaving ? "Saving…" : "Save changes"}
|
|
195
275
|
</Button>
|
|
196
|
-
<Button
|
|
276
|
+
<Button
|
|
277
|
+
type="button"
|
|
278
|
+
variant="outline"
|
|
279
|
+
onClick={() => setChangePasswordOpen(true)}
|
|
280
|
+
>
|
|
197
281
|
Change password
|
|
198
282
|
</Button>
|
|
199
283
|
</div>
|
|
@@ -243,7 +327,9 @@ export default observer(function AccountPage() {
|
|
|
243
327
|
</div>
|
|
244
328
|
<div>
|
|
245
329
|
<div className="text-xs text-muted-foreground">Two-factor</div>
|
|
246
|
-
<Badge variant={user.twoFactorEnabled ? "success" : "outline"}>
|
|
330
|
+
<Badge variant={user.twoFactorEnabled ? "success" : "outline"}>
|
|
331
|
+
{user.twoFactorEnabled ? "Enabled" : "Disabled"}
|
|
332
|
+
</Badge>
|
|
247
333
|
</div>
|
|
248
334
|
</CardContent>
|
|
249
335
|
</Card>
|
|
@@ -251,7 +337,10 @@ export default observer(function AccountPage() {
|
|
|
251
337
|
<Card>
|
|
252
338
|
<CardHeader>
|
|
253
339
|
<CardTitle>Active sessions</CardTitle>
|
|
254
|
-
<CardDescription>
|
|
340
|
+
<CardDescription>
|
|
341
|
+
Only bulk sign-out is available — the backend doesn't yet
|
|
342
|
+
expose per-session revocation.
|
|
343
|
+
</CardDescription>
|
|
255
344
|
</CardHeader>
|
|
256
345
|
<CardContent className="flex flex-col gap-4">
|
|
257
346
|
<Table>
|
|
@@ -274,21 +363,35 @@ export default observer(function AccountPage() {
|
|
|
274
363
|
</Badge>
|
|
275
364
|
)}
|
|
276
365
|
</TableCell>
|
|
277
|
-
<TableCell className="text-xs text-muted-foreground">
|
|
278
|
-
|
|
279
|
-
|
|
366
|
+
<TableCell className="text-xs text-muted-foreground">
|
|
367
|
+
{new Date(session.createdAt).toLocaleString()}
|
|
368
|
+
</TableCell>
|
|
369
|
+
<TableCell className="max-w-xs truncate text-xs text-muted-foreground">
|
|
370
|
+
{session.userAgent ?? "—"}
|
|
371
|
+
</TableCell>
|
|
372
|
+
<TableCell className="text-xs text-muted-foreground">
|
|
373
|
+
{session.ip ?? "—"}
|
|
374
|
+
</TableCell>
|
|
280
375
|
</TableRow>
|
|
281
376
|
))}
|
|
282
377
|
{sessions.length === 0 && (
|
|
283
378
|
<TableRow>
|
|
284
|
-
<TableCell
|
|
379
|
+
<TableCell
|
|
380
|
+
colSpan={4}
|
|
381
|
+
className="text-center text-sm text-muted-foreground"
|
|
382
|
+
>
|
|
285
383
|
No active sessions.
|
|
286
384
|
</TableCell>
|
|
287
385
|
</TableRow>
|
|
288
386
|
)}
|
|
289
387
|
</TableBody>
|
|
290
388
|
</Table>
|
|
291
|
-
<Button
|
|
389
|
+
<Button
|
|
390
|
+
variant="destructive"
|
|
391
|
+
className="w-fit"
|
|
392
|
+
disabled={logoutAllBusy}
|
|
393
|
+
onClick={handleLogoutAll}
|
|
394
|
+
>
|
|
292
395
|
{logoutAllBusy ? "Signing out…" : "Sign out of all sessions"}
|
|
293
396
|
</Button>
|
|
294
397
|
</CardContent>
|
|
@@ -301,7 +404,9 @@ export default observer(function AccountPage() {
|
|
|
301
404
|
<CardContent className="flex flex-col gap-4">
|
|
302
405
|
{backupCodes && (
|
|
303
406
|
<Alert variant="success">
|
|
304
|
-
<p className="mb-2 font-medium">
|
|
407
|
+
<p className="mb-2 font-medium">
|
|
408
|
+
Save these backup codes — they won't be shown again:
|
|
409
|
+
</p>
|
|
305
410
|
<ul className="grid grid-cols-2 gap-1 font-mono text-xs sm:grid-cols-5">
|
|
306
411
|
{backupCodes.map((code) => (
|
|
307
412
|
<li key={code}>{code}</li>
|
|
@@ -312,30 +417,54 @@ export default observer(function AccountPage() {
|
|
|
312
417
|
|
|
313
418
|
{user.twoFactorEnabled ? (
|
|
314
419
|
<div className="flex flex-col gap-3">
|
|
315
|
-
<p className="text-sm text-muted-foreground">
|
|
420
|
+
<p className="text-sm text-muted-foreground">
|
|
421
|
+
Two-factor authentication is enabled on your account.
|
|
422
|
+
</p>
|
|
316
423
|
<div className="flex items-end gap-2">
|
|
317
424
|
<div className="flex flex-col gap-1.5">
|
|
318
425
|
<Label htmlFor="disableCode">Enter a code to disable</Label>
|
|
319
|
-
<Input
|
|
426
|
+
<Input
|
|
427
|
+
id="disableCode"
|
|
428
|
+
value={disableCode}
|
|
429
|
+
onChange={(e) => setDisableCode(e.target.value)}
|
|
430
|
+
placeholder="123456"
|
|
431
|
+
/>
|
|
320
432
|
</div>
|
|
321
|
-
<Button
|
|
433
|
+
<Button
|
|
434
|
+
variant="destructive"
|
|
435
|
+
disabled={twoFactorBusy || !disableCode}
|
|
436
|
+
onClick={handleDisable}
|
|
437
|
+
>
|
|
322
438
|
{twoFactorBusy ? "Disabling…" : "Disable 2FA"}
|
|
323
439
|
</Button>
|
|
324
440
|
</div>
|
|
325
441
|
</div>
|
|
326
442
|
) : enrollment ? (
|
|
327
443
|
<div className="flex flex-col gap-3">
|
|
328
|
-
<p className="text-sm text-muted-foreground">
|
|
444
|
+
<p className="text-sm text-muted-foreground">
|
|
445
|
+
Scan this QR code with your authenticator app, then enter the
|
|
446
|
+
6-digit code it shows.
|
|
447
|
+
</p>
|
|
329
448
|
<div className="w-fit rounded-md border border-border bg-white p-3">
|
|
330
449
|
<QRCodeSVG value={enrollment.provisioningUri} size={176} />
|
|
331
450
|
</div>
|
|
332
|
-
<p className="font-mono text-xs text-muted-foreground">
|
|
451
|
+
<p className="font-mono text-xs text-muted-foreground">
|
|
452
|
+
Secret: {enrollment.secret}
|
|
453
|
+
</p>
|
|
333
454
|
<div className="flex items-end gap-2">
|
|
334
455
|
<div className="flex flex-col gap-1.5">
|
|
335
456
|
<Label htmlFor="enrollCode">Confirmation code</Label>
|
|
336
|
-
<Input
|
|
457
|
+
<Input
|
|
458
|
+
id="enrollCode"
|
|
459
|
+
value={enrollCode}
|
|
460
|
+
onChange={(e) => setEnrollCode(e.target.value)}
|
|
461
|
+
placeholder="123456"
|
|
462
|
+
/>
|
|
337
463
|
</div>
|
|
338
|
-
<Button
|
|
464
|
+
<Button
|
|
465
|
+
disabled={twoFactorBusy || !enrollCode}
|
|
466
|
+
onClick={handleConfirm}
|
|
467
|
+
>
|
|
339
468
|
{twoFactorBusy ? "Confirming…" : "Confirm"}
|
|
340
469
|
</Button>
|
|
341
470
|
<Button variant="ghost" onClick={() => setEnrollment(null)}>
|
|
@@ -345,8 +474,14 @@ export default observer(function AccountPage() {
|
|
|
345
474
|
</div>
|
|
346
475
|
) : (
|
|
347
476
|
<div className="flex flex-col gap-3">
|
|
348
|
-
<p className="text-sm text-muted-foreground">
|
|
349
|
-
|
|
477
|
+
<p className="text-sm text-muted-foreground">
|
|
478
|
+
Two-factor authentication is not enabled on your account.
|
|
479
|
+
</p>
|
|
480
|
+
<Button
|
|
481
|
+
className="w-fit"
|
|
482
|
+
disabled={twoFactorBusy}
|
|
483
|
+
onClick={handleEnroll}
|
|
484
|
+
>
|
|
350
485
|
{twoFactorBusy ? "Starting…" : "Enable 2FA"}
|
|
351
486
|
</Button>
|
|
352
487
|
</div>
|
|
@@ -354,7 +489,10 @@ export default observer(function AccountPage() {
|
|
|
354
489
|
</CardContent>
|
|
355
490
|
</Card>
|
|
356
491
|
|
|
357
|
-
<ChangePasswordDialog
|
|
492
|
+
<ChangePasswordDialog
|
|
493
|
+
open={changePasswordOpen}
|
|
494
|
+
onOpenChange={setChangePasswordOpen}
|
|
495
|
+
/>
|
|
358
496
|
</div>
|
|
359
497
|
);
|
|
360
498
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npx --no -- commitlint --edit "$1"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npx lint-staged
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
import reactHooks from "eslint-plugin-react-hooks";
|
|
4
|
+
import reactRefresh from "eslint-plugin-react-refresh";
|
|
5
|
+
import tseslint from "typescript-eslint";
|
|
6
|
+
|
|
7
|
+
export default tseslint.config(
|
|
8
|
+
{
|
|
9
|
+
// src/components/ui/** is shadcn-generated (components.json), not hand-authored — same
|
|
10
|
+
// "vendored, don't lint" treatment this repo already gives it for Biome (the surviving
|
|
11
|
+
// biome-ignore-all comment on multi-selector.tsx).
|
|
12
|
+
ignores: ["dist/**", "node_modules/**", "src/components/ui/**"],
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
files: ["**/*.{ts,tsx}"],
|
|
16
|
+
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
|
17
|
+
languageOptions: {
|
|
18
|
+
ecmaVersion: 2022,
|
|
19
|
+
globals: globals.browser,
|
|
20
|
+
},
|
|
21
|
+
plugins: {
|
|
22
|
+
"react-hooks": reactHooks,
|
|
23
|
+
"react-refresh": reactRefresh,
|
|
24
|
+
},
|
|
25
|
+
rules: {
|
|
26
|
+
...reactHooks.configs.recommended.rules,
|
|
27
|
+
"react-refresh/only-export-components": [
|
|
28
|
+
"warn",
|
|
29
|
+
{ allowConstantExport: true },
|
|
30
|
+
],
|
|
31
|
+
// TanStack Table's useReactTable() is on React Compiler's own documented list of
|
|
32
|
+
// incompatible APIs (it returns functions that can't be safely memoized) — the compiler
|
|
33
|
+
// already handles this correctly by skipping memoization for it; there's no code change
|
|
34
|
+
// that "fixes" a structural library incompatibility.
|
|
35
|
+
"react-hooks/incompatible-library": "off",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
);
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"dev": "vite",
|
|
9
9
|
"build": "tsc --noEmit && vite build",
|
|
10
10
|
"preview": "vite preview",
|
|
11
|
-
"typecheck": "tsc --noEmit"
|
|
11
|
+
"typecheck": "tsc --noEmit",
|
|
12
|
+
"lint": "eslint .",
|
|
13
|
+
"prepare": "husky"
|
|
12
14
|
},
|
|
13
15
|
"dependencies": {
|
|
14
16
|
"@casl/ability": "^6.7.3",
|
|
@@ -48,14 +50,25 @@
|
|
|
48
50
|
"vaul": "^1.1.2"
|
|
49
51
|
},
|
|
50
52
|
"devDependencies": {
|
|
53
|
+
"@commitlint/cli": "^21.2.2",
|
|
54
|
+
"@commitlint/config-conventional": "^21.2.2",
|
|
55
|
+
"@eslint/js": "^9.39.5",
|
|
51
56
|
"@tailwindcss/postcss": "^4.3.3",
|
|
52
57
|
"@types/node": "^26.2.0",
|
|
53
58
|
"@types/react": "^19.2.7",
|
|
54
59
|
"@types/react-dom": "^19.2.4",
|
|
55
60
|
"@vitejs/plugin-react": "^5.0.4",
|
|
61
|
+
"eslint": "^9.39.5",
|
|
62
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
63
|
+
"eslint-plugin-react-refresh": "^0.5.6",
|
|
64
|
+
"globals": "^17.12.0",
|
|
65
|
+
"husky": "^9.1.7",
|
|
66
|
+
"lint-staged": "^17.5.1",
|
|
56
67
|
"postcss": "^8.5.6",
|
|
68
|
+
"prettier": "^3.9.6",
|
|
57
69
|
"tailwindcss": "^4.3.3",
|
|
58
|
-
"typescript": "^
|
|
70
|
+
"typescript": "^6.0.3",
|
|
71
|
+
"typescript-eslint": "^8.70.0",
|
|
59
72
|
"vite": "^7.1.14"
|
|
60
73
|
}
|
|
61
74
|
}
|