betterstart-cli 0.0.22 → 0.0.23
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/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar-branding-skeleton.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar-user-menu-skeleton.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar-user-menu.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/lifecycle-hooks-tab.tsx +2 -2
- package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/snippets-tab.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-version-item.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-versions-current-row.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-versions-drawer.tsx +3 -3
- package/dist/assets/adapters/next/templates/init/components/shared/media/edit-media-dialog-content.tsx +2 -2
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-delete-drawer.tsx +3 -3
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-gallery-dialog.tsx +62 -64
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-grid-item.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-url-importer.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/data/navigation.ts +1 -3
- package/dist/assets/adapters/next/templates/init/lib/actions/auth/auth.ts +8 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/profile/change-own-password.ts +59 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/profile/index.ts +6 -1
- package/dist/assets/adapters/next/templates/init/lib/actions/profile/types.ts +10 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/users/change-user-password.ts +77 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/users/index.ts +3 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/users/types.ts +10 -0
- package/dist/assets/adapters/next/templates/init/lib/db/core/schema.ts +4 -0
- package/dist/assets/adapters/next/templates/init/pages/dashboard-page.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/pages/forgot-password-page-skeleton.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/pages/login-page-skeleton.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/pages/media/media-page-content.tsx +13 -18
- package/dist/assets/adapters/next/templates/init/pages/profile/profile-form.tsx +133 -1
- package/dist/assets/adapters/next/templates/init/pages/reset-password-page-skeleton.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/pages/users/change-password-dialog.tsx +229 -0
- package/dist/assets/adapters/next/templates/init/pages/users/columns.tsx +22 -14
- package/dist/assets/adapters/next/templates/init/pages/users/create-user-dialog.tsx +99 -93
- package/dist/assets/adapters/next/templates/init/pages/users/delete-user-dialog.tsx +17 -25
- package/dist/assets/adapters/next/templates/init/pages/users/users-table.tsx +5 -4
- package/dist/assets/shared-assets/react-admin/custom/content-editor/math-bubble-menu.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/content-editor/remove-table-part-icon.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/content-editor/selection-bubble-menu.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/custom/content-editor/table-bubble-menu.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-button/color-highlight-button.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/color-highlight-popover-content.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/color-highlight-popover.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/highlight-color-button.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/source-color-highlight-popover.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/date-range-picker.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/media-field-empty-state.tsx +19 -12
- package/dist/assets/shared-assets/react-admin/custom/media-field.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/custom/nested-object-list-field.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/placeholder-card.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/placeholder.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/accordion.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/alert-dialog.tsx +15 -4
- package/dist/assets/shared-assets/react-admin/ui/alert.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/avatar.tsx +5 -5
- package/dist/assets/shared-assets/react-admin/ui/badge.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/button-group.tsx +3 -3
- package/dist/assets/shared-assets/react-admin/ui/button.tsx +6 -6
- package/dist/assets/shared-assets/react-admin/ui/card.tsx +4 -4
- package/dist/assets/shared-assets/react-admin/ui/carousel.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/chart.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/combobox.tsx +4 -4
- package/dist/assets/shared-assets/react-admin/ui/command.tsx +3 -3
- package/dist/assets/shared-assets/react-admin/ui/context-menu.tsx +6 -6
- package/dist/assets/shared-assets/react-admin/ui/dialog.tsx +29 -6
- package/dist/assets/shared-assets/react-admin/ui/drawer.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/dropdown-menu.tsx +6 -6
- package/dist/assets/shared-assets/react-admin/ui/empty.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/field.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/hover-card.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/input-group.tsx +5 -6
- package/dist/assets/shared-assets/react-admin/ui/input-otp.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/input.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/item.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/menubar.tsx +8 -8
- package/dist/assets/shared-assets/react-admin/ui/native-select.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/navigation-menu.tsx +4 -4
- package/dist/assets/shared-assets/react-admin/ui/popover.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/progress.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/radio-group.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/resizable.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/scroll-area.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/select.tsx +3 -3
- package/dist/assets/shared-assets/react-admin/ui/sidebar.tsx +10 -10
- package/dist/assets/shared-assets/react-admin/ui/skeleton.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/slider.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/switch.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/tabs.tsx +3 -3
- package/dist/assets/shared-assets/react-admin/ui/textarea.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/toggle-group.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/toggle.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/tooltip.tsx +1 -1
- package/dist/cli.js +5372 -4243
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
changeOwnPassword,
|
|
5
|
+
invalidateUsersCache,
|
|
6
|
+
isEmailConfigured,
|
|
7
|
+
updateEmail
|
|
8
|
+
} from '@admin/actions/profile'
|
|
4
9
|
import { authClient } from '@admin/auth/client'
|
|
5
10
|
import { MediaField } from '@admin/components/custom/media-field'
|
|
6
11
|
import { Avatar, AvatarFallback, AvatarImage } from '@admin/components/ui/avatar'
|
|
@@ -16,6 +21,7 @@ import {
|
|
|
16
21
|
import {
|
|
17
22
|
Form,
|
|
18
23
|
FormControl,
|
|
24
|
+
FormDescription,
|
|
19
25
|
FormField,
|
|
20
26
|
FormItem,
|
|
21
27
|
FormLabel,
|
|
@@ -40,7 +46,19 @@ const profileSchema = z.object({
|
|
|
40
46
|
currentPasswordForEmail: z.string().optional()
|
|
41
47
|
})
|
|
42
48
|
|
|
49
|
+
const passwordSchema = z
|
|
50
|
+
.object({
|
|
51
|
+
currentPassword: z.string().min(1, 'Current password is required'),
|
|
52
|
+
newPassword: z.string().min(8, 'Password must be at least 8 characters'),
|
|
53
|
+
confirmPassword: z.string().min(1, 'Confirm the new password')
|
|
54
|
+
})
|
|
55
|
+
.refine((values) => values.newPassword === values.confirmPassword, {
|
|
56
|
+
message: 'Passwords do not match',
|
|
57
|
+
path: ['confirmPassword']
|
|
58
|
+
})
|
|
59
|
+
|
|
43
60
|
type ProfileValues = z.infer<typeof profileSchema>
|
|
61
|
+
type PasswordValues = z.infer<typeof passwordSchema>
|
|
44
62
|
|
|
45
63
|
interface ProfileFormProps {
|
|
46
64
|
user: {
|
|
@@ -53,6 +71,7 @@ interface ProfileFormProps {
|
|
|
53
71
|
export function ProfileForm({ user }: ProfileFormProps) {
|
|
54
72
|
const router = useRouter()
|
|
55
73
|
const [profilePending, startProfileTransition] = React.useTransition()
|
|
74
|
+
const [passwordPending, startPasswordTransition] = React.useTransition()
|
|
56
75
|
const [resetPending, startResetTransition] = React.useTransition()
|
|
57
76
|
|
|
58
77
|
const profileForm = useForm<ProfileValues>({
|
|
@@ -64,6 +83,16 @@ export function ProfileForm({ user }: ProfileFormProps) {
|
|
|
64
83
|
currentPasswordForEmail: ''
|
|
65
84
|
}
|
|
66
85
|
})
|
|
86
|
+
const passwordForm = useForm<PasswordValues>({
|
|
87
|
+
resolver: standardSchemaResolver(passwordSchema),
|
|
88
|
+
defaultValues: {
|
|
89
|
+
currentPassword: '',
|
|
90
|
+
newPassword: '',
|
|
91
|
+
confirmPassword: ''
|
|
92
|
+
},
|
|
93
|
+
mode: 'onBlur',
|
|
94
|
+
reValidateMode: 'onChange'
|
|
95
|
+
})
|
|
67
96
|
|
|
68
97
|
const emailDirty = profileForm.watch('email') !== user.email
|
|
69
98
|
const imageValue = profileForm.watch('image')
|
|
@@ -115,6 +144,31 @@ export function ProfileForm({ user }: ProfileFormProps) {
|
|
|
115
144
|
})
|
|
116
145
|
}
|
|
117
146
|
|
|
147
|
+
function onPasswordSubmit(values: PasswordValues) {
|
|
148
|
+
startPasswordTransition(async () => {
|
|
149
|
+
try {
|
|
150
|
+
const result = await changeOwnPassword({
|
|
151
|
+
currentPassword: values.currentPassword,
|
|
152
|
+
newPassword: values.newPassword
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
if (!result.success) {
|
|
156
|
+
toast.error(result.error || 'Failed to change password')
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
toast.success('Password changed successfully')
|
|
161
|
+
passwordForm.reset({
|
|
162
|
+
currentPassword: '',
|
|
163
|
+
newPassword: '',
|
|
164
|
+
confirmPassword: ''
|
|
165
|
+
})
|
|
166
|
+
} catch {
|
|
167
|
+
toast.error('An error occurred while changing your password')
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
118
172
|
function handleResetPassword() {
|
|
119
173
|
startResetTransition(async () => {
|
|
120
174
|
try {
|
|
@@ -253,6 +307,84 @@ export function ProfileForm({ user }: ProfileFormProps) {
|
|
|
253
307
|
</form>
|
|
254
308
|
</Form>
|
|
255
309
|
|
|
310
|
+
<Form {...passwordForm}>
|
|
311
|
+
<form onSubmit={passwordForm.handleSubmit(onPasswordSubmit)}>
|
|
312
|
+
<Card>
|
|
313
|
+
<CardHeader>
|
|
314
|
+
<CardTitle>Change Password</CardTitle>
|
|
315
|
+
<CardDescription>Update the password for your account</CardDescription>
|
|
316
|
+
</CardHeader>
|
|
317
|
+
<CardContent className="space-y-5">
|
|
318
|
+
<FormField
|
|
319
|
+
control={passwordForm.control}
|
|
320
|
+
name="currentPassword"
|
|
321
|
+
render={({ field }) => (
|
|
322
|
+
<FormItem>
|
|
323
|
+
<FormLabel>Current Password</FormLabel>
|
|
324
|
+
<FormControl>
|
|
325
|
+
<Input
|
|
326
|
+
type="password"
|
|
327
|
+
autoComplete="current-password"
|
|
328
|
+
disabled={passwordPending}
|
|
329
|
+
{...field}
|
|
330
|
+
/>
|
|
331
|
+
</FormControl>
|
|
332
|
+
<FormMessage />
|
|
333
|
+
</FormItem>
|
|
334
|
+
)}
|
|
335
|
+
/>
|
|
336
|
+
<FormField
|
|
337
|
+
control={passwordForm.control}
|
|
338
|
+
name="newPassword"
|
|
339
|
+
render={({ field }) => (
|
|
340
|
+
<FormItem>
|
|
341
|
+
<FormLabel>New Password</FormLabel>
|
|
342
|
+
<FormControl>
|
|
343
|
+
<Input
|
|
344
|
+
type="password"
|
|
345
|
+
autoComplete="new-password"
|
|
346
|
+
disabled={passwordPending}
|
|
347
|
+
{...field}
|
|
348
|
+
/>
|
|
349
|
+
</FormControl>
|
|
350
|
+
<FormDescription>Use at least 8 characters.</FormDescription>
|
|
351
|
+
<FormMessage />
|
|
352
|
+
</FormItem>
|
|
353
|
+
)}
|
|
354
|
+
/>
|
|
355
|
+
<FormField
|
|
356
|
+
control={passwordForm.control}
|
|
357
|
+
name="confirmPassword"
|
|
358
|
+
render={({ field }) => (
|
|
359
|
+
<FormItem>
|
|
360
|
+
<FormLabel>Confirm Password</FormLabel>
|
|
361
|
+
<FormControl>
|
|
362
|
+
<Input
|
|
363
|
+
type="password"
|
|
364
|
+
autoComplete="new-password"
|
|
365
|
+
disabled={passwordPending}
|
|
366
|
+
{...field}
|
|
367
|
+
/>
|
|
368
|
+
</FormControl>
|
|
369
|
+
<FormMessage />
|
|
370
|
+
</FormItem>
|
|
371
|
+
)}
|
|
372
|
+
/>
|
|
373
|
+
</CardContent>
|
|
374
|
+
<CardFooter>
|
|
375
|
+
<Button
|
|
376
|
+
type="submit"
|
|
377
|
+
disabled={passwordPending || !passwordForm.formState.isDirty}
|
|
378
|
+
className="ml-auto min-w-25"
|
|
379
|
+
>
|
|
380
|
+
{passwordPending && <LoaderCircle className="animate-spin" />}
|
|
381
|
+
{passwordPending ? 'Changing...' : 'Change Password'}
|
|
382
|
+
</Button>
|
|
383
|
+
</CardFooter>
|
|
384
|
+
</Card>
|
|
385
|
+
</form>
|
|
386
|
+
</Form>
|
|
387
|
+
|
|
256
388
|
<Card>
|
|
257
389
|
<CardHeader>
|
|
258
390
|
<CardTitle>Reset Password</CardTitle>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export function ResetPasswordPageSkeleton() {
|
|
2
2
|
return (
|
|
3
|
-
<div className="overflow-hidden rounded-
|
|
3
|
+
<div className="overflow-hidden rounded-sm border bg-card">
|
|
4
4
|
<div className="grid md:grid-cols-[5fr_7fr]">
|
|
5
5
|
<div className="hidden min-h-[520px] bg-muted md:block" />
|
|
6
6
|
<div className="flex min-h-[520px] flex-col gap-10 p-6 md:p-20">
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { changeOwnPassword } from '@admin/actions/profile'
|
|
4
|
+
import { changeUserPassword } from '@admin/actions/users'
|
|
5
|
+
import { Button } from '@admin/components/ui/button'
|
|
6
|
+
import {
|
|
7
|
+
Dialog,
|
|
8
|
+
DialogBody,
|
|
9
|
+
DialogContent,
|
|
10
|
+
DialogDescription,
|
|
11
|
+
DialogFooter,
|
|
12
|
+
DialogHeader,
|
|
13
|
+
DialogTitle,
|
|
14
|
+
DialogTrigger
|
|
15
|
+
} from '@admin/components/ui/dialog'
|
|
16
|
+
import { DropdownMenuItem } from '@admin/components/ui/dropdown-menu'
|
|
17
|
+
import {
|
|
18
|
+
Form,
|
|
19
|
+
FormControl,
|
|
20
|
+
FormDescription,
|
|
21
|
+
FormField,
|
|
22
|
+
FormItem,
|
|
23
|
+
FormLabel,
|
|
24
|
+
FormMessage
|
|
25
|
+
} from '@admin/components/ui/form'
|
|
26
|
+
import { Input } from '@admin/components/ui/input'
|
|
27
|
+
import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'
|
|
28
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
29
|
+
import { KeySquare, LoaderCircle } from 'lucide-react'
|
|
30
|
+
import * as React from 'react'
|
|
31
|
+
import { useForm } from 'react-hook-form'
|
|
32
|
+
import { toast } from 'sonner'
|
|
33
|
+
import { z } from 'zod/v3'
|
|
34
|
+
|
|
35
|
+
const passwordFieldsSchema = z.object({
|
|
36
|
+
newPassword: z.string().min(8, 'Password must be at least 8 characters'),
|
|
37
|
+
confirmPassword: z.string().min(1, 'Confirm the new password')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const ownPasswordSchema = passwordFieldsSchema
|
|
41
|
+
.extend({
|
|
42
|
+
currentPassword: z.string().min(1, 'Current password is required')
|
|
43
|
+
})
|
|
44
|
+
.refine((values) => values.newPassword === values.confirmPassword, {
|
|
45
|
+
message: 'Passwords do not match',
|
|
46
|
+
path: ['confirmPassword']
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const userPasswordSchema = passwordFieldsSchema
|
|
50
|
+
.extend({
|
|
51
|
+
currentPassword: z.string().optional()
|
|
52
|
+
})
|
|
53
|
+
.refine((values) => values.newPassword === values.confirmPassword, {
|
|
54
|
+
message: 'Passwords do not match',
|
|
55
|
+
path: ['confirmPassword']
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
type ChangePasswordFormValues = z.infer<typeof userPasswordSchema>
|
|
59
|
+
|
|
60
|
+
const defaultValues: ChangePasswordFormValues = {
|
|
61
|
+
currentPassword: '',
|
|
62
|
+
newPassword: '',
|
|
63
|
+
confirmPassword: ''
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface ChangePasswordDialogProps {
|
|
67
|
+
userId: string
|
|
68
|
+
userName: string
|
|
69
|
+
userEmail: string
|
|
70
|
+
isCurrentUser: boolean
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function ChangePasswordDialog({
|
|
74
|
+
userId,
|
|
75
|
+
userName,
|
|
76
|
+
userEmail,
|
|
77
|
+
isCurrentUser
|
|
78
|
+
}: ChangePasswordDialogProps) {
|
|
79
|
+
const [open, setOpen] = React.useState(false)
|
|
80
|
+
const queryClient = useQueryClient()
|
|
81
|
+
const form = useForm<ChangePasswordFormValues>({
|
|
82
|
+
resolver: standardSchemaResolver(isCurrentUser ? ownPasswordSchema : userPasswordSchema),
|
|
83
|
+
defaultValues,
|
|
84
|
+
mode: 'onBlur',
|
|
85
|
+
reValidateMode: 'onChange'
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const mutation = useMutation({
|
|
89
|
+
mutationFn: async (values: ChangePasswordFormValues) => {
|
|
90
|
+
const result = isCurrentUser
|
|
91
|
+
? await changeOwnPassword({
|
|
92
|
+
currentPassword: values.currentPassword ?? '',
|
|
93
|
+
newPassword: values.newPassword
|
|
94
|
+
})
|
|
95
|
+
: await changeUserPassword({
|
|
96
|
+
userId,
|
|
97
|
+
newPassword: values.newPassword
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
if (!result.success) {
|
|
101
|
+
throw new Error(result.error || 'Failed to change password')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return result
|
|
105
|
+
},
|
|
106
|
+
onSuccess: async () => {
|
|
107
|
+
toast.success(
|
|
108
|
+
isCurrentUser ? 'Password changed successfully' : `Password changed for ${userName}`
|
|
109
|
+
)
|
|
110
|
+
await queryClient.invalidateQueries({ queryKey: ['users'] })
|
|
111
|
+
resetFormState()
|
|
112
|
+
setOpen(false)
|
|
113
|
+
},
|
|
114
|
+
onError: (error: Error) => {
|
|
115
|
+
toast.error(error.message || 'An unexpected error occurred')
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const isPending = mutation.isPending
|
|
120
|
+
|
|
121
|
+
function resetFormState() {
|
|
122
|
+
form.reset(defaultValues)
|
|
123
|
+
mutation.reset()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function handleOpenChange(nextOpen: boolean) {
|
|
127
|
+
setOpen(nextOpen)
|
|
128
|
+
|
|
129
|
+
if (!nextOpen) {
|
|
130
|
+
resetFormState()
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<Dialog open={open} onOpenChange={handleOpenChange}>
|
|
136
|
+
<DialogTrigger asChild>
|
|
137
|
+
<DropdownMenuItem onSelect={(event) => event.preventDefault()}>
|
|
138
|
+
<KeySquare className="size-4" />
|
|
139
|
+
Change password
|
|
140
|
+
</DropdownMenuItem>
|
|
141
|
+
</DialogTrigger>
|
|
142
|
+
<DialogContent className="sm:max-w-135">
|
|
143
|
+
<Form {...form}>
|
|
144
|
+
<form onSubmit={form.handleSubmit((values) => mutation.mutate(values))}>
|
|
145
|
+
<DialogHeader>
|
|
146
|
+
<DialogTitle>Change Password</DialogTitle>
|
|
147
|
+
<DialogDescription>
|
|
148
|
+
{isCurrentUser
|
|
149
|
+
? 'Set a new password for your account.'
|
|
150
|
+
: `Set a new password for ${userName || userEmail}.`}
|
|
151
|
+
</DialogDescription>
|
|
152
|
+
</DialogHeader>
|
|
153
|
+
|
|
154
|
+
<DialogBody>
|
|
155
|
+
<div className="space-y-4">
|
|
156
|
+
{isCurrentUser ? (
|
|
157
|
+
<FormField
|
|
158
|
+
control={form.control}
|
|
159
|
+
name="currentPassword"
|
|
160
|
+
render={({ field }) => (
|
|
161
|
+
<FormItem>
|
|
162
|
+
<FormLabel>Current Password</FormLabel>
|
|
163
|
+
<FormControl>
|
|
164
|
+
<Input
|
|
165
|
+
type="password"
|
|
166
|
+
autoComplete="current-password"
|
|
167
|
+
disabled={isPending}
|
|
168
|
+
autoFocus
|
|
169
|
+
{...field}
|
|
170
|
+
/>
|
|
171
|
+
</FormControl>
|
|
172
|
+
<FormMessage />
|
|
173
|
+
</FormItem>
|
|
174
|
+
)}
|
|
175
|
+
/>
|
|
176
|
+
) : null}
|
|
177
|
+
<FormField
|
|
178
|
+
control={form.control}
|
|
179
|
+
name="newPassword"
|
|
180
|
+
render={({ field }) => (
|
|
181
|
+
<FormItem>
|
|
182
|
+
<FormLabel>New Password</FormLabel>
|
|
183
|
+
<FormControl>
|
|
184
|
+
<Input
|
|
185
|
+
type="password"
|
|
186
|
+
autoComplete="new-password"
|
|
187
|
+
disabled={isPending}
|
|
188
|
+
autoFocus={!isCurrentUser}
|
|
189
|
+
{...field}
|
|
190
|
+
/>
|
|
191
|
+
</FormControl>
|
|
192
|
+
<FormDescription>Use at least 8 characters.</FormDescription>
|
|
193
|
+
<FormMessage />
|
|
194
|
+
</FormItem>
|
|
195
|
+
)}
|
|
196
|
+
/>
|
|
197
|
+
<FormField
|
|
198
|
+
control={form.control}
|
|
199
|
+
name="confirmPassword"
|
|
200
|
+
render={({ field }) => (
|
|
201
|
+
<FormItem>
|
|
202
|
+
<FormLabel>Confirm Password</FormLabel>
|
|
203
|
+
<FormControl>
|
|
204
|
+
<Input
|
|
205
|
+
type="password"
|
|
206
|
+
autoComplete="new-password"
|
|
207
|
+
disabled={isPending}
|
|
208
|
+
{...field}
|
|
209
|
+
/>
|
|
210
|
+
</FormControl>
|
|
211
|
+
<FormMessage />
|
|
212
|
+
</FormItem>
|
|
213
|
+
)}
|
|
214
|
+
/>
|
|
215
|
+
</div>
|
|
216
|
+
</DialogBody>
|
|
217
|
+
|
|
218
|
+
<DialogFooter showCloseButton={true}>
|
|
219
|
+
<Button type="submit" disabled={isPending} size="lg">
|
|
220
|
+
{isPending ? <LoaderCircle className="animate-spin" /> : null}
|
|
221
|
+
{isPending ? 'Changing...' : 'Change Password'}
|
|
222
|
+
</Button>
|
|
223
|
+
</DialogFooter>
|
|
224
|
+
</form>
|
|
225
|
+
</Form>
|
|
226
|
+
</DialogContent>
|
|
227
|
+
</Dialog>
|
|
228
|
+
)
|
|
229
|
+
}
|
|
@@ -5,13 +5,13 @@ import { Button } from '@admin/components/ui/button'
|
|
|
5
5
|
import {
|
|
6
6
|
DropdownMenu,
|
|
7
7
|
DropdownMenuContent,
|
|
8
|
-
DropdownMenuItem,
|
|
9
8
|
DropdownMenuTrigger
|
|
10
9
|
} from '@admin/components/ui/dropdown-menu'
|
|
11
|
-
import type
|
|
10
|
+
import { type User, UserRole } from '@admin/types/auth'
|
|
12
11
|
import { getUserInitials } from '@admin/utils/user/user'
|
|
13
12
|
import type { ColumnDef } from '@tanstack/react-table'
|
|
14
|
-
import { Edit,
|
|
13
|
+
import { Edit, MoreHorizontal, ShieldCheck } from 'lucide-react'
|
|
14
|
+
import { ChangePasswordDialog } from './change-password-dialog'
|
|
15
15
|
import { DeleteUserDialog } from './delete-user-dialog'
|
|
16
16
|
import { EditRoleDialog } from './edit-role-dialog'
|
|
17
17
|
|
|
@@ -138,9 +138,11 @@ export const columns: ColumnDef<User>[] = [
|
|
|
138
138
|
meta: { width: '80px' },
|
|
139
139
|
cell: ({ row, table }) => {
|
|
140
140
|
const currentUser = table.options.meta?.currentUser
|
|
141
|
-
const isCurrentUser =
|
|
141
|
+
const isCurrentUser =
|
|
142
|
+
currentUser?.id === row.original.id || currentUser?.email === row.original.email
|
|
143
|
+
const canChangePassword = isCurrentUser || currentUser?.role === UserRole.ADMIN
|
|
142
144
|
|
|
143
|
-
if (isCurrentUser) return null
|
|
145
|
+
if (!isCurrentUser && !currentUser) return null
|
|
144
146
|
|
|
145
147
|
return (
|
|
146
148
|
<div className="flex justify-end">
|
|
@@ -152,15 +154,21 @@ export const columns: ColumnDef<User>[] = [
|
|
|
152
154
|
</Button>
|
|
153
155
|
</DropdownMenuTrigger>
|
|
154
156
|
<DropdownMenuContent align="end">
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
157
|
+
{!isCurrentUser ? (
|
|
158
|
+
<DeleteUserDialog
|
|
159
|
+
userId={row.original.id}
|
|
160
|
+
userName={row.original.name}
|
|
161
|
+
isCurrentUser={isCurrentUser}
|
|
162
|
+
/>
|
|
163
|
+
) : null}
|
|
164
|
+
{canChangePassword ? (
|
|
165
|
+
<ChangePasswordDialog
|
|
166
|
+
userId={row.original.id}
|
|
167
|
+
userName={row.original.name}
|
|
168
|
+
userEmail={row.original.email}
|
|
169
|
+
isCurrentUser={isCurrentUser}
|
|
170
|
+
/>
|
|
171
|
+
) : null}
|
|
164
172
|
</DropdownMenuContent>
|
|
165
173
|
</DropdownMenu>
|
|
166
174
|
</div>
|