@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.
Files changed (80) hide show
  1. package/lib/copy.ts +67 -10
  2. package/package.json +1 -1
  3. package/registry/admin-apps/nextjs/shared/.husky/commit-msg +1 -0
  4. package/registry/admin-apps/nextjs/shared/.husky/pre-commit +1 -0
  5. package/registry/admin-apps/nextjs/shared/.lintstagedrc.json +4 -0
  6. package/registry/admin-apps/nextjs/shared/.prettierrc.json +3 -0
  7. package/registry/admin-apps/nextjs/shared/commitlint.config.js +5 -0
  8. package/registry/admin-apps/nextjs/shared/eslint.config.js +28 -0
  9. package/registry/admin-apps/nextjs/shared/gitignore +11 -0
  10. package/registry/admin-apps/nextjs/variants/base/package.json +11 -2
  11. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/audit-log/page.tsx +72 -16
  12. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/customers/[id]/edit/page.tsx +107 -26
  13. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/customers/[id]/page.tsx +131 -29
  14. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/languages/[id]/edit/page.tsx +111 -25
  15. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/languages/[id]/page.tsx +97 -22
  16. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/permissions/[id]/edit/page.tsx +129 -46
  17. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/permissions/[id]/page.tsx +64 -14
  18. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/roles/[id]/page.tsx +91 -22
  19. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/users/[id]/edit/page.tsx +94 -23
  20. package/registry/admin-apps/nextjs/variants/workspaces/package.json +11 -2
  21. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/account/page.tsx +179 -41
  22. package/registry/admin-apps/react/shared/.husky/commit-msg +1 -0
  23. package/registry/admin-apps/react/shared/.husky/pre-commit +1 -0
  24. package/registry/admin-apps/react/shared/.lintstagedrc.json +4 -0
  25. package/registry/admin-apps/react/shared/.prettierrc.json +3 -0
  26. package/registry/admin-apps/react/shared/commitlint.config.js +3 -0
  27. package/registry/admin-apps/react/shared/eslint.config.js +38 -0
  28. package/registry/admin-apps/react/shared/gitignore +17 -0
  29. package/registry/admin-apps/react/variants/base/package.json +15 -2
  30. package/registry/admin-apps/react/variants/base/src/pages/CountryDetailPage.tsx +76 -19
  31. package/registry/admin-apps/react/variants/base/src/pages/CustomerDetailPage.tsx +110 -26
  32. package/registry/admin-apps/react/variants/base/src/pages/EditCountryPage.tsx +115 -29
  33. package/registry/admin-apps/react/variants/base/src/pages/EditCustomerPage.tsx +123 -31
  34. package/registry/admin-apps/react/variants/base/src/pages/EditLanguagePage.tsx +100 -24
  35. package/registry/admin-apps/react/variants/base/src/pages/EditRolePage.tsx +99 -26
  36. package/registry/admin-apps/react/variants/base/src/pages/EditUserPage.tsx +126 -31
  37. package/registry/admin-apps/react/variants/base/src/pages/LanguageDetailPage.tsx +75 -19
  38. package/registry/admin-apps/react/variants/base/src/pages/PermissionDetailPage.tsx +58 -13
  39. package/registry/admin-apps/react/variants/base/src/pages/PermissionsPage.tsx +132 -29
  40. package/registry/admin-apps/react/variants/base/src/pages/RoleDetailPage.tsx +70 -17
  41. package/registry/admin-apps/react/variants/base/src/pages/RolesPage.tsx +277 -59
  42. package/registry/admin-apps/react/variants/workspaces/package.json +15 -2
  43. package/registry/combos/express-drizzle/eslint.config.js +18 -0
  44. package/registry/combos/express-drizzle/package.json +5 -1
  45. package/registry/combos/express-drizzle/shared/src/request-context.ts +3 -0
  46. package/registry/combos/express-prisma/eslint.config.js +18 -0
  47. package/registry/combos/express-prisma/package.json +5 -1
  48. package/registry/combos/express-prisma/shared/src/request-context.ts +3 -0
  49. package/registry/combos/nestjs-drizzle/eslint.config.js +18 -0
  50. package/registry/combos/nestjs-drizzle/package.json +5 -1
  51. package/registry/combos/nestjs-drizzle/shared/src/auth.controller.ts +138 -33
  52. package/registry/combos/nestjs-drizzle/shared/src/route-tiers.ts +71 -18
  53. package/registry/combos/nestjs-prisma/eslint.config.js +18 -0
  54. package/registry/combos/nestjs-prisma/package.json +5 -1
  55. package/registry/combos/nestjs-prisma/shared/src/auth.controller.ts +148 -35
  56. package/registry/combos/nestjs-prisma/shared/src/route-tiers.ts +72 -18
  57. package/registry/mobile-apps/bare-rn/shared/.husky/commit-msg +1 -0
  58. package/registry/mobile-apps/bare-rn/shared/.husky/pre-commit +1 -0
  59. package/registry/mobile-apps/bare-rn/shared/.lintstagedrc.json +4 -0
  60. package/registry/mobile-apps/bare-rn/shared/.prettierrc.json +5 -0
  61. package/registry/mobile-apps/bare-rn/shared/commitlint.config.js +3 -0
  62. package/registry/mobile-apps/bare-rn/shared/eslint.config.js +19 -0
  63. package/registry/mobile-apps/bare-rn/shared/gitignore +78 -0
  64. package/registry/mobile-apps/bare-rn/variants/base/package.json +17 -12
  65. package/registry/mobile-apps/bare-rn/variants/workspaces/package.json +17 -12
  66. package/registry/mobile-apps/expo/shared/.husky/commit-msg +1 -0
  67. package/registry/mobile-apps/expo/shared/.husky/pre-commit +1 -0
  68. package/registry/mobile-apps/expo/shared/.lintstagedrc.json +4 -0
  69. package/registry/mobile-apps/expo/shared/.prettierrc.json +5 -0
  70. package/registry/mobile-apps/expo/shared/commitlint.config.js +3 -0
  71. package/registry/mobile-apps/expo/shared/eslint.config.js +9 -0
  72. package/registry/mobile-apps/expo/shared/gitignore +44 -0
  73. package/registry/mobile-apps/expo/shared/src/screens/SessionsScreen.tsx +6 -17
  74. package/registry/mobile-apps/expo/shared/src/store/sessionsStore.ts +35 -0
  75. package/registry/mobile-apps/expo/variants/base/package.json +12 -3
  76. package/registry/mobile-apps/expo/variants/workspaces/package.json +12 -3
  77. package/registry/mobile-apps/expo/variants/workspaces/src/screens/WorkspaceSelectScreen.tsx +1 -1
  78. package/registry.json +13 -36
  79. package/registry/mobile-apps/bare-rn/shared/.eslintrc.js +0 -4
  80. 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 { AuthApiError, type PermissionSummary, type RoleSummary } from "@simple-auth-kit/auth-client";
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 { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
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 { PERMISSIONS, hasPermission, missingPermissionHint, type AppAbility } from "@/lib/ability";
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(ability, PERMISSIONS.permissionsRead);
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<PermissionSummary[]>([]);
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) => toast.error(apiErrorMessage(err, "Couldn't load the permission catalog.")));
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) return <PermissionRequired permission={PERMISSIONS.rolesManage} what="Role details" />;
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(apiErrorMessage(err, "Couldn't change this role's status. Try again."));
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(apiErrorMessage(err, "Couldn't delete this role. Try again."));
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 items={[{ title: "Roles", href: "/roles" }, { title: role?.displayName ?? role?.name ?? "Details", href: `/roles/${id}` }]} />
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 && <p className="text-sm text-muted-foreground">Loading…</p>}
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"}>{role.isActive ? "Active" : "Inactive"}</Badge>
119
- <Link href={`/roles/${id}/edit`} className={buttonVariants({ variant: "outline", size: "sm" })}>
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">Permissions ({role.permissions.length} attached)</span>
176
+ <span className="text-xs text-muted-foreground">
177
+ Permissions ({role.permissions.length} attached)
178
+ </span>
128
179
  {canReadPermissions ? (
129
- <PermissionGroupView permissions={permissionCatalog} selected={role.permissions} />
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 variant={role.isActive ? "destructive" : "outline"} onClick={() => void toggleActive()}>
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>Delete {role.displayName || role.name}?</DialogTitle>
215
+ <DialogTitle>
216
+ Delete {role.displayName || role.name}?
217
+ </DialogTitle>
159
218
  <DialogDescription>
160
- Soft-delete: existing assignments are left in place rather than cascade-deleted, and the role simply stops being resolved.
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 variant="outline" onClick={() => setDeleteOpen(false)} disabled={deleting}>
225
+ <Button
226
+ variant="outline"
227
+ onClick={() => setDeleteOpen(false)}
228
+ disabled={deleting}
229
+ >
165
230
  Cancel
166
231
  </Button>
167
- <Button variant="destructive" onClick={() => void handleDelete()} disabled={deleting}>
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 { AuthApiError, type RoleSummary, type UpdateUserInput, type UserSummary } from "@simple-auth-kit/auth-client";
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 { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
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 { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
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 { GENDER_OPTIONS, editUserSchema, type EditUserFormValues } from "../../user-schema";
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) => toast.error(apiErrorMessage(err, "Couldn't load the role catalog.")));
125
+ .catch((err) =>
126
+ toast.error(apiErrorMessage(err, "Couldn't load the role catalog.")),
127
+ );
103
128
  }, [canReadRoles]);
