@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
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
import { useAbility } from "@casl/react";
|
|
4
4
|
import Link from "next/link";
|
|
5
5
|
import { useParams, useRouter } from "next/navigation";
|
|
6
|
-
import { useCallback, useEffect, useState } from "react";
|
|
7
|
-
import {
|
|
6
|
+
import { startTransition, useCallback, useEffect, useState } from "react";
|
|
7
|
+
import {
|
|
8
|
+
AuthApiError,
|
|
9
|
+
type LanguageSummary,
|
|
10
|
+
} from "@simple-auth-kit/auth-client";
|
|
8
11
|
import { format } from "date-fns";
|
|
9
12
|
import { PencilIcon } from "lucide-react";
|
|
10
13
|
import { toast } from "sonner";
|
|
@@ -12,7 +15,13 @@ import { Breadcrumb } from "@/components/breadcrumb";
|
|
|
12
15
|
import { PermissionRequired } from "@/components/permission-required";
|
|
13
16
|
import { Badge } from "@/components/ui/badge";
|
|
14
17
|
import { Button, buttonVariants } from "@/components/ui/button";
|
|
15
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
Card,
|
|
20
|
+
CardContent,
|
|
21
|
+
CardDescription,
|
|
22
|
+
CardHeader,
|
|
23
|
+
CardTitle,
|
|
24
|
+
} from "@/components/ui/card";
|
|
16
25
|
import {
|
|
17
26
|
Dialog,
|
|
18
27
|
DialogContent,
|
|
@@ -22,7 +31,12 @@ import {
|
|
|
22
31
|
DialogTitle,
|
|
23
32
|
DialogTrigger,
|
|
24
33
|
} from "@/components/ui/dialog";
|
|
25
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
PERMISSIONS,
|
|
36
|
+
hasPermission,
|
|
37
|
+
missingPermissionHint,
|
|
38
|
+
type AppAbility,
|
|
39
|
+
} from "@/lib/ability";
|
|
26
40
|
import { authClient } from "@/lib/auth-client";
|
|
27
41
|
|
|
28
42
|
function apiErrorMessage(err: unknown, fallback: string): string {
|
|
@@ -65,20 +79,35 @@ export default function LanguageDetailPage() {
|
|
|
65
79
|
|
|
66
80
|
useEffect(() => {
|
|
67
81
|
if (!canRead) return;
|
|
68
|
-
void load();
|
|
82
|
+
startTransition(() => void load());
|
|
69
83
|
}, [canRead, load]);
|
|
70
84
|
|
|
71
|
-
if (!canRead)
|
|
85
|
+
if (!canRead)
|
|
86
|
+
return (
|
|
87
|
+
<PermissionRequired
|
|
88
|
+
permission={PERMISSIONS.languagesRead}
|
|
89
|
+
what="Language details"
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
72
92
|
|
|
73
93
|
async function toggleActive() {
|
|
74
94
|
if (!language) return;
|
|
75
95
|
try {
|
|
76
96
|
if (language.isActive) await authClient.deactivateLanguage(id);
|
|
77
97
|
else await authClient.activateLanguage(id);
|
|
78
|
-
setLanguage((prev) =>
|
|
79
|
-
|
|
98
|
+
setLanguage((prev) =>
|
|
99
|
+
prev ? { ...prev, isActive: !prev.isActive } : prev,
|
|
100
|
+
);
|
|
101
|
+
toast.success(
|
|
102
|
+
language.isActive ? "Language deactivated." : "Language activated.",
|
|
103
|
+
);
|
|
80
104
|
} catch (err) {
|
|
81
|
-
toast.error(
|
|
105
|
+
toast.error(
|
|
106
|
+
apiErrorMessage(
|
|
107
|
+
err,
|
|
108
|
+
"Couldn't change this language's status. Try again.",
|
|
109
|
+
),
|
|
110
|
+
);
|
|
82
111
|
}
|
|
83
112
|
}
|
|
84
113
|
|
|
@@ -89,7 +118,9 @@ export default function LanguageDetailPage() {
|
|
|
89
118
|
toast.success("Language deleted.");
|
|
90
119
|
router.push("/languages");
|
|
91
120
|
} catch (err) {
|
|
92
|
-
toast.error(
|
|
121
|
+
toast.error(
|
|
122
|
+
apiErrorMessage(err, "Couldn't delete this language. Try again."),
|
|
123
|
+
);
|
|
93
124
|
setDeleting(false);
|
|
94
125
|
setDeleteOpen(false);
|
|
95
126
|
}
|
|
@@ -97,9 +128,16 @@ export default function LanguageDetailPage() {
|
|
|
97
128
|
|
|
98
129
|
return (
|
|
99
130
|
<div className="flex flex-col gap-4">
|
|
100
|
-
<Breadcrumb
|
|
131
|
+
<Breadcrumb
|
|
132
|
+
items={[
|
|
133
|
+
{ title: "Languages", href: "/languages" },
|
|
134
|
+
{ title: language?.name ?? "Details", href: `/languages/${id}` },
|
|
135
|
+
]}
|
|
136
|
+
/>
|
|
101
137
|
|
|
102
|
-
{loading && !language &&
|
|
138
|
+
{loading && !language && (
|
|
139
|
+
<p className="text-sm text-muted-foreground">Loading…</p>
|
|
140
|
+
)}
|
|
103
141
|
|
|
104
142
|
{language && (
|
|
105
143
|
<>
|
|
@@ -108,16 +146,26 @@ export default function LanguageDetailPage() {
|
|
|
108
146
|
<div>
|
|
109
147
|
<CardTitle>{language.name}</CardTitle>
|
|
110
148
|
<CardDescription>
|
|
111
|
-
Created: {format(new Date(language.createdAt), "dd MMM yyyy")}
|
|
149
|
+
Created: {format(new Date(language.createdAt), "dd MMM yyyy")}{" "}
|
|
150
|
+
· Updated:{" "}
|
|
151
|
+
{format(new Date(language.updatedAt), "dd MMM yyyy")}
|
|
112
152
|
</CardDescription>
|
|
113
153
|
</div>
|
|
114
154
|
<div className="flex items-center gap-1.5">
|
|
115
|
-
{language.isDefault &&
|
|
116
|
-
|
|
155
|
+
{language.isDefault && (
|
|
156
|
+
<Badge variant="secondary">Default</Badge>
|
|
157
|
+
)}
|
|
158
|
+
<Badge variant={language.isActive ? "success" : "destructive"}>
|
|
159
|
+
{language.isActive ? "Active" : "Inactive"}
|
|
160
|
+
</Badge>
|
|
117
161
|
<Link
|
|
118
162
|
href={`/languages/${id}/edit`}
|
|
119
163
|
className={buttonVariants({ variant: "outline", size: "sm" })}
|
|
120
|
-
title={
|
|
164
|
+
title={
|
|
165
|
+
canManage
|
|
166
|
+
? undefined
|
|
167
|
+
: missingPermissionHint(PERMISSIONS.languagesManage)
|
|
168
|
+
}
|
|
121
169
|
aria-disabled={!canManage}
|
|
122
170
|
onClick={(e) => !canManage && e.preventDefault()}
|
|
123
171
|
>
|
|
@@ -131,7 +179,12 @@ export default function LanguageDetailPage() {
|
|
|
131
179
|
{field("Name", language.name)}
|
|
132
180
|
{field("Native name", language.nativeName)}
|
|
133
181
|
{field("Code", language.code)}
|
|
134
|
-
{field(
|
|
182
|
+
{field(
|
|
183
|
+
"Direction",
|
|
184
|
+
language.direction === "rtl"
|
|
185
|
+
? "Right to left (RTL)"
|
|
186
|
+
: "Left to right (LTR)",
|
|
187
|
+
)}
|
|
135
188
|
{field("Default language", language.isDefault ? "Yes" : "No")}
|
|
136
189
|
</div>
|
|
137
190
|
</CardContent>
|
|
@@ -145,7 +198,11 @@ export default function LanguageDetailPage() {
|
|
|
145
198
|
<Button
|
|
146
199
|
variant={language.isActive ? "destructive" : "outline"}
|
|
147
200
|
disabled={!canStatus}
|
|
148
|
-
title={
|
|
201
|
+
title={
|
|
202
|
+
canStatus
|
|
203
|
+
? undefined
|
|
204
|
+
: missingPermissionHint(PERMISSIONS.languagesStatus)
|
|
205
|
+
}
|
|
149
206
|
onClick={() => void toggleActive()}
|
|
150
207
|
>
|
|
151
208
|
{language.isActive ? "Deactivate" : "Activate"}
|
|
@@ -153,7 +210,15 @@ export default function LanguageDetailPage() {
|
|
|
153
210
|
|
|
154
211
|
<Dialog open={deleteOpen} onOpenChange={setDeleteOpen}>
|
|
155
212
|
<DialogTrigger asChild>
|
|
156
|
-
<Button
|
|
213
|
+
<Button
|
|
214
|
+
variant="destructive"
|
|
215
|
+
disabled={!canManage}
|
|
216
|
+
title={
|
|
217
|
+
canManage
|
|
218
|
+
? undefined
|
|
219
|
+
: missingPermissionHint(PERMISSIONS.languagesManage)
|
|
220
|
+
}
|
|
221
|
+
>
|
|
157
222
|
Delete language
|
|
158
223
|
</Button>
|
|
159
224
|
</DialogTrigger>
|
|
@@ -161,14 +226,24 @@ export default function LanguageDetailPage() {
|
|
|
161
226
|
<DialogHeader>
|
|
162
227
|
<DialogTitle>Delete {language.name}?</DialogTitle>
|
|
163
228
|
<DialogDescription>
|
|
164
|
-
This soft-deletes the language: it stops appearing in
|
|
229
|
+
This soft-deletes the language: it stops appearing in
|
|
230
|
+
listings and pickers, but the row is kept for audit
|
|
231
|
+
purposes.
|
|
165
232
|
</DialogDescription>
|
|
166
233
|
</DialogHeader>
|
|
167
234
|
<DialogFooter>
|
|
168
|
-
<Button
|
|
235
|
+
<Button
|
|
236
|
+
variant="outline"
|
|
237
|
+
onClick={() => setDeleteOpen(false)}
|
|
238
|
+
disabled={deleting}
|
|
239
|
+
>
|
|
169
240
|
Cancel
|
|
170
241
|
</Button>
|
|
171
|
-
<Button
|
|
242
|
+
<Button
|
|
243
|
+
variant="destructive"
|
|
244
|
+
onClick={() => void handleDelete()}
|
|
245
|
+
disabled={deleting}
|
|
246
|
+
>
|
|
172
247
|
{deleting ? "Deleting…" : "Delete language"}
|
|
173
248
|
</Button>
|
|
174
249
|
</DialogFooter>
|
package/registry/admin-apps/nextjs/variants/base/src/app/(console)/permissions/[id]/edit/page.tsx
CHANGED
|
@@ -3,18 +3,36 @@
|
|
|
3
3
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
4
4
|
import { useAbility } from "@casl/react";
|
|
5
5
|
import { useParams, useRouter } from "next/navigation";
|
|
6
|
-
import { useEffect, useMemo, useState } from "react";
|
|
6
|
+
import { startTransition, useEffect, useMemo, useState } from "react";
|
|
7
7
|
import { useForm } from "react-hook-form";
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
DefinePermissionInput,
|
|
10
|
+
PermissionSummary,
|
|
11
|
+
} from "@simple-auth-kit/auth-client";
|
|
9
12
|
import { toast } from "sonner";
|
|
10
13
|
import { Breadcrumb } from "@/components/breadcrumb";
|
|
11
14
|
import { FormErrorAlert } from "@/components/form-error-alert";
|
|
12
15
|
import { PermissionRequired } from "@/components/permission-required";
|
|
13
16
|
import { Button } from "@/components/ui/button";
|
|
14
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
Card,
|
|
19
|
+
CardContent,
|
|
20
|
+
CardDescription,
|
|
21
|
+
CardFooter,
|
|
22
|
+
CardHeader,
|
|
23
|
+
CardTitle,
|
|
24
|
+
} from "@/components/ui/card";
|
|
15
25
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
16
26
|
import { Combobox, type ComboboxOptions } from "@/components/ui/combobox";
|
|
17
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
Form,
|
|
29
|
+
FormControl,
|
|
30
|
+
FormDescription,
|
|
31
|
+
FormField,
|
|
32
|
+
FormItem,
|
|
33
|
+
FormLabel,
|
|
34
|
+
FormMessage,
|
|
35
|
+
} from "@/components/ui/form";
|
|
18
36
|
import { Heading } from "@/components/ui/heading";
|
|
19
37
|
import { Input } from "@/components/ui/input";
|
|
20
38
|
import { Separator } from "@/components/ui/separator";
|
|
@@ -49,37 +67,48 @@ export default function EditPermissionPage() {
|
|
|
49
67
|
|
|
50
68
|
useEffect(() => {
|
|
51
69
|
if (!canDefine) return;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
startTransition(() => {
|
|
71
|
+
setLoading(true);
|
|
72
|
+
// No single-permission endpoint on the client — load the catalog and find by id.
|
|
73
|
+
authClient
|
|
74
|
+
.listPermissions()
|
|
75
|
+
.then((catalog) => {
|
|
76
|
+
setPermissions(catalog);
|
|
77
|
+
const permission = catalog.find((p) => p.id === id) ?? null;
|
|
78
|
+
setEditing(permission);
|
|
79
|
+
if (permission) {
|
|
80
|
+
form.reset({
|
|
81
|
+
slug: permission.slug,
|
|
82
|
+
displayName: permission.displayName,
|
|
83
|
+
description: permission.description ?? "",
|
|
84
|
+
group: permission.group,
|
|
85
|
+
groupOrder: permission.groupOrder,
|
|
86
|
+
order: permission.order,
|
|
87
|
+
isActive: permission.isActive,
|
|
88
|
+
});
|
|
89
|
+
} else {
|
|
90
|
+
toast.error("Couldn't find this permission.");
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
.catch((err) => toast.error(errorMessage(err)))
|
|
94
|
+
.finally(() => setLoading(false));
|
|
95
|
+
});
|
|
76
96
|
// `form` is stable across renders (react-hook-form memoizes it), so it's safe to omit here.
|
|
77
97
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
78
98
|
}, [canDefine, id]);
|
|
79
99
|
|
|
80
|
-
const groupOptions = useMemo(
|
|
100
|
+
const groupOptions = useMemo(
|
|
101
|
+
() => deriveGroupOptions(permissions),
|
|
102
|
+
[permissions],
|
|
103
|
+
);
|
|
81
104
|
|
|
82
|
-
if (!canDefine)
|
|
105
|
+
if (!canDefine)
|
|
106
|
+
return (
|
|
107
|
+
<PermissionRequired
|
|
108
|
+
permission={PERMISSIONS.permissionsDefine}
|
|
109
|
+
what="Editing a permission"
|
|
110
|
+
/>
|
|
111
|
+
);
|
|
83
112
|
|
|
84
113
|
function handleGroupSelect(option: ComboboxOptions) {
|
|
85
114
|
form.setValue("group", option.value, { shouldValidate: true });
|
|
@@ -119,11 +148,16 @@ export default function EditPermissionPage() {
|
|
|
119
148
|
]}
|
|
120
149
|
/>
|
|
121
150
|
<div className="flex items-start justify-between">
|
|
122
|
-
<Heading
|
|
151
|
+
<Heading
|
|
152
|
+
title="Edit Permission"
|
|
153
|
+
description="Update permission details"
|
|
154
|
+
/>
|
|
123
155
|
</div>
|
|
124
156
|
<Separator />
|
|
125
157
|
|
|
126
|
-
{loading && !editing &&
|
|
158
|
+
{loading && !editing && (
|
|
159
|
+
<p className="text-sm text-muted-foreground">Loading…</p>
|
|
160
|
+
)}
|
|
127
161
|
|
|
128
162
|
{editing && (
|
|
129
163
|
<Card>
|
|
@@ -131,16 +165,26 @@ export default function EditPermissionPage() {
|
|
|
131
165
|
<CardContent>
|
|
132
166
|
<FormErrorAlert messages={formError} />
|
|
133
167
|
<Form {...form}>
|
|
134
|
-
<form
|
|
168
|
+
<form
|
|
169
|
+
onSubmit={form.handleSubmit(onSubmit)}
|
|
170
|
+
className="space-y-8 w-full"
|
|
171
|
+
>
|
|
135
172
|
<Card className="w-full">
|
|
136
173
|
<CardHeader className="border-b bg-muted/50">
|
|
137
|
-
<CardTitle className="text-2xl">
|
|
138
|
-
|
|
174
|
+
<CardTitle className="text-2xl">
|
|
175
|
+
Permission Information
|
|
176
|
+
</CardTitle>
|
|
177
|
+
<CardDescription className="text-base">
|
|
178
|
+
Editing is an upsert keyed on slug — the slug itself
|
|
179
|
+
can't change here
|
|
180
|
+
</CardDescription>
|
|
139
181
|
</CardHeader>
|
|
140
182
|
<CardContent className="pt-6">
|
|
141
183
|
<div className="space-y-6">
|
|
142
184
|
<div className="space-y-4">
|
|
143
|
-
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
185
|
+
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
186
|
+
Basic Information
|
|
187
|
+
</h3>
|
|
144
188
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
145
189
|
<FormField
|
|
146
190
|
control={form.control}
|
|
@@ -203,7 +247,9 @@ export default function EditPermissionPage() {
|
|
|
203
247
|
</div>
|
|
204
248
|
|
|
205
249
|
<div className="space-y-4">
|
|
206
|
-
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
250
|
+
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
251
|
+
Grouping
|
|
252
|
+
</h3>
|
|
207
253
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
208
254
|
<FormField
|
|
209
255
|
control={form.control}
|
|
@@ -238,11 +284,20 @@ export default function EditPermissionPage() {
|
|
|
238
284
|
type="number"
|
|
239
285
|
disabled={saving}
|
|
240
286
|
value={field.value ?? ""}
|
|
241
|
-
onChange={(e) =>
|
|
287
|
+
onChange={(e) =>
|
|
288
|
+
field.onChange(
|
|
289
|
+
e.target.value === ""
|
|
290
|
+
? undefined
|
|
291
|
+
: Number(e.target.value),
|
|
292
|
+
)
|
|
293
|
+
}
|
|
242
294
|
className="bg-background border-2 focus:border-purple-500 transition-colors"
|
|
243
295
|
/>
|
|
244
296
|
</FormControl>
|
|
245
|
-
<FormDescription>
|
|
297
|
+
<FormDescription>
|
|
298
|
+
Derived from the group — override to reorder
|
|
299
|
+
groups.
|
|
300
|
+
</FormDescription>
|
|
246
301
|
<FormMessage />
|
|
247
302
|
</FormItem>
|
|
248
303
|
)}
|
|
@@ -258,11 +313,20 @@ export default function EditPermissionPage() {
|
|
|
258
313
|
type="number"
|
|
259
314
|
disabled={saving}
|
|
260
315
|
value={field.value ?? ""}
|
|
261
|
-
onChange={(e) =>
|
|
316
|
+
onChange={(e) =>
|
|
317
|
+
field.onChange(
|
|
318
|
+
e.target.value === ""
|
|
319
|
+
? undefined
|
|
320
|
+
: Number(e.target.value),
|
|
321
|
+
)
|
|
322
|
+
}
|
|
262
323
|
className="bg-background border-2 focus:border-purple-500 transition-colors"
|
|
263
324
|
/>
|
|
264
325
|
</FormControl>
|
|
265
|
-
<FormDescription>
|
|
326
|
+
<FormDescription>
|
|
327
|
+
Derived from the group's existing
|
|
328
|
+
permissions.
|
|
329
|
+
</FormDescription>
|
|
266
330
|
<FormMessage />
|
|
267
331
|
</FormItem>
|
|
268
332
|
)}
|
|
@@ -279,11 +343,21 @@ export default function EditPermissionPage() {
|
|
|
279
343
|
render={({ field }) => (
|
|
280
344
|
<FormItem className="flex flex-row items-center space-x-3 space-y-0 rounded-lg border border-purple-500 bg-purple-50 dark:bg-purple-950/20 p-4">
|
|
281
345
|
<FormControl>
|
|
282
|
-
<Checkbox
|
|
346
|
+
<Checkbox
|
|
347
|
+
checked={field.value ?? true}
|
|
348
|
+
onCheckedChange={(checked) =>
|
|
349
|
+
field.onChange(checked === true)
|
|
350
|
+
}
|
|
351
|
+
/>
|
|
283
352
|
</FormControl>
|
|
284
353
|
<div className="space-y-1 leading-none">
|
|
285
|
-
<FormLabel className="text-base font-medium">
|
|
286
|
-
|
|
354
|
+
<FormLabel className="text-base font-medium">
|
|
355
|
+
Active Status
|
|
356
|
+
</FormLabel>
|
|
357
|
+
<FormDescription className="text-sm">
|
|
358
|
+
This permission will be active and can be assigned
|
|
359
|
+
to roles
|
|
360
|
+
</FormDescription>
|
|
287
361
|
</div>
|
|
288
362
|
</FormItem>
|
|
289
363
|
)}
|
|
@@ -291,10 +365,19 @@ export default function EditPermissionPage() {
|
|
|
291
365
|
</div>
|
|
292
366
|
|
|
293
367
|
<CardFooter className="flex justify-center gap-4 mt-8 pb-8">
|
|
294
|
-
<Button
|
|
368
|
+
<Button
|
|
369
|
+
type="button"
|
|
370
|
+
variant="outline"
|
|
371
|
+
onClick={() => router.push("/permissions")}
|
|
372
|
+
disabled={saving}
|
|
373
|
+
>
|
|
295
374
|
Cancel
|
|
296
375
|
</Button>
|
|
297
|
-
<Button
|
|
376
|
+
<Button
|
|
377
|
+
disabled={saving}
|
|
378
|
+
className="bg-purple-600 hover:bg-purple-700 dark:bg-purple-600 dark:hover:bg-purple-700 min-w-32"
|
|
379
|
+
type="submit"
|
|
380
|
+
>
|
|
298
381
|
{saving ? "Updating..." : "Update Permission"}
|
|
299
382
|
</Button>
|
|
300
383
|
</CardFooter>
|
package/registry/admin-apps/nextjs/variants/base/src/app/(console)/permissions/[id]/page.tsx
CHANGED
|
@@ -3,15 +3,24 @@
|
|
|
3
3
|
import { useAbility } from "@casl/react";
|
|
4
4
|
import Link from "next/link";
|
|
5
5
|
import { useParams } from "next/navigation";
|
|
6
|
-
import { useCallback, useEffect, useState } from "react";
|
|
7
|
-
import {
|
|
6
|
+
import { startTransition, useCallback, useEffect, useState } from "react";
|
|
7
|
+
import {
|
|
8
|
+
AuthApiError,
|
|
9
|
+
type PermissionSummary,
|
|
10
|
+
} from "@simple-auth-kit/auth-client";
|
|
8
11
|
import { PencilIcon } from "lucide-react";
|
|
9
12
|
import { toast } from "sonner";
|
|
10
13
|
import { Breadcrumb } from "@/components/breadcrumb";
|
|
11
14
|
import { PermissionRequired } from "@/components/permission-required";
|
|
12
15
|
import { Badge } from "@/components/ui/badge";
|
|
13
16
|
import { Button, buttonVariants } 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
|
import { PERMISSIONS, hasPermission, type AppAbility } from "@/lib/ability";
|
|
16
25
|
import { authClient } from "@/lib/auth-client";
|
|
17
26
|
|
|
@@ -52,27 +61,57 @@ export default function PermissionDetailPage() {
|
|
|
52
61
|
|
|
53
62
|
useEffect(() => {
|
|
54
63
|
if (!canRead) return;
|
|
55
|
-
void load();
|
|
64
|
+
startTransition(() => void load());
|
|
56
65
|
}, [canRead, load]);
|
|
57
66
|
|
|
58
|
-
if (!canRead)
|
|
67
|
+
if (!canRead)
|
|
68
|
+
return (
|
|
69
|
+
<PermissionRequired
|
|
70
|
+
permission={PERMISSIONS.permissionsRead}
|
|
71
|
+
what="Permission details"
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
59
74
|
|
|
60
75
|
async function toggleActive() {
|
|
61
76
|
if (!permission) return;
|
|
62
77
|
try {
|
|
63
|
-
await authClient.definePermission({
|
|
64
|
-
|
|
65
|
-
|
|
78
|
+
await authClient.definePermission({
|
|
79
|
+
slug: permission.slug,
|
|
80
|
+
isActive: !permission.isActive,
|
|
81
|
+
});
|
|
82
|
+
setPermission((prev) =>
|
|
83
|
+
prev ? { ...prev, isActive: !prev.isActive } : prev,
|
|
84
|
+
);
|
|
85
|
+
toast.success(
|
|
86
|
+
permission.isActive
|
|
87
|
+
? "Permission deactivated."
|
|
88
|
+
: "Permission activated.",
|
|
89
|
+
);
|
|
66
90
|
} catch (err) {
|
|
67
|
-
toast.error(
|
|
91
|
+
toast.error(
|
|
92
|
+
apiErrorMessage(
|
|
93
|
+
err,
|
|
94
|
+
"Couldn't change this permission's status. Try again.",
|
|
95
|
+
),
|
|
96
|
+
);
|
|
68
97
|
}
|
|
69
98
|
}
|
|
70
99
|
|
|
71
100
|
return (
|
|
72
101
|
<div className="flex flex-col gap-4">
|
|
73
|
-
<Breadcrumb
|
|
102
|
+
<Breadcrumb
|
|
103
|
+
items={[
|
|
104
|
+
{ title: "Permissions", href: "/permissions" },
|
|
105
|
+
{
|
|
106
|
+
title: permission?.displayName ?? "Details",
|
|
107
|
+
href: `/permissions/${id}`,
|
|
108
|
+
},
|
|
109
|
+
]}
|
|
110
|
+
/>
|
|
74
111
|
|
|
75
|
-
{loading && !permission &&
|
|
112
|
+
{loading && !permission && (
|
|
113
|
+
<p className="text-sm text-muted-foreground">Loading…</p>
|
|
114
|
+
)}
|
|
76
115
|
|
|
77
116
|
{permission && (
|
|
78
117
|
<>
|
|
@@ -85,8 +124,15 @@ export default function PermissionDetailPage() {
|
|
|
85
124
|
</CardDescription>
|
|
86
125
|
</div>
|
|
87
126
|
<div className="flex items-center gap-1.5">
|
|
88
|
-
<Badge
|
|
89
|
-
|
|
127
|
+
<Badge
|
|
128
|
+
variant={permission.isActive ? "success" : "destructive"}
|
|
129
|
+
>
|
|
130
|
+
{permission.isActive ? "Active" : "Inactive"}
|
|
131
|
+
</Badge>
|
|
132
|
+
<Link
|
|
133
|
+
href={`/permissions/${id}/edit`}
|
|
134
|
+
className={buttonVariants({ variant: "outline", size: "sm" })}
|
|
135
|
+
>
|
|
90
136
|
<PencilIcon />
|
|
91
137
|
Edit
|
|
92
138
|
</Link>
|
|
@@ -107,7 +153,11 @@ export default function PermissionDetailPage() {
|
|
|
107
153
|
<CardTitle>Danger zone</CardTitle>
|
|
108
154
|
</CardHeader>
|
|
109
155
|
<CardContent className="flex flex-wrap gap-2">
|
|
110
|
-
<Button
|
|
156
|
+
<Button
|
|
157
|
+
variant={permission.isActive ? "destructive" : "outline"}
|
|
158
|
+
disabled={!canDefine}
|
|
159
|
+
onClick={() => void toggleActive()}
|
|
160
|
+
>
|
|
111
161
|
{permission.isActive ? "Deactivate" : "Activate"}
|
|
112
162
|
</Button>
|
|
113
163
|
</CardContent>
|