@simple-auth-kit/cli 1.3.1 → 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/README.md +6 -5
- 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/simple-auth-kit.ts +13 -10
- package/registry/mobile-apps/bare-rn/shared/.eslintrc.js +0 -4
- package/registry/mobile-apps/bare-rn/shared/.prettierrc.js +0 -5
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type FormEvent,
|
|
3
|
+
startTransition,
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useState,
|
|
7
|
+
} from "react";
|
|
2
8
|
import { useNavigate, useParams } from "react-router-dom";
|
|
3
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
RoleSummary,
|
|
11
|
+
UpdateUserInput,
|
|
12
|
+
UserSummary,
|
|
13
|
+
} from "@simple-auth-kit/auth-client";
|
|
4
14
|
import { AuthApiError } from "@simple-auth-kit/auth-client";
|
|
5
15
|
import { toast } from "sonner";
|
|
6
16
|
import { PERMISSIONS, useAbility } from "@/lib/ability";
|
|
@@ -11,13 +21,31 @@ import { FormErrorAlert } from "@/components/form-error-alert";
|
|
|
11
21
|
import { PhotoUpload } from "@/components/photo-upload";
|
|
12
22
|
import { RoleMultiSelect } from "@/components/role-multi-select";
|
|
13
23
|
import { Button } from "@/components/ui/button";
|
|
14
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
Card,
|
|
26
|
+
CardContent,
|
|
27
|
+
CardDescription,
|
|
28
|
+
CardFooter,
|
|
29
|
+
CardHeader,
|
|
30
|
+
CardTitle,
|
|
31
|
+
} from "@/components/ui/card";
|
|
15
32
|
import { Heading } from "@/components/ui/heading";
|
|
16
33
|
import { Input } from "@/components/ui/input";
|
|
17
34
|
import { Label } from "@/components/ui/label";
|
|
18
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
Select,
|
|
37
|
+
SelectContent,
|
|
38
|
+
SelectItem,
|
|
39
|
+
SelectTrigger,
|
|
40
|
+
SelectValue,
|
|
41
|
+
} from "@/components/ui/select";
|
|
19
42
|
import { Separator } from "@/components/ui/separator";
|
|
20
|
-
import {
|
|
43
|
+
import {
|
|
44
|
+
emptyUserFields,
|
|
45
|
+
GENDER_OPTIONS,
|
|
46
|
+
toDateInputValue,
|
|
47
|
+
type UserFormFields,
|
|
48
|
+
} from "./user-schema";
|
|
21
49
|
|
|
22
50
|
function apiErrorMessage(err: unknown, fallback: string): string {
|
|
23
51
|
return err instanceof AuthApiError ? err.message : fallback;
|
|
@@ -36,7 +64,8 @@ function toForm(user: UserSummary): UserFormFields {
|
|
|
36
64
|
};
|
|
37
65
|
}
|
|
38
66
|
|
|
39
|
-
const inputClassName =
|
|
67
|
+
const inputClassName =
|
|
68
|
+
"bg-background border-2 focus:border-purple-500 transition-colors";
|
|
40
69
|
|
|
41
70
|
export function EditUserPage() {
|
|
42
71
|
const { id } = useParams<{ id: string }>();
|
|
@@ -71,7 +100,7 @@ export function EditUserPage() {
|
|
|
71
100
|
}, [id]);
|
|
72
101
|
|
|
73
102
|
useEffect(() => {
|
|
74
|
-
void load();
|
|
103
|
+
startTransition(() => void load());
|
|
75
104
|
}, [load]);
|
|
76
105
|
|
|
77
106
|
useEffect(() => {
|
|
@@ -79,7 +108,9 @@ export function EditUserPage() {
|
|
|
79
108
|
authClient
|
|
80
109
|
.listRoles()
|
|
81
110
|
.then(setRoleCatalog)
|
|
82
|
-
.catch((err) =>
|
|
111
|
+
.catch((err) =>
|
|
112
|
+
toast.error(apiErrorMessage(err, "Couldn't load the role catalog.")),
|
|
113
|
+
);
|
|
83
114
|
}, [canReadRoles]);
|
|
84
115
|
|
|
85
116
|
if (!id) return null;
|
|
@@ -133,7 +164,9 @@ export function EditUserPage() {
|
|
|
133
164
|
</div>
|
|
134
165
|
<Separator />
|
|
135
166
|
|
|
136
|
-
{loading && !user &&
|
|
167
|
+
{loading && !user && (
|
|
168
|
+
<p className="text-sm text-muted-foreground">Loading…</p>
|
|
169
|
+
)}
|
|
137
170
|
|
|
138
171
|
{user && (
|
|
139
172
|
<Card>
|
|
@@ -144,12 +177,16 @@ export function EditUserPage() {
|
|
|
144
177
|
<Card className="w-full">
|
|
145
178
|
<CardHeader className="border-b bg-muted/50">
|
|
146
179
|
<CardTitle className="text-2xl">User Information</CardTitle>
|
|
147
|
-
<CardDescription className="text-base">
|
|
180
|
+
<CardDescription className="text-base">
|
|
181
|
+
Update user's basic information
|
|
182
|
+
</CardDescription>
|
|
148
183
|
</CardHeader>
|
|
149
184
|
<CardContent className="pt-6">
|
|
150
185
|
<div className="space-y-6">
|
|
151
186
|
<div className="space-y-4">
|
|
152
|
-
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
187
|
+
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
188
|
+
Personal Information
|
|
189
|
+
</h3>
|
|
153
190
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
154
191
|
<div className="flex flex-col gap-2">
|
|
155
192
|
<Label htmlFor="firstName">First Name</Label>
|
|
@@ -158,7 +195,9 @@ export function EditUserPage() {
|
|
|
158
195
|
disabled={saving}
|
|
159
196
|
placeholder="First Name"
|
|
160
197
|
value={form.firstName}
|
|
161
|
-
onChange={(e) =>
|
|
198
|
+
onChange={(e) =>
|
|
199
|
+
setForm({ ...form, firstName: e.target.value })
|
|
200
|
+
}
|
|
162
201
|
type="text"
|
|
163
202
|
className={inputClassName}
|
|
164
203
|
/>
|
|
@@ -170,7 +209,9 @@ export function EditUserPage() {
|
|
|
170
209
|
disabled={saving}
|
|
171
210
|
placeholder="Last Name"
|
|
172
211
|
value={form.lastName}
|
|
173
|
-
onChange={(e) =>
|
|
212
|
+
onChange={(e) =>
|
|
213
|
+
setForm({ ...form, lastName: e.target.value })
|
|
214
|
+
}
|
|
174
215
|
type="text"
|
|
175
216
|
className={inputClassName}
|
|
176
217
|
/>
|
|
@@ -182,7 +223,9 @@ export function EditUserPage() {
|
|
|
182
223
|
disabled={saving}
|
|
183
224
|
placeholder="Display Name"
|
|
184
225
|
value={form.displayName}
|
|
185
|
-
onChange={(e) =>
|
|
226
|
+
onChange={(e) =>
|
|
227
|
+
setForm({ ...form, displayName: e.target.value })
|
|
228
|
+
}
|
|
186
229
|
type="text"
|
|
187
230
|
className={inputClassName}
|
|
188
231
|
/>
|
|
@@ -190,14 +233,18 @@ export function EditUserPage() {
|
|
|
190
233
|
<div className="flex flex-col gap-2">
|
|
191
234
|
<Label htmlFor="username">
|
|
192
235
|
Username
|
|
193
|
-
<span className="text-xs text-destructive dark:text-destructive-foreground">
|
|
236
|
+
<span className="text-xs text-destructive dark:text-destructive-foreground">
|
|
237
|
+
(Must be unique)
|
|
238
|
+
</span>
|
|
194
239
|
</Label>
|
|
195
240
|
<Input
|
|
196
241
|
id="username"
|
|
197
242
|
disabled={saving}
|
|
198
243
|
placeholder="Username"
|
|
199
244
|
value={form.username}
|
|
200
|
-
onChange={(e) =>
|
|
245
|
+
onChange={(e) =>
|
|
246
|
+
setForm({ ...form, username: e.target.value })
|
|
247
|
+
}
|
|
201
248
|
className={inputClassName}
|
|
202
249
|
/>
|
|
203
250
|
</div>
|
|
@@ -205,19 +252,25 @@ export function EditUserPage() {
|
|
|
205
252
|
</div>
|
|
206
253
|
|
|
207
254
|
<div className="space-y-4">
|
|
208
|
-
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
255
|
+
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
256
|
+
Contact Information
|
|
257
|
+
</h3>
|
|
209
258
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
210
259
|
<div className="flex flex-col gap-2">
|
|
211
260
|
<Label htmlFor="phone">
|
|
212
261
|
Phone
|
|
213
|
-
<span className="text-xs text-muted-foreground">
|
|
262
|
+
<span className="text-xs text-muted-foreground">
|
|
263
|
+
(With country code)
|
|
264
|
+
</span>
|
|
214
265
|
</Label>
|
|
215
266
|
<Input
|
|
216
267
|
id="phone"
|
|
217
268
|
type="text"
|
|
218
269
|
disabled={saving}
|
|
219
270
|
value={form.phone}
|
|
220
|
-
onChange={(e) =>
|
|
271
|
+
onChange={(e) =>
|
|
272
|
+
setForm({ ...form, phone: e.target.value })
|
|
273
|
+
}
|
|
221
274
|
placeholder="+1234567890"
|
|
222
275
|
className={inputClassName}
|
|
223
276
|
/>
|
|
@@ -226,7 +279,9 @@ export function EditUserPage() {
|
|
|
226
279
|
</div>
|
|
227
280
|
|
|
228
281
|
<div className="space-y-4">
|
|
229
|
-
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
282
|
+
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
283
|
+
Additional Details
|
|
284
|
+
</h3>
|
|
230
285
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
231
286
|
<div className="flex flex-col gap-2 p-2">
|
|
232
287
|
<Label htmlFor="joinedDate">Joined Date</Label>
|
|
@@ -235,7 +290,9 @@ export function EditUserPage() {
|
|
|
235
290
|
type="date"
|
|
236
291
|
disabled={saving}
|
|
237
292
|
value={form.joinedDate}
|
|
238
|
-
onChange={(e) =>
|
|
293
|
+
onChange={(e) =>
|
|
294
|
+
setForm({ ...form, joinedDate: e.target.value })
|
|
295
|
+
}
|
|
239
296
|
className={inputClassName}
|
|
240
297
|
/>
|
|
241
298
|
</div>
|
|
@@ -246,19 +303,33 @@ export function EditUserPage() {
|
|
|
246
303
|
type="date"
|
|
247
304
|
disabled={saving}
|
|
248
305
|
value={form.dob}
|
|
249
|
-
onChange={(e) =>
|
|
306
|
+
onChange={(e) =>
|
|
307
|
+
setForm({ ...form, dob: e.target.value })
|
|
308
|
+
}
|
|
250
309
|
className={inputClassName}
|
|
251
310
|
/>
|
|
252
311
|
</div>
|
|
253
312
|
<div className="flex flex-col gap-2">
|
|
254
313
|
<Label htmlFor="gender">Gender</Label>
|
|
255
|
-
<Select
|
|
256
|
-
|
|
314
|
+
<Select
|
|
315
|
+
value={form.gender || undefined}
|
|
316
|
+
onValueChange={(value) =>
|
|
317
|
+
setForm({ ...form, gender: value })
|
|
318
|
+
}
|
|
319
|
+
disabled={saving}
|
|
320
|
+
>
|
|
321
|
+
<SelectTrigger
|
|
322
|
+
id="gender"
|
|
323
|
+
className={`w-full ${inputClassName}`}
|
|
324
|
+
>
|
|
257
325
|
<SelectValue placeholder="Select Gender" />
|
|
258
326
|
</SelectTrigger>
|
|
259
327
|
<SelectContent>
|
|
260
328
|
{GENDER_OPTIONS.map((option) => (
|
|
261
|
-
<SelectItem
|
|
329
|
+
<SelectItem
|
|
330
|
+
key={option.value}
|
|
331
|
+
value={option.value}
|
|
332
|
+
>
|
|
262
333
|
{option.label}
|
|
263
334
|
</SelectItem>
|
|
264
335
|
))}
|
|
@@ -270,11 +341,18 @@ export function EditUserPage() {
|
|
|
270
341
|
|
|
271
342
|
{canReadRoles && (
|
|
272
343
|
<div className="space-y-4">
|
|
273
|
-
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
344
|
+
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
345
|
+
Roles
|
|
346
|
+
</h3>
|
|
274
347
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
275
348
|
<div className="flex flex-col gap-2">
|
|
276
349
|
<Label>Roles</Label>
|
|
277
|
-
<RoleMultiSelect
|
|
350
|
+
<RoleMultiSelect
|
|
351
|
+
roles={roleCatalog}
|
|
352
|
+
selected={selectedRoles}
|
|
353
|
+
onChange={setSelectedRoles}
|
|
354
|
+
disabled={!canAssignRoles}
|
|
355
|
+
/>
|
|
278
356
|
</div>
|
|
279
357
|
</div>
|
|
280
358
|
</div>
|
|
@@ -284,17 +362,34 @@ export function EditUserPage() {
|
|
|
284
362
|
|
|
285
363
|
<CardHeader className="border-b bg-muted/50 mt-6">
|
|
286
364
|
<CardTitle className="text-2xl">Profile Photo</CardTitle>
|
|
287
|
-
<CardDescription className="text-base">
|
|
365
|
+
<CardDescription className="text-base">
|
|
366
|
+
Upload user's profile picture (Max size: 2MB, Formats:
|
|
367
|
+
JPG, PNG)
|
|
368
|
+
</CardDescription>
|
|
288
369
|
</CardHeader>
|
|
289
370
|
<CardContent className="pt-6">
|
|
290
|
-
<PhotoUpload
|
|
371
|
+
<PhotoUpload
|
|
372
|
+
photo={photo}
|
|
373
|
+
fallback="?"
|
|
374
|
+
onChange={(next) => setPhoto(next)}
|
|
375
|
+
disabled={saving}
|
|
376
|
+
/>
|
|
291
377
|
</CardContent>
|
|
292
378
|
|
|
293
379
|
<CardFooter className="flex justify-center gap-4 mt-8 pb-8">
|
|
294
|
-
<Button
|
|
380
|
+
<Button
|
|
381
|
+
type="button"
|
|
382
|
+
variant="outline"
|
|
383
|
+
onClick={() => navigate(`/users/${id}`)}
|
|
384
|
+
disabled={saving}
|
|
385
|
+
>
|
|
295
386
|
Cancel
|
|
296
387
|
</Button>
|
|
297
|
-
<Button
|
|
388
|
+
<Button
|
|
389
|
+
disabled={saving}
|
|
390
|
+
className="bg-purple-600 hover:bg-purple-700 dark:bg-purple-600 dark:hover:bg-purple-700 min-w-32"
|
|
391
|
+
type="submit"
|
|
392
|
+
>
|
|
298
393
|
{saving ? "Updating..." : "Update User"}
|
|
299
394
|
</Button>
|
|
300
395
|
</CardFooter>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from "react";
|
|
1
|
+
import { startTransition, useCallback, useEffect, useState } from "react";
|
|
2
2
|
import { Link, useNavigate, useParams } from "react-router-dom";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
AuthApiError,
|
|
5
|
+
type LanguageSummary,
|
|
6
|
+
} from "@simple-auth-kit/auth-client";
|
|
4
7
|
import { format } from "date-fns";
|
|
5
8
|
import { PencilIcon } from "lucide-react";
|
|
6
9
|
import { toast } from "sonner";
|
|
@@ -10,7 +13,13 @@ import { cn } from "@/lib/cn";
|
|
|
10
13
|
import { Breadcrumb } from "@/components/breadcrumb";
|
|
11
14
|
import { Badge } from "@/components/ui/badge";
|
|
12
15
|
import { Button, buttonVariants } 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 { Modal } from "@/components/ui/modal";
|
|
15
24
|
|
|
16
25
|
function apiErrorMessage(err: unknown, fallback: string): string {
|
|
@@ -51,7 +60,7 @@ export function LanguageDetailPage() {
|
|
|
51
60
|
}, [id]);
|
|
52
61
|
|
|
53
62
|
useEffect(() => {
|
|
54
|
-
void load();
|
|
63
|
+
startTransition(() => void load());
|
|
55
64
|
}, [load]);
|
|
56
65
|
|
|
57
66
|
async function toggleActive() {
|
|
@@ -60,10 +69,19 @@ export function LanguageDetailPage() {
|
|
|
60
69
|
try {
|
|
61
70
|
if (language.isActive) await authClient.deactivateLanguage(id);
|
|
62
71
|
else await authClient.activateLanguage(id);
|
|
63
|
-
setLanguage((prev) =>
|
|
64
|
-
|
|
72
|
+
setLanguage((prev) =>
|
|
73
|
+
prev ? { ...prev, isActive: !prev.isActive } : prev,
|
|
74
|
+
);
|
|
75
|
+
toast.success(
|
|
76
|
+
language.isActive ? "Language deactivated." : "Language activated.",
|
|
77
|
+
);
|
|
65
78
|
} catch (err) {
|
|
66
|
-
toast.error(
|
|
79
|
+
toast.error(
|
|
80
|
+
apiErrorMessage(
|
|
81
|
+
err,
|
|
82
|
+
"Couldn't change this language's status. Try again.",
|
|
83
|
+
),
|
|
84
|
+
);
|
|
67
85
|
} finally {
|
|
68
86
|
setStatusPending(false);
|
|
69
87
|
}
|
|
@@ -73,9 +91,16 @@ export function LanguageDetailPage() {
|
|
|
73
91
|
|
|
74
92
|
return (
|
|
75
93
|
<div className="flex flex-col gap-4">
|
|
76
|
-
<Breadcrumb
|
|
94
|
+
<Breadcrumb
|
|
95
|
+
items={[
|
|
96
|
+
{ title: "Languages", href: "/languages" },
|
|
97
|
+
{ title: language?.name ?? "Details", href: `/languages/${id}` },
|
|
98
|
+
]}
|
|
99
|
+
/>
|
|
77
100
|
|
|
78
|
-
{loading && !language &&
|
|
101
|
+
{loading && !language && (
|
|
102
|
+
<p className="text-sm text-muted-foreground">Loading…</p>
|
|
103
|
+
)}
|
|
79
104
|
|
|
80
105
|
{language && (
|
|
81
106
|
<>
|
|
@@ -84,16 +109,28 @@ export function LanguageDetailPage() {
|
|
|
84
109
|
<div>
|
|
85
110
|
<CardTitle>{language.name}</CardTitle>
|
|
86
111
|
<CardDescription>
|
|
87
|
-
Created: {format(new Date(language.createdAt), "dd MMM yyyy")}
|
|
112
|
+
Created: {format(new Date(language.createdAt), "dd MMM yyyy")}{" "}
|
|
113
|
+
· Updated:{" "}
|
|
114
|
+
{format(new Date(language.updatedAt), "dd MMM yyyy")}
|
|
88
115
|
</CardDescription>
|
|
89
116
|
</div>
|
|
90
117
|
<div className="flex items-center gap-1.5">
|
|
91
|
-
{language.isDefault &&
|
|
92
|
-
|
|
118
|
+
{language.isDefault && (
|
|
119
|
+
<Badge variant="secondary">Default</Badge>
|
|
120
|
+
)}
|
|
121
|
+
<Badge variant={language.isActive ? "success" : "destructive"}>
|
|
122
|
+
{language.isActive ? "Active" : "Inactive"}
|
|
123
|
+
</Badge>
|
|
93
124
|
<Link
|
|
94
125
|
to={`/languages/${id}/edit`}
|
|
95
|
-
className={cn(
|
|
96
|
-
|
|
126
|
+
className={cn(
|
|
127
|
+
buttonVariants({ variant: "outline", size: "sm" }),
|
|
128
|
+
)}
|
|
129
|
+
title={
|
|
130
|
+
canManage
|
|
131
|
+
? undefined
|
|
132
|
+
: `You need the "${PERMISSIONS.languagesManage}" permission to do this.`
|
|
133
|
+
}
|
|
97
134
|
aria-disabled={!canManage}
|
|
98
135
|
onClick={(e) => !canManage && e.preventDefault()}
|
|
99
136
|
>
|
|
@@ -107,7 +144,12 @@ export function LanguageDetailPage() {
|
|
|
107
144
|
{field("Name", language.name)}
|
|
108
145
|
{field("Native name", language.nativeName)}
|
|
109
146
|
{field("Code", language.code)}
|
|
110
|
-
{field(
|
|
147
|
+
{field(
|
|
148
|
+
"Direction",
|
|
149
|
+
language.direction === "rtl"
|
|
150
|
+
? "Right to left (RTL)"
|
|
151
|
+
: "Left to right (LTR)",
|
|
152
|
+
)}
|
|
111
153
|
{field("Default language", language.isDefault ? "Yes" : "No")}
|
|
112
154
|
</div>
|
|
113
155
|
</CardContent>
|
|
@@ -121,7 +163,11 @@ export function LanguageDetailPage() {
|
|
|
121
163
|
<Button
|
|
122
164
|
variant={language.isActive ? "destructive" : "outline"}
|
|
123
165
|
disabled={!canStatus || statusPending}
|
|
124
|
-
title={
|
|
166
|
+
title={
|
|
167
|
+
canStatus
|
|
168
|
+
? undefined
|
|
169
|
+
: `You need the "${PERMISSIONS.languagesStatus}" permission to do this.`
|
|
170
|
+
}
|
|
125
171
|
onClick={() => void toggleActive()}
|
|
126
172
|
>
|
|
127
173
|
{language.isActive ? "Deactivate" : "Activate"}
|
|
@@ -130,7 +176,11 @@ export function LanguageDetailPage() {
|
|
|
130
176
|
<Button
|
|
131
177
|
variant="destructive"
|
|
132
178
|
disabled={!canManage}
|
|
133
|
-
title={
|
|
179
|
+
title={
|
|
180
|
+
canManage
|
|
181
|
+
? undefined
|
|
182
|
+
: `You need the "${PERMISSIONS.languagesManage}" permission to do this.`
|
|
183
|
+
}
|
|
134
184
|
onClick={() => setConfirmingDelete(true)}
|
|
135
185
|
>
|
|
136
186
|
Delete language
|
|
@@ -173,7 +223,9 @@ function DeleteLanguageDialog({
|
|
|
173
223
|
toast.success("Language deleted.");
|
|
174
224
|
onDeleted();
|
|
175
225
|
} catch (err) {
|
|
176
|
-
toast.error(
|
|
226
|
+
toast.error(
|
|
227
|
+
apiErrorMessage(err, "Couldn't delete this language. Try again."),
|
|
228
|
+
);
|
|
177
229
|
} finally {
|
|
178
230
|
setSubmitting(false);
|
|
179
231
|
}
|
|
@@ -188,7 +240,11 @@ function DeleteLanguageDialog({
|
|
|
188
240
|
>
|
|
189
241
|
<div className="flex flex-col gap-4">
|
|
190
242
|
<div className="flex gap-2">
|
|
191
|
-
<Button
|
|
243
|
+
<Button
|
|
244
|
+
variant="destructive"
|
|
245
|
+
disabled={submitting}
|
|
246
|
+
onClick={() => void handleDelete()}
|
|
247
|
+
>
|
|
192
248
|
{submitting ? "Deleting…" : "Delete language"}
|
|
193
249
|
</Button>
|
|
194
250
|
<Button variant="outline" onClick={onClose}>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from "react";
|
|
1
|
+
import { startTransition, useCallback, useEffect, useState } from "react";
|
|
2
2
|
import { useParams } from "react-router-dom";
|
|
3
3
|
import type { PermissionSummary } from "@simple-auth-kit/auth-client";
|
|
4
4
|
import { AuthApiError } from "@simple-auth-kit/auth-client";
|
|
@@ -10,7 +10,13 @@ import { DefinePermissionDialog } from "@/pages/PermissionsPage";
|
|
|
10
10
|
import { Breadcrumb } from "@/components/breadcrumb";
|
|
11
11
|
import { Badge } from "@/components/ui/badge";
|
|
12
12
|
import { Button } from "@/components/ui/button";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
Card,
|
|
15
|
+
CardContent,
|
|
16
|
+
CardDescription,
|
|
17
|
+
CardHeader,
|
|
18
|
+
CardTitle,
|
|
19
|
+
} from "@/components/ui/card";
|
|
14
20
|
|
|
15
21
|
function field(label: string, value: string | number | null) {
|
|
16
22
|
return (
|
|
@@ -38,25 +44,42 @@ export function PermissionDetailPage() {
|
|
|
38
44
|
const permissions = await authClient.listPermissions();
|
|
39
45
|
setPermission(permissions.find((p) => p.id === id) ?? null);
|
|
40
46
|
} catch (err) {
|
|
41
|
-
toast.error(
|
|
47
|
+
toast.error(
|
|
48
|
+
err instanceof AuthApiError
|
|
49
|
+
? err.message
|
|
50
|
+
: "Couldn't load this permission.",
|
|
51
|
+
);
|
|
42
52
|
} finally {
|
|
43
53
|
setLoading(false);
|
|
44
54
|
}
|
|
45
55
|
}, [id]);
|
|
46
56
|
|
|
47
57
|
useEffect(() => {
|
|
48
|
-
void load();
|
|
58
|
+
startTransition(() => void load());
|
|
49
59
|
}, [load]);
|
|
50
60
|
|
|
51
61
|
async function toggleActive() {
|
|
52
62
|
if (!permission) return;
|
|
53
63
|
setStatusPending(true);
|
|
54
64
|
try {
|
|
55
|
-
await authClient.definePermission({
|
|
56
|
-
|
|
57
|
-
|
|
65
|
+
await authClient.definePermission({
|
|
66
|
+
slug: permission.slug,
|
|
67
|
+
isActive: !permission.isActive,
|
|
68
|
+
});
|
|
69
|
+
setPermission((prev) =>
|
|
70
|
+
prev ? { ...prev, isActive: !prev.isActive } : prev,
|
|
71
|
+
);
|
|
72
|
+
toast.success(
|
|
73
|
+
permission.isActive
|
|
74
|
+
? "Permission deactivated."
|
|
75
|
+
: "Permission activated.",
|
|
76
|
+
);
|
|
58
77
|
} catch (err) {
|
|
59
|
-
toast.error(
|
|
78
|
+
toast.error(
|
|
79
|
+
err instanceof AuthApiError
|
|
80
|
+
? err.message
|
|
81
|
+
: "Couldn't change this permission's status. Try again.",
|
|
82
|
+
);
|
|
60
83
|
} finally {
|
|
61
84
|
setStatusPending(false);
|
|
62
85
|
}
|
|
@@ -66,9 +89,19 @@ export function PermissionDetailPage() {
|
|
|
66
89
|
|
|
67
90
|
return (
|
|
68
91
|
<div className="flex flex-col gap-4">
|
|
69
|
-
<Breadcrumb
|
|
92
|
+
<Breadcrumb
|
|
93
|
+
items={[
|
|
94
|
+
{ title: "Permissions", href: "/permissions" },
|
|
95
|
+
{
|
|
96
|
+
title: permission?.displayName ?? "Details",
|
|
97
|
+
href: `/permissions/${id}`,
|
|
98
|
+
},
|
|
99
|
+
]}
|
|
100
|
+
/>
|
|
70
101
|
|
|
71
|
-
{loading && !permission &&
|
|
102
|
+
{loading && !permission && (
|
|
103
|
+
<p className="text-sm text-muted-foreground">Loading…</p>
|
|
104
|
+
)}
|
|
72
105
|
|
|
73
106
|
{permission && (
|
|
74
107
|
<>
|
|
@@ -81,12 +114,20 @@ export function PermissionDetailPage() {
|
|
|
81
114
|
</CardDescription>
|
|
82
115
|
</div>
|
|
83
116
|
<div className="flex items-center gap-1.5">
|
|
84
|
-
<Badge
|
|
117
|
+
<Badge
|
|
118
|
+
variant={permission.isActive ? "success" : "destructive"}
|
|
119
|
+
>
|
|
120
|
+
{permission.isActive ? "Active" : "Inactive"}
|
|
121
|
+
</Badge>
|
|
85
122
|
<Button
|
|
86
123
|
variant="outline"
|
|
87
124
|
size="sm"
|
|
88
125
|
disabled={!canDefine}
|
|
89
|
-
title={
|
|
126
|
+
title={
|
|
127
|
+
canDefine
|
|
128
|
+
? undefined
|
|
129
|
+
: `You need the "${PERMISSIONS.permissionsDefine}" permission to do this.`
|
|
130
|
+
}
|
|
90
131
|
onClick={() => setEditing(true)}
|
|
91
132
|
>
|
|
92
133
|
<PencilIcon />
|
|
@@ -112,7 +153,11 @@ export function PermissionDetailPage() {
|
|
|
112
153
|
<Button
|
|
113
154
|
variant={permission.isActive ? "destructive" : "outline"}
|
|
114
155
|
disabled={!canDefine || statusPending}
|
|
115
|
-
title={
|
|
156
|
+
title={
|
|
157
|
+
canDefine
|
|
158
|
+
? undefined
|
|
159
|
+
: `You need the "${PERMISSIONS.permissionsDefine}" permission to do this.`
|
|
160
|
+
}
|
|
116
161
|
onClick={() => void toggleActive()}
|
|
117
162
|
>
|
|
118
163
|
{permission.isActive ? "Deactivate" : "Activate"}
|