104
129
 
105
- if (!canManage) return <PermissionRequired permission={PERMISSIONS.usersManage} what="Editing a user" />;
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 ? format(values.joinedDate, "yyyy-MM-dd") : undefined,
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 && <p className="text-sm text-muted-foreground">Loading…</p>}
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 onSubmit={form.handleSubmit(onSubmit)} className="space-y-8 w-full">
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">Update user&apos;s basic information</CardDescription>
206
+ <CardDescription className="text-base">
207
+ Update user&apos;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">Personal Information</h3>
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 &nbsp;
239
- <span className="text-xs text-destructive dark:text-destructive-foreground">(Must be unique)</span>
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">Contact Information</h3>
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 &nbsp;
266
- <span className="text-xs text-muted-foreground">(With country code)</span>
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">Additional Details</h3>
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) => field.onChange(option.value)}
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">Roles</h3>
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">Upload user&apos;s profile picture (Max size: 2MB, Formats: JPG, PNG)</CardDescription>
430
+ <CardDescription className="text-base">
431
+ Upload user&apos;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 photo={field.value} fallback="?" onChange={field.onChange} disabled={saving} />
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 type="button" variant="outline" onClick={() => router.push(`/users/${id}`)} disabled={saving}>
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 disabled={saving} className="bg-purple-600 hover:bg-purple-700 dark:bg-purple-600 dark:hover:bg-purple-700 min-w-32" type="submit">
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": "^7.0.2"
55
+ "typescript": "^6.0.3"
47
56
  }
48
57
  }