@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,8 +3,12 @@
|
|
|
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 PermissionSummary,
|
|
10
|
+
type RoleSummary,
|
|
11
|
+
} from "@simple-auth-kit/auth-client";
|
|
8
12
|
import { PencilIcon } from "lucide-react";
|
|
9
13
|
import { toast } from "sonner";
|
|
10
14
|
import { Breadcrumb } from "@/components/breadcrumb";
|
|
@@ -12,7 +16,13 @@ import { PermissionGroupView } from "@/components/permission-group-view";
|
|
|
12
16
|
import { PermissionRequired } from "@/components/permission-required";
|
|
13
17
|
import { Badge } from "@/components/ui/badge";
|
|
14
18
|
import { Button, buttonVariants } from "@/components/ui/button";
|
|
15
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
Card,
|
|
21
|
+
CardContent,
|
|
22
|
+
CardDescription,
|
|
23
|
+
CardHeader,
|
|
24
|
+
CardTitle,
|
|
25
|
+
} from "@/components/ui/card";
|
|
16
26
|
import {
|
|
17
27
|
Dialog,
|
|
18
28
|
DialogContent,
|
|
@@ -22,7 +32,12 @@ import {
|
|
|
22
32
|
DialogTitle,
|
|
23
33
|
DialogTrigger,
|
|
24
34
|
} from "@/components/ui/dialog";
|
|
25
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
PERMISSIONS,
|
|
37
|
+
hasPermission,
|
|
38
|
+
missingPermissionHint,
|
|
39
|
+
type AppAbility,
|
|
40
|
+
} from "@/lib/ability";
|
|
26
41
|
import { authClient } from "@/lib/auth-client";
|
|
27
42
|
|
|
28
43
|
function apiErrorMessage(err: unknown, fallback: string): string {
|
|
@@ -34,10 +49,15 @@ export default function RoleDetailPage() {
|
|
|
34
49
|
const router = useRouter();
|
|
35
50
|
const ability = useAbility<AppAbility>();
|
|
36
51
|
const canManage = hasPermission(ability, PERMISSIONS.rolesManage);
|
|
37
|
-
const canReadPermissions = hasPermission(
|
|
52
|
+
const canReadPermissions = hasPermission(
|
|
53
|
+
ability,
|
|
54
|
+
PERMISSIONS.permissionsRead,
|
|
55
|
+
);
|
|
38
56
|
|
|
39
57
|
const [role, setRole] = useState<RoleSummary | null>(null);
|
|
40
|
-
const [permissionCatalog, setPermissionCatalog] = useState<
|
|
58
|
+
const [permissionCatalog, setPermissionCatalog] = useState<
|
|
59
|
+
PermissionSummary[]
|
|
60
|
+
>([]);
|
|
41
61
|
const [loading, setLoading] = useState(false);
|
|
42
62
|
const [deleting, setDeleting] = useState(false);
|
|
43
63
|
const [deleteOpen, setDeleteOpen] = useState(false);
|
|
@@ -57,7 +77,7 @@ export default function RoleDetailPage() {
|
|
|
57
77
|
|
|
58
78
|
useEffect(() => {
|
|
59
79
|
if (!canManage) return;
|
|
60
|
-
void load();
|
|
80
|
+
startTransition(() => void load());
|
|
61
81
|
}, [canManage, load]);
|
|
62
82
|
|
|
63
83
|
useEffect(() => {
|
|
@@ -65,10 +85,20 @@ export default function RoleDetailPage() {
|
|
|
65
85
|
authClient
|
|
66
86
|
.listPermissions()
|
|
67
87
|
.then(setPermissionCatalog)
|
|
68
|
-
.catch((err) =>
|
|
88
|
+
.catch((err) =>
|
|
89
|
+
toast.error(
|
|
90
|
+
apiErrorMessage(err, "Couldn't load the permission catalog."),
|
|
91
|
+
),
|
|
92
|
+
);
|
|
69
93
|
}, [canReadPermissions]);
|
|
70
94
|
|
|
71
|
-
if (!canManage)
|
|
95
|
+
if (!canManage)
|
|
96
|
+
return (
|
|
97
|
+
<PermissionRequired
|
|
98
|
+
permission={PERMISSIONS.rolesManage}
|
|
99
|
+
what="Role details"
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
72
102
|
|
|
73
103
|
async function toggleActive() {
|
|
74
104
|
if (!role) return;
|
|
@@ -77,7 +107,9 @@ export default function RoleDetailPage() {
|
|
|
77
107
|
setRole((prev) => (prev ? { ...prev, isActive: !prev.isActive } : prev));
|
|
78
108
|
toast.success(role.isActive ? "Role deactivated." : "Role activated.");
|
|
79
109
|
} catch (err) {
|
|
80
|
-
toast.error(
|
|
110
|
+
toast.error(
|
|
111
|
+
apiErrorMessage(err, "Couldn't change this role's status. Try again."),
|
|
112
|
+
);
|
|
81
113
|
}
|
|
82
114
|
}
|
|
83
115
|
|
|
@@ -89,7 +121,9 @@ export default function RoleDetailPage() {
|
|
|
89
121
|
toast.success(`Role "${role.name}" deleted.`);
|
|
90
122
|
router.push("/roles");
|
|
91
123
|
} catch (err) {
|
|
92
|
-
toast.error(
|
|
124
|
+
toast.error(
|
|
125
|
+
apiErrorMessage(err, "Couldn't delete this role. Try again."),
|
|
126
|
+
);
|
|
93
127
|
setDeleting(false);
|
|
94
128
|
setDeleteOpen(false);
|
|
95
129
|
}
|
|
@@ -97,9 +131,19 @@ export default function RoleDetailPage() {
|
|
|
97
131
|
|
|
98
132
|
return (
|
|
99
133
|
<div className="flex flex-col gap-4">
|
|
100
|
-
<Breadcrumb
|
|
134
|
+
<Breadcrumb
|
|
135
|
+
items={[
|
|
136
|
+
{ title: "Roles", href: "/roles" },
|
|
137
|
+
{
|
|
138
|
+
title: role?.displayName ?? role?.name ?? "Details",
|
|
139
|
+
href: `/roles/${id}`,
|
|
140
|
+
},
|
|
141
|
+
]}
|
|
142
|
+
/>
|
|
101
143
|
|
|
102
|
-
{loading && !role &&
|
|
144
|
+
{loading && !role && (
|
|
145
|
+
<p className="text-sm text-muted-foreground">Loading…</p>
|
|
146
|
+
)}
|
|
103
147
|
|
|
104
148
|
{role && (
|
|
105
149
|
<>
|
|
@@ -115,8 +159,13 @@ export default function RoleDetailPage() {
|
|
|
115
159
|
</CardDescription>
|
|
116
160
|
</div>
|
|
117
161
|
<div className="flex items-center gap-1.5">
|
|
118
|
-
<Badge variant={role.isActive ? "success" : "destructive"}>
|
|
119
|
-
|
|
162
|
+
<Badge variant={role.isActive ? "success" : "destructive"}>
|
|
163
|
+
{role.isActive ? "Active" : "Inactive"}
|
|
164
|
+
</Badge>
|
|
165
|
+
<Link
|
|
166
|
+
href={`/roles/${id}/edit`}
|
|
167
|
+
className={buttonVariants({ variant: "outline", size: "sm" })}
|
|
168
|
+
>
|
|
120
169
|
<PencilIcon />
|
|
121
170
|
Edit
|
|
122
171
|
</Link>
|
|
@@ -124,9 +173,14 @@ export default function RoleDetailPage() {
|
|
|
124
173
|
</CardHeader>
|
|
125
174
|
<CardContent className="flex flex-col gap-4">
|
|
126
175
|
<div className="flex flex-col gap-1.5">
|
|
127
|
-
<span className="text-xs text-muted-foreground">
|
|
176
|
+
<span className="text-xs text-muted-foreground">
|
|
177
|
+
Permissions ({role.permissions.length} attached)
|
|
178
|
+
</span>
|
|
128
179
|
{canReadPermissions ? (
|
|
129
|
-
<PermissionGroupView
|
|
180
|
+
<PermissionGroupView
|
|
181
|
+
permissions={permissionCatalog}
|
|
182
|
+
selected={role.permissions}
|
|
183
|
+
/>
|
|
130
184
|
) : (
|
|
131
185
|
<div className="flex flex-wrap gap-1">
|
|
132
186
|
{role.permissions.map((slug) => (
|
|
@@ -145,7 +199,10 @@ export default function RoleDetailPage() {
|
|
|
145
199
|
<CardTitle>Danger zone</CardTitle>
|
|
146
200
|
</CardHeader>
|
|
147
201
|
<CardContent className="flex flex-wrap gap-2">
|
|
148
|
-
<Button
|
|
202
|
+
<Button
|
|
203
|
+
variant={role.isActive ? "destructive" : "outline"}
|
|
204
|
+
onClick={() => void toggleActive()}
|
|
205
|
+
>
|
|
149
206
|
{role.isActive ? "Deactivate" : "Activate"}
|
|
150
207
|
</Button>
|
|
151
208
|
|
|
@@ -155,16 +212,28 @@ export default function RoleDetailPage() {
|
|
|
155
212
|
</DialogTrigger>
|
|
156
213
|
<DialogContent>
|
|
157
214
|
<DialogHeader>
|
|
158
|
-
<DialogTitle>
|
|
215
|
+
<DialogTitle>
|
|
216
|
+
Delete {role.displayName || role.name}?
|
|
217
|
+
</DialogTitle>
|
|
159
218
|
<DialogDescription>
|
|
160
|
-
Soft-delete: existing assignments are left in place rather
|
|
219
|
+
Soft-delete: existing assignments are left in place rather
|
|
220
|
+
than cascade-deleted, and the role simply stops being
|
|
221
|
+
resolved.
|
|
161
222
|
</DialogDescription>
|
|
162
223
|
</DialogHeader>
|
|
163
224
|
<DialogFooter>
|
|
164
|
-
<Button
|
|
225
|
+
<Button
|
|
226
|
+
variant="outline"
|
|
227
|
+
onClick={() => setDeleteOpen(false)}
|
|
228
|
+
disabled={deleting}
|
|
229
|
+
>
|
|
165
230
|
Cancel
|
|
166
231
|
</Button>
|
|
167
|
-
<Button
|
|
232
|
+
<Button
|
|
233
|
+
variant="destructive"
|
|
234
|
+
onClick={() => void handleDelete()}
|
|
235
|
+
disabled={deleting}
|
|
236
|
+
>
|
|
168
237
|
{deleting ? "Deleting…" : "Delete role"}
|
|
169
238
|
</Button>
|
|
170
239
|
</DialogFooter>
|
|
@@ -3,10 +3,15 @@
|
|
|
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 { useCallback, useEffect, useState } from "react";
|
|
6
|
+
import { startTransition, useCallback, useEffect, useState } from "react";
|
|
7
7
|
import { useForm } from "react-hook-form";
|
|
8
8
|
import { format } from "date-fns";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
AuthApiError,
|
|
11
|
+
type RoleSummary,
|
|
12
|
+
type UpdateUserInput,
|
|
13
|
+
type UserSummary,
|
|
14
|
+
} from "@simple-auth-kit/auth-client";
|
|
10
15
|
import { toast } from "sonner";
|
|
11
16
|
import { Breadcrumb } from "@/components/breadcrumb";
|
|
12
17
|
import { FormErrorAlert } from "@/components/form-error-alert";
|
|
@@ -14,17 +19,35 @@ import { PermissionRequired } from "@/components/permission-required";
|
|
|
14
19
|
import { PhotoUpload } from "@/components/photo-upload";
|
|
15
20
|
import { RoleMultiSelect } from "@/components/role-multi-select";
|
|
16
21
|
import { Button } from "@/components/ui/button";
|
|
17
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
Card,
|
|
24
|
+
CardContent,
|
|
25
|
+
CardDescription,
|
|
26
|
+
CardFooter,
|
|
27
|
+
CardHeader,
|
|
28
|
+
CardTitle,
|
|
29
|
+
} from "@/components/ui/card";
|
|
18
30
|
import { Combobox } from "@/components/ui/combobox";
|
|
19
31
|
import { DatePicker } from "@/components/ui/date-picker";
|
|
20
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
Form,
|
|
34
|
+
FormControl,
|
|
35
|
+
FormField,
|
|
36
|
+
FormItem,
|
|
37
|
+
FormLabel,
|
|
38
|
+
FormMessage,
|
|
39
|
+
} from "@/components/ui/form";
|
|
21
40
|
import { Heading } from "@/components/ui/heading";
|
|
22
41
|
import { Input } from "@/components/ui/input";
|
|
23
42
|
import { Separator } from "@/components/ui/separator";
|
|
24
43
|
import { PERMISSIONS, hasPermission, type AppAbility } from "@/lib/ability";
|
|
25
44
|
import { authClient } from "@/lib/auth-client";
|
|
26
45
|
import { errorMessage, errorMessages } from "@/lib/error";
|
|
27
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
GENDER_OPTIONS,
|
|
48
|
+
editUserSchema,
|
|
49
|
+
type EditUserFormValues,
|
|
50
|
+
} from "../../user-schema";
|
|
28
51
|
|
|
29
52
|
function apiErrorMessage(err: unknown, fallback: string): string {
|
|
30
53
|
return err instanceof AuthApiError ? err.message : fallback;
|
|
@@ -91,7 +114,7 @@ export default function EditUserPage() {
|
|
|
91
114
|
|
|
92
115
|
useEffect(() => {
|
|
93
116
|
if (!canManage) return;
|
|
94
|
-
void load();
|
|
117
|
+
startTransition(() => void load());
|
|
95
118
|
}, [canManage, load]);
|
|
96
119
|
|
|
97
120
|
useEffect(() => {
|
|
@@ -99,10 +122,18 @@ export default function EditUserPage() {
|
|
|
99
122
|
authClient
|
|
100
123
|
.listRoles()
|
|
101
124
|
.then(setRoleCatalog)
|
|
102
|
-
.catch((err) =>
|
|
125
|
+
.catch((err) =>
|
|
126
|
+
toast.error(apiErrorMessage(err, "Couldn't load the role catalog.")),
|
|
127
|
+
);
|
|
103
128
|
}, [canReadRoles]);
|
|
104
129
|
|
|
105
|
-
if (!canManage)
|
|
130
|
+
if (!canManage)
|
|
131
|
+
return (
|
|
132
|
+
<PermissionRequired
|
|
133
|
+
permission={PERMISSIONS.usersManage}
|
|
134
|
+
what="Editing a user"
|
|
135
|
+
/>
|
|
136
|
+
);
|
|
106
137
|
|
|
107
138
|
async function onSubmit(values: EditUserFormValues) {
|
|
108
139
|
if (!user) return;
|
|
@@ -116,7 +147,9 @@ export default function EditUserPage() {
|
|
|
116
147
|
username: values.username || null,
|
|
117
148
|
dob: values.dob ? format(values.dob, "yyyy-MM-dd") : null,
|
|
118
149
|
gender: values.gender || null,
|
|
119
|
-
joinedDate: values.joinedDate
|
|
150
|
+
joinedDate: values.joinedDate
|
|
151
|
+
? format(values.joinedDate, "yyyy-MM-dd")
|
|
152
|
+
: undefined,
|
|
120
153
|
photo: values.photo || null,
|
|
121
154
|
};
|
|
122
155
|
// No bulk "set roles" endpoint — diffs against the roles loaded at mount and assigns/revokes only the delta.
|
|
@@ -153,7 +186,9 @@ export default function EditUserPage() {
|
|
|
153
186
|
</div>
|
|
154
187
|
<Separator />
|
|
155
188
|
|
|
156
|
-
{loading && !user &&
|
|
189
|
+
{loading && !user && (
|
|
190
|
+
<p className="text-sm text-muted-foreground">Loading…</p>
|
|
191
|
+
)}
|
|
157
192
|
|
|
158
193
|
{user && (
|
|
159
194
|
<Card>
|
|
@@ -161,16 +196,23 @@ export default function EditUserPage() {
|
|
|
161
196
|
<CardContent>
|
|
162
197
|
<FormErrorAlert messages={formError} />
|
|
163
198
|
<Form {...form}>
|
|
164
|
-
<form
|
|
199
|
+
<form
|
|
200
|
+
onSubmit={form.handleSubmit(onSubmit)}
|
|
201
|
+
className="space-y-8 w-full"
|
|
202
|
+
>
|
|
165
203
|
<Card className="w-full">
|
|
166
204
|
<CardHeader className="border-b bg-muted/50">
|
|
167
205
|
<CardTitle className="text-2xl">User Information</CardTitle>
|
|
168
|
-
<CardDescription className="text-base">
|
|
206
|
+
<CardDescription className="text-base">
|
|
207
|
+
Update user's basic information
|
|
208
|
+
</CardDescription>
|
|
169
209
|
</CardHeader>
|
|
170
210
|
<CardContent className="pt-6">
|
|
171
211
|
<div className="space-y-6">
|
|
172
212
|
<div className="space-y-4">
|
|
173
|
-
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
213
|
+
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
214
|
+
Personal Information
|
|
215
|
+
</h3>
|
|
174
216
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
175
217
|
<FormField
|
|
176
218
|
control={form.control}
|
|
@@ -236,7 +278,9 @@ export default function EditUserPage() {
|
|
|
236
278
|
<FormItem>
|
|
237
279
|
<FormLabel>
|
|
238
280
|
Username
|
|
239
|
-
<span className="text-xs text-destructive dark:text-destructive-foreground">
|
|
281
|
+
<span className="text-xs text-destructive dark:text-destructive-foreground">
|
|
282
|
+
(Must be unique)
|
|
283
|
+
</span>
|
|
240
284
|
</FormLabel>
|
|
241
285
|
<FormControl>
|
|
242
286
|
<Input
|
|
@@ -254,7 +298,9 @@ export default function EditUserPage() {
|
|
|
254
298
|
</div>
|
|
255
299
|
|
|
256
300
|
<div className="space-y-4">
|
|
257
|
-
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
301
|
+
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
302
|
+
Contact Information
|
|
303
|
+
</h3>
|
|
258
304
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
259
305
|
<FormField
|
|
260
306
|
control={form.control}
|
|
@@ -263,7 +309,9 @@ export default function EditUserPage() {
|
|
|
263
309
|
<FormItem>
|
|
264
310
|
<FormLabel>
|
|
265
311
|
Phone
|
|
266
|
-
<span className="text-xs text-muted-foreground">
|
|
312
|
+
<span className="text-xs text-muted-foreground">
|
|
313
|
+
(With country code)
|
|
314
|
+
</span>
|
|
267
315
|
</FormLabel>
|
|
268
316
|
<FormControl>
|
|
269
317
|
<Input
|
|
@@ -282,7 +330,9 @@ export default function EditUserPage() {
|
|
|
282
330
|
</div>
|
|
283
331
|
|
|
284
332
|
<div className="space-y-4">
|
|
285
|
-
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
333
|
+
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
334
|
+
Additional Details
|
|
335
|
+
</h3>
|
|
286
336
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
287
337
|
<FormField
|
|
288
338
|
control={form.control}
|
|
@@ -328,7 +378,9 @@ export default function EditUserPage() {
|
|
|
328
378
|
options={GENDER_OPTIONS}
|
|
329
379
|
selected={field.value ?? ""}
|
|
330
380
|
placeholder="Select Gender"
|
|
331
|
-
onChange={(option) =>
|
|
381
|
+
onChange={(option) =>
|
|
382
|
+
field.onChange(option.value)
|
|
383
|
+
}
|
|
332
384
|
showCreate={false}
|
|
333
385
|
popoverClassName="min-w-[200px]"
|
|
334
386
|
/>
|
|
@@ -343,7 +395,9 @@ export default function EditUserPage() {
|
|
|
343
395
|
|
|
344
396
|
{canReadRoles && (
|
|
345
397
|
<div className="space-y-4">
|
|
346
|
-
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
398
|
+
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
399
|
+
Roles
|
|
400
|
+
</h3>
|
|
347
401
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
348
402
|
<FormField
|
|
349
403
|
control={form.control}
|
|
@@ -373,7 +427,10 @@ export default function EditUserPage() {
|
|
|
373
427
|
|
|
374
428
|
<CardHeader className="border-b bg-muted/50 mt-6">
|
|
375
429
|
<CardTitle className="text-2xl">Profile Photo</CardTitle>
|
|
376
|
-
<CardDescription className="text-base">
|
|
430
|
+
<CardDescription className="text-base">
|
|
431
|
+
Upload user's profile picture (Max size: 2MB,
|
|
432
|
+
Formats: JPG, PNG)
|
|
433
|
+
</CardDescription>
|
|
377
434
|
</CardHeader>
|
|
378
435
|
<CardContent className="pt-6">
|
|
379
436
|
<FormField
|
|
@@ -383,7 +440,12 @@ export default function EditUserPage() {
|
|
|
383
440
|
<FormItem>
|
|
384
441
|
<FormControl>
|
|
385
442
|
<div>
|
|
386
|
-
<PhotoUpload
|
|
443
|
+
<PhotoUpload
|
|
444
|
+
photo={field.value}
|
|
445
|
+
fallback="?"
|
|
446
|
+
onChange={field.onChange}
|
|
447
|
+
disabled={saving}
|
|
448
|
+
/>
|
|
387
449
|
</div>
|
|
388
450
|
</FormControl>
|
|
389
451
|
<FormMessage />
|
|
@@ -393,10 +455,19 @@ export default function EditUserPage() {
|
|
|
393
455
|
</CardContent>
|
|
394
456
|
|
|
395
457
|
<CardFooter className="flex justify-center gap-4 mt-8 pb-8">
|
|
396
|
-
<Button
|
|
458
|
+
<Button
|
|
459
|
+
type="button"
|
|
460
|
+
variant="outline"
|
|
461
|
+
onClick={() => router.push(`/users/${id}`)}
|
|
462
|
+
disabled={saving}
|
|
463
|
+
>
|
|
397
464
|
Cancel
|
|
398
465
|
</Button>
|
|
399
|
-
<Button
|
|
466
|
+
<Button
|
|
467
|
+
disabled={saving}
|
|
468
|
+
className="bg-purple-600 hover:bg-purple-700 dark:bg-purple-600 dark:hover:bg-purple-700 min-w-32"
|
|
469
|
+
type="submit"
|
|
470
|
+
>
|
|
400
471
|
{saving ? "Updating..." : "Update User"}
|
|
401
472
|
</Button>
|
|
402
473
|
</CardFooter>
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"dev": "next dev -p 3010",
|
|
9
9
|
"build": "next build",
|
|
10
10
|
"start": "next start -p 3010",
|
|
11
|
-
"typecheck": "tsc --noEmit"
|
|
11
|
+
"typecheck": "tsc --noEmit",
|
|
12
|
+
"lint": "eslint .",
|
|
13
|
+
"prepare": "husky"
|
|
12
14
|
},
|
|
13
15
|
"dependencies": {
|
|
14
16
|
"@casl/ability": "^7.0.1",
|
|
@@ -38,11 +40,18 @@
|
|
|
38
40
|
"use-debounce": "^10.0.6"
|
|
39
41
|
},
|
|
40
42
|
"devDependencies": {
|
|
43
|
+
"@commitlint/cli": "^21.2.2",
|
|
44
|
+
"@commitlint/config-conventional": "^21.2.2",
|
|
41
45
|
"@tailwindcss/postcss": "^4.3.3",
|
|
42
46
|
"@types/node": "^26.2.0",
|
|
43
47
|
"@types/react": "^19.2.9",
|
|
44
48
|
"@types/react-dom": "^19.2.4",
|
|
49
|
+
"eslint": "^9.39.5",
|
|
50
|
+
"eslint-config-next": "^16.3.5",
|
|
51
|
+
"husky": "^9.1.7",
|
|
52
|
+
"lint-staged": "^17.5.1",
|
|
53
|
+
"prettier": "^3.9.6",
|
|
45
54
|
"tailwindcss": "^4.3.3",
|
|
46
|
-
"typescript": "^
|
|
55
|
+
"typescript": "^6.0.3"
|
|
47
56
|
}
|
|
48
57
|
}
|