@simple-auth-kit/cli 1.3.2 → 1.4.1
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/lib/copy.ts +67 -10
- 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/shared/gitignore +11 -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/shared/gitignore +17 -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/shared/gitignore +78 -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/gitignore +44 -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
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type FormEvent,
|
|
3
|
+
startTransition,
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useState,
|
|
7
|
+
} from "react";
|
|
2
8
|
import { Link } from "react-router-dom";
|
|
3
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
DefinePermissionInput,
|
|
11
|
+
PermissionSummary,
|
|
12
|
+
} from "@simple-auth-kit/auth-client";
|
|
4
13
|
import { AuthApiError } from "@simple-auth-kit/auth-client";
|
|
5
14
|
import { EyeIcon, PowerIcon } from "lucide-react";
|
|
6
15
|
import { toast } from "sonner";
|
|
@@ -14,10 +23,18 @@ import { Label } from "@/components/ui/label";
|
|
|
14
23
|
import { Button, buttonVariants } from "@/components/ui/button";
|
|
15
24
|
import { Badge } from "@/components/ui/badge";
|
|
16
25
|
import { Modal } from "@/components/ui/modal";
|
|
17
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
Table,
|
|
28
|
+
TableBody,
|
|
29
|
+
TableCell,
|
|
30
|
+
TableHead,
|
|
31
|
+
TableHeader,
|
|
32
|
+
TableRow,
|
|
33
|
+
} from "@/components/ui/table";
|
|
18
34
|
|
|
19
35
|
/** One dialog serves both create (slug editable) and edit (slug fixed) — `definePermission` upserts on slug. */
|
|
20
|
-
export type DialogState =
|
|
36
|
+
export type DialogState =
|
|
37
|
+
{ mode: "create" } | { mode: "edit"; permission: PermissionSummary };
|
|
21
38
|
|
|
22
39
|
interface PermissionGroup {
|
|
23
40
|
name: string;
|
|
@@ -51,7 +68,8 @@ export function PermissionsPage() {
|
|
|
51
68
|
const [error, setError] = useState<string | null>(null);
|
|
52
69
|
const [dialogState, setDialogState] = useState<DialogState | null>(null);
|
|
53
70
|
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
54
|
-
const [pendingPermission, setPendingPermission] =
|
|
71
|
+
const [pendingPermission, setPendingPermission] =
|
|
72
|
+
useState<PermissionSummary | null>(null);
|
|
55
73
|
const [pendingBusy, setPendingBusy] = useState(false);
|
|
56
74
|
|
|
57
75
|
const load = useCallback(async () => {
|
|
@@ -60,14 +78,18 @@ export function PermissionsPage() {
|
|
|
60
78
|
try {
|
|
61
79
|
setPermissions(await authClient.listPermissions());
|
|
62
80
|
} catch (err) {
|
|
63
|
-
setError(
|
|
81
|
+
setError(
|
|
82
|
+
err instanceof AuthApiError
|
|
83
|
+
? err.message
|
|
84
|
+
: "Couldn't load permissions. Check that the backend is running, then try again.",
|
|
85
|
+
);
|
|
64
86
|
} finally {
|
|
65
87
|
setLoading(false);
|
|
66
88
|
}
|
|
67
89
|
}, []);
|
|
68
90
|
|
|
69
91
|
useEffect(() => {
|
|
70
|
-
void load();
|
|
92
|
+
startTransition(() => void load());
|
|
71
93
|
}, [load]);
|
|
72
94
|
|
|
73
95
|
function openStatusConfirm(permission: PermissionSummary) {
|
|
@@ -79,13 +101,26 @@ export function PermissionsPage() {
|
|
|
79
101
|
if (!pendingPermission) return;
|
|
80
102
|
setPendingBusy(true);
|
|
81
103
|
try {
|
|
82
|
-
const updated = await authClient.definePermission({
|
|
83
|
-
|
|
84
|
-
|
|
104
|
+
const updated = await authClient.definePermission({
|
|
105
|
+
slug: pendingPermission.slug,
|
|
106
|
+
isActive: !pendingPermission.isActive,
|
|
107
|
+
});
|
|
108
|
+
setPermissions((prev) =>
|
|
109
|
+
prev.map((p) => (p.id === updated.id ? updated : p)),
|
|
110
|
+
);
|
|
111
|
+
toast.success(
|
|
112
|
+
pendingPermission.isActive
|
|
113
|
+
? "Permission deactivated."
|
|
114
|
+
: "Permission activated.",
|
|
115
|
+
);
|
|
85
116
|
setConfirmOpen(false);
|
|
86
117
|
setPendingPermission(null);
|
|
87
118
|
} catch (err) {
|
|
88
|
-
toast.error(
|
|
119
|
+
toast.error(
|
|
120
|
+
err instanceof AuthApiError
|
|
121
|
+
? err.message
|
|
122
|
+
: "Couldn't change this permission's status. Try again.",
|
|
123
|
+
);
|
|
89
124
|
} finally {
|
|
90
125
|
setPendingBusy(false);
|
|
91
126
|
}
|
|
@@ -98,9 +133,16 @@ export function PermissionsPage() {
|
|
|
98
133
|
<div className="flex items-start justify-between gap-4">
|
|
99
134
|
<div>
|
|
100
135
|
<h1 className="text-2xl font-semibold">Permissions</h1>
|
|
101
|
-
<p className="text-sm text-muted-foreground">
|
|
136
|
+
<p className="text-sm text-muted-foreground">
|
|
137
|
+
The full capability catalog, grouped the way the console reads it
|
|
138
|
+
elsewhere.
|
|
139
|
+
</p>
|
|
102
140
|
</div>
|
|
103
|
-
{canDefine ?
|
|
141
|
+
{canDefine ? (
|
|
142
|
+
<Button onClick={() => setDialogState({ mode: "create" })}>
|
|
143
|
+
New permission
|
|
144
|
+
</Button>
|
|
145
|
+
) : null}
|
|
104
146
|
</div>
|
|
105
147
|
|
|
106
148
|
<AlertModal
|
|
@@ -117,16 +159,22 @@ export function PermissionsPage() {
|
|
|
117
159
|
|
|
118
160
|
<Card>
|
|
119
161
|
<CardContent className="pt-6">
|
|
120
|
-
{error ?
|
|
162
|
+
{error ? (
|
|
163
|
+
<p className="mb-4 text-sm text-destructive">{error}</p>
|
|
164
|
+
) : null}
|
|
121
165
|
{loading ? (
|
|
122
166
|
<p className="text-sm text-muted-foreground">Loading…</p>
|
|
123
167
|
) : permissions.length === 0 ? (
|
|
124
|
-
<p className="text-sm text-muted-foreground">
|
|
168
|
+
<p className="text-sm text-muted-foreground">
|
|
169
|
+
No permissions defined yet.
|
|
170
|
+
</p>
|
|
125
171
|
) : (
|
|
126
172
|
<div className="flex flex-col gap-6">
|
|
127
173
|
{groups.map((group) => (
|
|
128
174
|
<div key={group.name}>
|
|
129
|
-
<h2 className="mb-2 text-sm font-semibold text-muted-foreground">
|
|
175
|
+
<h2 className="mb-2 text-sm font-semibold text-muted-foreground">
|
|
176
|
+
{group.name}
|
|
177
|
+
</h2>
|
|
130
178
|
<Table>
|
|
131
179
|
<TableHeader>
|
|
132
180
|
<TableRow>
|
|
@@ -140,19 +188,37 @@ export function PermissionsPage() {
|
|
|
140
188
|
<TableBody>
|
|
141
189
|
{group.permissions.map((permission) => (
|
|
142
190
|
<TableRow key={permission.id}>
|
|
143
|
-
<TableCell className="font-mono text-xs">
|
|
144
|
-
|
|
145
|
-
|
|
191
|
+
<TableCell className="font-mono text-xs">
|
|
192
|
+
{permission.slug}
|
|
193
|
+
</TableCell>
|
|
194
|
+
<TableCell className="font-medium">
|
|
195
|
+
{permission.displayName}
|
|
196
|
+
</TableCell>
|
|
197
|
+
<TableCell
|
|
198
|
+
className="max-w-xs truncate text-muted-foreground"
|
|
199
|
+
title={permission.description ?? undefined}
|
|
200
|
+
>
|
|
146
201
|
{permission.description ?? "—"}
|
|
147
202
|
</TableCell>
|
|
148
203
|
<TableCell>
|
|
149
|
-
<Badge
|
|
204
|
+
<Badge
|
|
205
|
+
variant={
|
|
206
|
+
permission.isActive ? "success" : "destructive"
|
|
207
|
+
}
|
|
208
|
+
>
|
|
209
|
+
{permission.isActive ? "Active" : "Inactive"}
|
|
210
|
+
</Badge>
|
|
150
211
|
</TableCell>
|
|
151
212
|
<TableCell className="text-right">
|
|
152
213
|
<div className="flex justify-end gap-2">
|
|
153
214
|
<Link
|
|
154
215
|
to={`/permissions/${permission.id}`}
|
|
155
|
-
className={cn(
|
|
216
|
+
className={cn(
|
|
217
|
+
buttonVariants({
|
|
218
|
+
variant: "outline",
|
|
219
|
+
size: "icon",
|
|
220
|
+
}),
|
|
221
|
+
)}
|
|
156
222
|
title="View details"
|
|
157
223
|
>
|
|
158
224
|
<EyeIcon />
|
|
@@ -162,12 +228,27 @@ export function PermissionsPage() {
|
|
|
162
228
|
<Button
|
|
163
229
|
size="icon"
|
|
164
230
|
variant="outline"
|
|
165
|
-
title={
|
|
166
|
-
|
|
231
|
+
title={
|
|
232
|
+
permission.isActive
|
|
233
|
+
? "Deactivate this permission"
|
|
234
|
+
: "Activate this permission"
|
|
235
|
+
}
|
|
236
|
+
onClick={() =>
|
|
237
|
+
openStatusConfirm(permission)
|
|
238
|
+
}
|
|
167
239
|
>
|
|
168
240
|
<PowerIcon />
|
|
169
241
|
</Button>
|
|
170
|
-
<Button
|
|
242
|
+
<Button
|
|
243
|
+
size="sm"
|
|
244
|
+
variant="outline"
|
|
245
|
+
onClick={() =>
|
|
246
|
+
setDialogState({
|
|
247
|
+
mode: "edit",
|
|
248
|
+
permission,
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
>
|
|
171
252
|
Edit
|
|
172
253
|
</Button>
|
|
173
254
|
</>
|
|
@@ -221,7 +302,11 @@ export function DefinePermissionDialog({
|
|
|
221
302
|
const [submitting, setSubmitting] = useState(false);
|
|
222
303
|
const [loadedFor, setLoadedFor] = useState<string | null>(null);
|
|
223
304
|
|
|
224
|
-
const key = state
|
|
305
|
+
const key = state
|
|
306
|
+
? state.mode === "edit"
|
|
307
|
+
? state.permission.id
|
|
308
|
+
: "__create__"
|
|
309
|
+
: null;
|
|
225
310
|
|
|
226
311
|
// Seed from the permission being edited (or blank, for create), once per open.
|
|
227
312
|
if (state && loadedFor !== key) {
|
|
@@ -258,7 +343,11 @@ export function DefinePermissionDialog({
|
|
|
258
343
|
const permission = await authClient.definePermission(input);
|
|
259
344
|
onSaved(permission);
|
|
260
345
|
} catch (err) {
|
|
261
|
-
setError(
|
|
346
|
+
setError(
|
|
347
|
+
err instanceof AuthApiError
|
|
348
|
+
? err.message
|
|
349
|
+
: "Couldn't save the permission. Try again.",
|
|
350
|
+
);
|
|
262
351
|
} finally {
|
|
263
352
|
setSubmitting(false);
|
|
264
353
|
}
|
|
@@ -294,15 +383,29 @@ export function DefinePermissionDialog({
|
|
|
294
383
|
</div>
|
|
295
384
|
<div className="flex flex-col gap-1.5">
|
|
296
385
|
<Label htmlFor="permission-displayName">Display name</Label>
|
|
297
|
-
<Input
|
|
386
|
+
<Input
|
|
387
|
+
id="permission-displayName"
|
|
388
|
+
value={displayName}
|
|
389
|
+
onChange={(e) => setDisplayName(e.target.value)}
|
|
390
|
+
placeholder="Defaults to the slug"
|
|
391
|
+
/>
|
|
298
392
|
</div>
|
|
299
393
|
<div className="flex flex-col gap-1.5">
|
|
300
394
|
<Label htmlFor="permission-description">Description</Label>
|
|
301
|
-
<Input
|
|
395
|
+
<Input
|
|
396
|
+
id="permission-description"
|
|
397
|
+
value={description}
|
|
398
|
+
onChange={(e) => setDescription(e.target.value)}
|
|
399
|
+
/>
|
|
302
400
|
</div>
|
|
303
401
|
<div className="flex flex-col gap-1.5">
|
|
304
402
|
<Label htmlFor="permission-group">Group</Label>
|
|
305
|
-
<Input
|
|
403
|
+
<Input
|
|
404
|
+
id="permission-group"
|
|
405
|
+
value={group}
|
|
406
|
+
onChange={(e) => setGroup(e.target.value)}
|
|
407
|
+
placeholder='Defaults to "Custom"'
|
|
408
|
+
/>
|
|
306
409
|
</div>
|
|
307
410
|
{state?.mode === "edit" ? (
|
|
308
411
|
<div className="flex items-center gap-2">
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from "react";
|
|
1
|
+
import { startTransition, useCallback, useEffect, useState } from "react";
|
|
2
2
|
import { useNavigate, useParams } from "react-router-dom";
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
PermissionSummary,
|
|
5
|
+
RoleSummary,
|
|
6
|
+
} from "@simple-auth-kit/auth-client";
|
|
4
7
|
import { AuthApiError } from "@simple-auth-kit/auth-client";
|
|
5
8
|
import { PencilIcon } from "lucide-react";
|
|
6
9
|
import { toast } from "sonner";
|
|
@@ -11,7 +14,13 @@ import { EditRoleDialog, DeleteRoleDialog } from "@/pages/RolesPage";
|
|
|
11
14
|
import { Breadcrumb } from "@/components/breadcrumb";
|
|
12
15
|
import { Badge } from "@/components/ui/badge";
|
|
13
16
|
import { Button } from "@/components/ui/button";
|
|
14
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
Card,
|
|
19
|
+
CardContent,
|
|
20
|
+
CardDescription,
|
|
21
|
+
CardHeader,
|
|
22
|
+
CardTitle,
|
|
23
|
+
} from "@/components/ui/card";
|
|
15
24
|
|
|
16
25
|
function apiErrorMessage(err: unknown, fallback: string): string {
|
|
17
26
|
return err instanceof AuthApiError ? err.message : fallback;
|
|
@@ -22,10 +31,15 @@ export function RoleDetailPage() {
|
|
|
22
31
|
const navigate = useNavigate();
|
|
23
32
|
const ability = useAbility();
|
|
24
33
|
const canManageRoles = ability.can(PERMISSIONS.rolesManage, "permission");
|
|
25
|
-
const canReadPermissions = ability.can(
|
|
34
|
+
const canReadPermissions = ability.can(
|
|
35
|
+
PERMISSIONS.permissionsRead,
|
|
36
|
+
"permission",
|
|
37
|
+
);
|
|
26
38
|
|
|
27
39
|
const [role, setRole] = useState<RoleSummary | null>(null);
|
|
28
|
-
const [permissionCatalog, setPermissionCatalog] = useState<
|
|
40
|
+
const [permissionCatalog, setPermissionCatalog] = useState<
|
|
41
|
+
PermissionSummary[]
|
|
42
|
+
>([]);
|
|
29
43
|
const [loading, setLoading] = useState(true);
|
|
30
44
|
const [statusPending, setStatusPending] = useState(false);
|
|
31
45
|
const [editingRole, setEditingRole] = useState<RoleSummary | null>(null);
|
|
@@ -45,7 +59,7 @@ export function RoleDetailPage() {
|
|
|
45
59
|
}, [id]);
|
|
46
60
|
|
|
47
61
|
useEffect(() => {
|
|
48
|
-
void load();
|
|
62
|
+
startTransition(() => void load());
|
|
49
63
|
}, [load]);
|
|
50
64
|
|
|
51
65
|
useEffect(() => {
|
|
@@ -53,7 +67,11 @@ export function RoleDetailPage() {
|
|
|
53
67
|
authClient
|
|
54
68
|
.listPermissions({ activeOnly: true })
|
|
55
69
|
.then(setPermissionCatalog)
|
|
56
|
-
.catch((err) =>
|
|
70
|
+
.catch((err) =>
|
|
71
|
+
toast.error(
|
|
72
|
+
apiErrorMessage(err, "Couldn't load the permission catalog."),
|
|
73
|
+
),
|
|
74
|
+
);
|
|
57
75
|
}, [canReadPermissions]);
|
|
58
76
|
|
|
59
77
|
async function toggleActive() {
|
|
@@ -64,7 +82,9 @@ export function RoleDetailPage() {
|
|
|
64
82
|
setRole((prev) => (prev ? { ...prev, isActive: !prev.isActive } : prev));
|
|
65
83
|
toast.success(role.isActive ? "Role deactivated." : "Role activated.");
|
|
66
84
|
} catch (err) {
|
|
67
|
-
toast.error(
|
|
85
|
+
toast.error(
|
|
86
|
+
apiErrorMessage(err, "Couldn't change this role's status. Try again."),
|
|
87
|
+
);
|
|
68
88
|
} finally {
|
|
69
89
|
setStatusPending(false);
|
|
70
90
|
}
|
|
@@ -74,9 +94,19 @@ export function RoleDetailPage() {
|
|
|
74
94
|
|
|
75
95
|
return (
|
|
76
96
|
<div className="flex flex-col gap-4">
|
|
77
|
-
<Breadcrumb
|
|
97
|
+
<Breadcrumb
|
|
98
|
+
items={[
|
|
99
|
+
{ title: "Roles", href: "/roles" },
|
|
100
|
+
{
|
|
101
|
+
title: role?.displayName ?? role?.name ?? "Details",
|
|
102
|
+
href: `/roles/${id}`,
|
|
103
|
+
},
|
|
104
|
+
]}
|
|
105
|
+
/>
|
|
78
106
|
|
|
79
|
-
{loading && !role &&
|
|
107
|
+
{loading && !role && (
|
|
108
|
+
<p className="text-sm text-muted-foreground">Loading…</p>
|
|
109
|
+
)}
|
|
80
110
|
|
|
81
111
|
{role && (
|
|
82
112
|
<>
|
|
@@ -92,12 +122,18 @@ export function RoleDetailPage() {
|
|
|
92
122
|
</CardDescription>
|
|
93
123
|
</div>
|
|
94
124
|
<div className="flex items-center gap-1.5">
|
|
95
|
-
<Badge variant={role.isActive ? "success" : "destructive"}>
|
|
125
|
+
<Badge variant={role.isActive ? "success" : "destructive"}>
|
|
126
|
+
{role.isActive ? "Active" : "Inactive"}
|
|
127
|
+
</Badge>
|
|
96
128
|
<Button
|
|
97
129
|
variant="outline"
|
|
98
130
|
size="sm"
|
|
99
131
|
disabled={!canManageRoles}
|
|
100
|
-
title={
|
|
132
|
+
title={
|
|
133
|
+
canManageRoles
|
|
134
|
+
? undefined
|
|
135
|
+
: `You need the "${PERMISSIONS.rolesManage}" permission to do this.`
|
|
136
|
+
}
|
|
101
137
|
onClick={() => setEditingRole(role)}
|
|
102
138
|
>
|
|
103
139
|
<PencilIcon />
|
|
@@ -107,9 +143,14 @@ export function RoleDetailPage() {
|
|
|
107
143
|
</CardHeader>
|
|
108
144
|
<CardContent className="flex flex-col gap-4">
|
|
109
145
|
<div className="flex flex-col gap-1.5">
|
|
110
|
-
<span className="text-xs text-muted-foreground">
|
|
146
|
+
<span className="text-xs text-muted-foreground">
|
|
147
|
+
Permissions ({role.permissions.length} attached)
|
|
148
|
+
</span>
|
|
111
149
|
{canReadPermissions ? (
|
|
112
|
-
<PermissionGroupView
|
|
150
|
+
<PermissionGroupView
|
|
151
|
+
permissions={permissionCatalog}
|
|
152
|
+
selected={role.permissions}
|
|
153
|
+
/>
|
|
113
154
|
) : (
|
|
114
155
|
<div className="flex flex-wrap gap-1">
|
|
115
156
|
{role.permissions.map((slug) => (
|
|
@@ -131,7 +172,11 @@ export function RoleDetailPage() {
|
|
|
131
172
|
<Button
|
|
132
173
|
variant={role.isActive ? "destructive" : "outline"}
|
|
133
174
|
disabled={!canManageRoles || statusPending}
|
|
134
|
-
title={
|
|
175
|
+
title={
|
|
176
|
+
canManageRoles
|
|
177
|
+
? undefined
|
|
178
|
+
: `You need the "${PERMISSIONS.rolesManage}" permission to do this.`
|
|
179
|
+
}
|
|
135
180
|
onClick={() => void toggleActive()}
|
|
136
181
|
>
|
|
137
182
|
{role.isActive ? "Deactivate" : "Activate"}
|
|
@@ -140,7 +185,11 @@ export function RoleDetailPage() {
|
|
|
140
185
|
<Button
|
|
141
186
|
variant="destructive"
|
|
142
187
|
disabled={!canManageRoles}
|
|
143
|
-
title={
|
|
188
|
+
title={
|
|
189
|
+
canManageRoles
|
|
190
|
+
? undefined
|
|
191
|
+
: `You need the "${PERMISSIONS.rolesManage}" permission to do this.`
|
|
192
|
+
}
|
|
144
193
|
onClick={() => setDeletingRole(role)}
|
|
145
194
|
>
|
|
146
195
|
Delete role
|
|
@@ -161,7 +210,11 @@ export function RoleDetailPage() {
|
|
|
161
210
|
}}
|
|
162
211
|
/>
|
|
163
212
|
|
|
164
|
-
<DeleteRoleDialog
|
|
213
|
+
<DeleteRoleDialog
|
|
214
|
+
role={deletingRole}
|
|
215
|
+
onClose={() => setDeletingRole(null)}
|
|
216
|
+
onDeleted={() => navigate("/roles")}
|
|
217
|
+
/>
|
|
165
218
|
</div>
|
|
166
219
|
);
|
|
167
220
|
}
|