diff --git a/messages/en.json b/messages/en.json index 66ff78b..019ede7 100644 --- a/messages/en.json +++ b/messages/en.json @@ -27,13 +27,13 @@ "exp_time": [ { "match": { - "time=1d": "1 day", - "time=7d": "7 days", - "time=15d": "15 days", - "time=1m": "1 month", - "time=6m": "6 month", - "time=1y": "1 year", - "time=0": "Forever" + "time=1": "1 day", + "time=7": "7 days", + "time=15": "15 days", + "time=30": "1 month", + "time=180": "6 month", + "time=365": "1 year", + "time=99999": "Forever" } } ], @@ -133,12 +133,23 @@ "users_page_message_update_info_success": "Edited user information successfully!", "users_page_message_set_role_success": "Modified user role successfully!", "users_page_message_banned_success": "Banned {name} successfully!", + "users_page_message_unbanned_success": "Unbanned {name} successfully!", "users_page_message_select_min_one_day": "Please select expiration at least 1 day!", "users_page_ui_form_ban_reason": "Ban reason", "users_page_ui_form_ban_exp": "Ban expiration", - "users_page_ui_select_placeholder_language": "Select language", + "users_page_ui_select_placeholder_role": "Select role", "users_page_ui_select_placeholder_ban_exp": "Select time", - "backend_INVALID_EMAIL_OR_PASSWORD": "Email or password incorrect!", - "backend_INVALID_PASSWORD": "Password incorrect!", - "backend_YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE": "You are not allowed to change users role" + "users_page_ui_dialog_alert_title": "Unban this user?", + "users_page_ui_dialog_alert_ban_title": "", + "users_page_ui_dialog_alert_description": "Detail: \nName: {name}. \nEmail: {email}", + "users_page_ui_dialog_alert_description_2": "Reason: {reason}. \nExpiration: {exp}", + "backend_message": [ + { + "match": { + "code=INVALID_EMAIL_OR_PASSWORD": "Email or password incorrect!", + "code=INVALID_PASSWORD": "Password incorrect!", + "code=YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE": "You are not allowed to change users role" + } + } + ] } diff --git a/messages/vi.json b/messages/vi.json index 4c94b84..534b059 100644 --- a/messages/vi.json +++ b/messages/vi.json @@ -27,13 +27,13 @@ "exp_time": [ { "match": { - "time=1d": "1 ngày", - "time=7d": "7 ngày", - "time=15d": "15 ngày", - "time=1m": "1 tháng", - "time=6m": "6 tháng", - "time=1y": "1 năm", - "time=0": "Vĩnh viễn" + "time=1": "1 ngày", + "time=7": "7 ngày", + "time=15": "15 ngày", + "time=30": "1 tháng", + "time=180": "6 tháng", + "time=365": "1 năm", + "time=99999": "Vĩnh viễn" } } ], @@ -133,12 +133,23 @@ "users_page_message_update_info_success": "Chỉnh sửa thông tin người dùng thành công!", "users_page_message_set_role_success": "Chỉnh sửa quyền hạn người dùng thành công!", "users_page_message_banned_success": "Người dùng {name} bị cấm thành công!", + "users_page_message_unbanned_success": "Người dùng {name} bỏ cấm thành công!", "users_page_message_select_min_one_day": "Chọn ít nhất là 1 ngày!", "users_page_ui_form_ban_reason": "Lý do cấm", "users_page_ui_form_ban_exp": "Thời gian cấm", - "users_page_ui_select_placeholder_language": "Hãy chọn ngôn ngữ", + "users_page_ui_select_placeholder_role": "Hãy chọn quyền hạn", "users_page_ui_select_placeholder_ban_exp": "Hãy chọn thời gian cấm", - "backend_INVALID_EMAIL_OR_PASSWORD": "Email hoặc mật khẩu không đúng!", - "backend_INVALID_PASSWORD": "Mật khẩu hiện tại không đúng!", - "backend_YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE": "Bạn không đủ quyền để chỉnh sửa quyền hạn người dùng!" + "users_page_ui_dialog_alert_title": "Bạn muốn mở khóa người dùng này?", + "users_page_ui_dialog_alert_ban_title": "Bạn muốn khóa người dùng này?", + "users_page_ui_dialog_alert_description": "Chi tiết: \nTên: {name}. \nEmail: {email}", + "users_page_ui_dialog_alert_description_2": "\nLý do: {reason}. \nHiệu lực: {exp}", + "backend_message": [ + { + "match": { + "code=INVALID_EMAIL_OR_PASSWORD": "Email hoặc mật khẩu không đúng!", + "code=INVALID_PASSWORD": "Mật khẩu hiện tại không đúng!", + "code=YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE": "Bạn không đủ quyền để chỉnh sửa quyền hạn người dùng!" + } + } + ] } diff --git a/src/components/DisplayBreakLineMessage.tsx b/src/components/DisplayBreakLineMessage.tsx new file mode 100644 index 0000000..dd1ad6f --- /dev/null +++ b/src/components/DisplayBreakLineMessage.tsx @@ -0,0 +1,9 @@ +const DisplayBreakLineMessage = ({ + children, +}: { + children: React.ReactNode; +}) => { + return
{children}
; +}; + +export default DisplayBreakLineMessage; diff --git a/src/components/form/admin-ban-user-form.tsx b/src/components/form/admin-ban-user-form.tsx index f9ad033..815ba55 100644 --- a/src/components/form/admin-ban-user-form.tsx +++ b/src/components/form/admin-ban-user-form.tsx @@ -1,13 +1,8 @@ import { m } from '@/paraglide/messages'; -import { usersQueries } from '@/service/queries'; -import { banUser } from '@/service/user.api'; import { userBanSchema } from '@/service/user.schema'; -import { ReturnError } from '@/types/common'; import { WarningIcon } from '@phosphor-icons/react'; import { useForm } from '@tanstack/react-form'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; import { UserWithRole } from 'better-auth/plugins'; -import { toast } from 'sonner'; import { Alert, AlertDescription, AlertTitle } from '../ui/alert'; import { Button } from '../ui/button'; import { DialogClose, DialogFooter } from '../ui/dialog'; @@ -21,34 +16,14 @@ import { SelectValue, } from '../ui/select'; import { Textarea } from '../ui/textarea'; +import { useBanContext } from '../user/ban-user-dialog'; type FormProps = { data: UserWithRole; - onSubmit: (open: boolean) => void; }; -const BanUserForm = ({ data, onSubmit }: FormProps) => { - const queryClient = useQueryClient(); - - const banUserMutation = useMutation({ - mutationFn: banUser, - onSuccess: () => { - queryClient.refetchQueries({ - queryKey: usersQueries.all, - }); - onSubmit(false); - toast.success(m.users_page_message_banned_success({ name: data.name }), { - richColors: true, - }); - }, - onError: (error: ReturnError) => { - console.error(error); - toast.error( - (m[`backend_${error.code}` as keyof typeof m] as () => string)(), - { richColors: true }, - ); - }, - }); +const BanUserForm = ({ data }: FormProps) => { + const { setSubmitData, setOpen, setOpenConfirm } = useBanContext(); const form = useForm({ defaultValues: { @@ -61,7 +36,11 @@ const BanUserForm = ({ data, onSubmit }: FormProps) => { onSubmit: userBanSchema, }, onSubmit: async ({ value }) => { - banUserMutation.mutate({ data: value }); + setSubmitData(value); + setOpen(false); + setTimeout(() => { + setOpenConfirm(true); + }, 100); }, }); @@ -147,25 +126,25 @@ const BanUserForm = ({ data, onSubmit }: FormProps) => { - {m.exp_time({ time: '1d' })} + {m.exp_time({ time: '1' })} - {m.exp_time({ time: '7d' })} + {m.exp_time({ time: '7' })} - {m.exp_time({ time: '15d' })} + {m.exp_time({ time: '15' })} - {m.exp_time({ time: '1m' })} + {m.exp_time({ time: '30' })} - {m.exp_time({ time: '6m' })} + {m.exp_time({ time: '180' })} - {m.exp_time({ time: '1y' })} + {m.exp_time({ time: '365' })} - {m.exp_time({ time: '0' })} + {m.exp_time({ time: '99999' })} @@ -176,11 +155,11 @@ const BanUserForm = ({ data, onSubmit }: FormProps) => { - - diff --git a/src/components/form/admin-set-password-form.tsx b/src/components/form/admin-set-password-form.tsx index 51591c2..830b31e 100644 --- a/src/components/form/admin-set-password-form.tsx +++ b/src/components/form/admin-set-password-form.tsx @@ -33,10 +33,9 @@ const AdminSetPasswordForm = ({ data, onSubmit }: FormProps) => { }, onError: (error: ReturnError) => { console.error(error); - toast.error( - (m[`backend_${error.code}` as keyof typeof m] as () => string)(), - { richColors: true }, - ); + toast.error(m.backend_message({ code: error.code }), { + richColors: true, + }); }, }); diff --git a/src/components/form/admin-set-user-role-form.tsx b/src/components/form/admin-set-user-role-form.tsx index 2c9c82f..6f8031e 100644 --- a/src/components/form/admin-set-user-role-form.tsx +++ b/src/components/form/admin-set-user-role-form.tsx @@ -45,10 +45,9 @@ const AdminSetUserRoleForm = ({ data, onSubmit }: SetRoleFormProps) => { }, onError: (error: ReturnError) => { console.error(error); - toast.error( - (m[`backend_${error.code}` as keyof typeof m] as () => string)(), - { richColors: true }, - ); + toast.error(m.backend_message({ code: error.code }), { + richColors: true, + }); }, }); @@ -111,7 +110,7 @@ const AdminSetUserRoleForm = ({ data, onSubmit }: SetRoleFormProps) => { > diff --git a/src/components/form/admin-update-user-info-form.tsx b/src/components/form/admin-update-user-info-form.tsx index 6eed8d8..bf5f4b0 100644 --- a/src/components/form/admin-update-user-info-form.tsx +++ b/src/components/form/admin-update-user-info-form.tsx @@ -33,10 +33,9 @@ const AdminUpdateUserInfoForm = ({ data, onSubmit }: UpdateUserFormProps) => { }, onError: (error: ReturnError) => { console.error(error); - toast.error( - (m[`backend_${error.code}` as keyof typeof m] as () => string)(), - { richColors: true }, - ); + toast.error(m.backend_message({ code: error.code }), { + richColors: true, + }); }, }); const form = useForm({ diff --git a/src/components/form/change-password-form.tsx b/src/components/form/change-password-form.tsx index 5137179..6ea2ca7 100644 --- a/src/components/form/change-password-form.tsx +++ b/src/components/form/change-password-form.tsx @@ -71,14 +71,9 @@ const ChangePasswordForm = () => { }, onError: (ctx) => { console.error(ctx.error.code); - toast.error( - ( - m[`backend_${ctx.error.code}` as keyof typeof m] as () => string - )(), - { - richColors: true, - }, - ); + toast.error(m.backend_message({ code: ctx.error.code }), { + richColors: true, + }); }, }, ); diff --git a/src/components/form/profile-form.tsx b/src/components/form/profile-form.tsx index aee8b1c..3f707fc 100644 --- a/src/components/form/profile-form.tsx +++ b/src/components/form/profile-form.tsx @@ -67,16 +67,9 @@ const ProfileForm = () => { }, onError: (ctx) => { console.error(ctx.error.code); - toast.error( - ( - m[ - `backend_${ctx.error.code}` as keyof typeof m - ] as () => string - )(), - { - richColors: true, - }, - ); + toast.error(m.backend_message({ code: ctx.error.code }), { + richColors: true, + }); }, }, ); diff --git a/src/components/form/settings-form.tsx b/src/components/form/settings-form.tsx index 3cbe075..4eea632 100644 --- a/src/components/form/settings-form.tsx +++ b/src/components/form/settings-form.tsx @@ -35,10 +35,9 @@ const SettingsForm = () => { }, onError: (error: ReturnError) => { console.error(error); - toast.error( - (m[`backend_${error.code}` as keyof typeof m] as () => string)(), - { richColors: true }, - ); + toast.error(m.backend_message({ code: error.code }), { + richColors: true, + }); }, }); diff --git a/src/components/form/signin-form.tsx b/src/components/form/signin-form.tsx index 9a6e945..51e96bd 100644 --- a/src/components/form/signin-form.tsx +++ b/src/components/form/signin-form.tsx @@ -52,14 +52,9 @@ const SignInForm = () => { }, onError: (ctx) => { console.error(ctx.error.code); - toast.error( - ( - m[`backend_${ctx.error.code}` as keyof typeof m] as () => string - )(), - { - richColors: true, - }, - ); + toast.error(m.backend_message({ code: ctx.error.code }), { + richColors: true, + }); }, }, ); diff --git a/src/components/form/user-settings-form.tsx b/src/components/form/user-settings-form.tsx index 2fb9bda..6f5f04a 100644 --- a/src/components/form/user-settings-form.tsx +++ b/src/components/form/user-settings-form.tsx @@ -43,10 +43,9 @@ const UserSettingsForm = () => { }, onError: (error: ReturnError) => { console.error(error); - toast.error( - (m[`backend_${error.code}` as keyof typeof m] as () => string)(), - { richColors: true }, - ); + toast.error(m.backend_message({ code: error.code }), { + richColors: true, + }); }, }); diff --git a/src/components/sidebar/nav-user.tsx b/src/components/sidebar/nav-user.tsx index 9db7e22..5437fde 100644 --- a/src/components/sidebar/nav-user.tsx +++ b/src/components/sidebar/nav-user.tsx @@ -49,14 +49,9 @@ const NavUser = () => { }, onError: (ctx) => { console.error(ctx.error.code); - toast.error( - ( - m[`backend_${ctx.error.code}` as keyof typeof m] as () => string - )(), - { - richColors: true, - }, - ); + toast.error(m.backend_message({ code: ctx.error.code }), { + richColors: true, + }); }, }, }); diff --git a/src/components/user/ban-user-confirm-dialog.tsx b/src/components/user/ban-user-confirm-dialog.tsx new file mode 100644 index 0000000..44aecd6 --- /dev/null +++ b/src/components/user/ban-user-confirm-dialog.tsx @@ -0,0 +1,98 @@ +import usePreventAutoFocus from '@/hooks/use-prevent-auto-focus'; +import { m } from '@/paraglide/messages'; +import { usersQueries } from '@/service/queries'; +import { banUser } from '@/service/user.api'; +import { ReturnError } from '@/types/common'; +import { ShieldWarningIcon } from '@phosphor-icons/react'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { UserWithRole } from 'better-auth/plugins'; +import { toast } from 'sonner'; +import DisplayBreakLineMessage from '../DisplayBreakLineMessage'; +import { Button } from '../ui/button'; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '../ui/dialog'; +import { useBanContext } from './ban-user-dialog'; + +type BanConfirmProps = { + data: UserWithRole; +}; + +const BanUserConfirm = ({ data }: BanConfirmProps) => { + const { openConfirm, setOpenConfirm, submitData } = useBanContext(); + const queryClient = useQueryClient(); + const prevent = usePreventAutoFocus(); + + const { mutate: banUserMutation } = useMutation({ + mutationFn: banUser, + onSuccess: () => { + queryClient.refetchQueries({ + queryKey: usersQueries.all, + }); + setOpenConfirm(false); + toast.success(m.users_page_message_banned_success({ name: data.name }), { + richColors: true, + }); + }, + onError: (error: ReturnError) => { + console.error(error); + toast.error(m.backend_message({ code: error.code }), { + richColors: true, + }); + }, + }); + + const onConfirm = () => { + banUserMutation({ data: submitData }); + }; + + return ( + + e.preventDefault()} + > + + +
+ +
+ {m.users_page_ui_dialog_alert_ban_title()} +
+ + {m.users_page_ui_dialog_alert_ban_title()} + +
+ + {m.users_page_ui_dialog_alert_description({ + name: data.name, + email: data.email, + })} + {m.users_page_ui_dialog_alert_description_2({ + reason: submitData.banReason, + exp: m.exp_time({ time: submitData.banExp }), + })} + + + + + + + +
+
+ ); +}; + +export default BanUserConfirm; diff --git a/src/components/user/ban-user-dialog.tsx b/src/components/user/ban-user-dialog.tsx new file mode 100644 index 0000000..bde71b7 --- /dev/null +++ b/src/components/user/ban-user-dialog.tsx @@ -0,0 +1,113 @@ +import usePreventAutoFocus from '@/hooks/use-prevent-auto-focus'; +import { m } from '@/paraglide/messages'; +import { LockIcon } from '@phosphor-icons/react'; +import { UserWithRole } from 'better-auth/plugins'; +import { createContext, useContext, useState } from 'react'; +import BanUserForm from '../form/admin-ban-user-form'; +import { Button } from '../ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '../ui/dialog'; +import { Label } from '../ui/label'; +import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; +import BanUserConfirm from './ban-user-confirm-dialog'; + +type ChangeUserStatusProps = { + data: UserWithRole; +}; + +type SubmitValue = { + id: string; + banReason: string; + banExp: number; +}; + +type BanContextProps = { + open: boolean; + setOpen: React.Dispatch>; + openConfirm: boolean; + setOpenConfirm: React.Dispatch>; + submitData: SubmitValue; + setSubmitData: React.Dispatch>; +}; + +const BanContext = createContext(null); + +const BanUserAction = ({ data }: ChangeUserStatusProps) => { + const [_open, _setOpen] = useState(false); + const [_openConfirm, _setOpenConfirm] = useState(false); + const [_confirmData, _setConfirmData] = useState({ + id: '', + banReason: '', + banExp: 0, + }); + const prevent = usePreventAutoFocus(); + + return ( + + + + + + + + + + + + + e.preventDefault()} + > + + + + {m.ui_ban_btn()} + + + {m.ui_change_role_btn()} + + + + + + + + ); +}; + +export default BanUserAction; + +export function useBanContext() { + const context = useContext(BanContext); + + if (!context) { + throw new Error('SHOULD_HAVE_PROVIDER'); + } + + return context; +} diff --git a/src/components/user/change-role-dialog.tsx b/src/components/user/change-role-dialog.tsx index c1ec42e..374fa11 100644 --- a/src/components/user/change-role-dialog.tsx +++ b/src/components/user/change-role-dialog.tsx @@ -43,23 +43,23 @@ const ChangeRoleAction = ({ data }: SetRoleProps) => { - e.preventDefault()} - > - - - - {m.ui_change_role_btn()} - - - {m.ui_change_role_btn()} - - - - + e.preventDefault()} + > + + + + {m.ui_change_role_btn()} + + + {m.ui_change_role_btn()} + + + + ); }; diff --git a/src/components/user/change-user-status-dialog.tsx b/src/components/user/change-user-status-dialog.tsx deleted file mode 100644 index 53854ce..0000000 --- a/src/components/user/change-user-status-dialog.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import usePreventAutoFocus from '@/hooks/use-prevent-auto-focus'; -import { m } from '@/paraglide/messages'; -import { LockIcon } from '@phosphor-icons/react'; -import { UserWithRole } from 'better-auth/plugins'; -import { useState } from 'react'; -import BanUserForm from '../form/admin-ban-user-form'; -import { Button } from '../ui/button'; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, -} from '../ui/dialog'; -import { Label } from '../ui/label'; -import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; - -type ChangeUserStatusProps = { - data: UserWithRole; -}; - -const ChangeUserStatusAction = ({ data }: ChangeUserStatusProps) => { - const [_open, _setOpen] = useState(false); - const prevent = usePreventAutoFocus(); - - return ( - - - - - - - - - - - e.preventDefault()} - > - - - - {m.ui_ban_btn()} - - - {m.ui_change_role_btn()} - - - - - - - ); -}; - -export default ChangeUserStatusAction; diff --git a/src/components/user/edit-user-dialog.tsx b/src/components/user/edit-user-dialog.tsx index 67f643a..b44abd3 100644 --- a/src/components/user/edit-user-dialog.tsx +++ b/src/components/user/edit-user-dialog.tsx @@ -43,22 +43,22 @@ const EditUserAction = ({ data }: EditUserProps) => { - e.preventDefault()} - > - - - {m.ui_edit_user_btn()} - - - {m.ui_edit_user_btn()} - - - - + e.preventDefault()} + > + + + {m.ui_edit_user_btn()} + + + {m.ui_edit_user_btn()} + + + + ); }; diff --git a/src/components/user/set-password-dialog.tsx b/src/components/user/set-password-dialog.tsx index b7aa4eb..d4c618e 100644 --- a/src/components/user/set-password-dialog.tsx +++ b/src/components/user/set-password-dialog.tsx @@ -43,23 +43,23 @@ const SetPasswordAction = ({ data }: UpdatePasswordProps) => { - e.preventDefault()} - > - - - - {m.ui_update_password_btn()} - - - {m.ui_update_password_btn()} - - - - + e.preventDefault()} + > + + + + {m.ui_update_password_btn()} + + + {m.ui_update_password_btn()} + + + + ); }; diff --git a/src/components/user/unban-user-dialog.tsx b/src/components/user/unban-user-dialog.tsx new file mode 100644 index 0000000..1ce57b4 --- /dev/null +++ b/src/components/user/unban-user-dialog.tsx @@ -0,0 +1,119 @@ +import usePreventAutoFocus from '@/hooks/use-prevent-auto-focus'; +import { m } from '@/paraglide/messages'; +import { usersQueries } from '@/service/queries'; +import { unbanUser } from '@/service/user.api'; +import { ReturnError } from '@/types/common'; +import { LockOpenIcon, ShieldWarningIcon } from '@phosphor-icons/react'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { UserWithRole } from 'better-auth/plugins'; +import { useState } from 'react'; +import { toast } from 'sonner'; +import DisplayBreakLineMessage from '../DisplayBreakLineMessage'; +import { Button } from '../ui/button'; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '../ui/dialog'; +import { Label } from '../ui/label'; +import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; + +type UnbanUserProps = { + data: UserWithRole; +}; + +const UnbanUserAction = ({ data }: UnbanUserProps) => { + const queryClient = useQueryClient(); + + const [_open, _setOpen] = useState(false); + const prevent = usePreventAutoFocus(); + + const { mutate: unbanMutation } = useMutation({ + mutationFn: unbanUser, + onSuccess: () => { + queryClient.refetchQueries({ + queryKey: usersQueries.all, + }); + _setOpen(false); + toast.success( + m.users_page_message_unbanned_success({ name: data.name }), + { + richColors: true, + }, + ); + }, + onError: (error: ReturnError) => { + console.error(error); + toast.error(m.backend_message({ code: error.code }), { + richColors: true, + }); + }, + }); + + const onConfirm = () => { + unbanMutation({ data: { id: data.id } }); + }; + + return ( + + + + + + + + + + + + e.preventDefault()} + > + + +
+ +
+ {m.users_page_ui_dialog_alert_title()} +
+ + {m.users_page_ui_dialog_alert_title()} + +
+ + {m.users_page_ui_dialog_alert_description({ + name: data.name, + email: data.email, + })} + + + + + + + +
+
+ ); +}; + +export default UnbanUserAction; diff --git a/src/components/user/user-column.tsx b/src/components/user/user-column.tsx index 362f022..9e796c4 100644 --- a/src/components/user/user-column.tsx +++ b/src/components/user/user-column.tsx @@ -4,10 +4,11 @@ import { CheckCircleIcon, XCircleIcon } from '@phosphor-icons/react'; import { ColumnDef } from '@tanstack/react-table'; import { UserWithRole } from 'better-auth/plugins'; import RoleBadge from '../avatar/role-badge'; +import BanUserAction from './ban-user-dialog'; import ChangeRoleAction from './change-role-dialog'; -import ChangeUserStatusAction from './change-user-status-dialog'; import EditUserAction from './edit-user-dialog'; import SetPasswordAction from './set-password-dialog'; +import UnbanUserAction from './unban-user-dialog'; export const userColumns: ColumnDef[] = [ { @@ -66,7 +67,11 @@ export const userColumns: ColumnDef[] = [ - + {row.original.banned ? ( + + ) : ( + + )} ); }, diff --git a/src/service/user.api.ts b/src/service/user.api.ts index a2c722c..044cd6f 100644 --- a/src/service/user.api.ts +++ b/src/service/user.api.ts @@ -4,6 +4,7 @@ import { parseError } from '@/utils/helper'; import { createServerFn } from '@tanstack/react-start'; import { getRequestHeaders } from '@tanstack/react-start/server'; import { + baseUser, userBanSchema, userListSchema, userSetPasswordSchema, @@ -131,3 +132,23 @@ export const banUser = createServerFn({ method: 'POST' }) throw { message, code }; } }); + +export const unbanUser = createServerFn({ method: 'POST' }) + .middleware([authMiddleware]) + .inputValidator(baseUser) + .handler(async ({ data }) => { + try { + const headers = getRequestHeaders(); + const result = await auth.api.unbanUser({ + body: { + userId: data.id, // required + }, + // This endpoint requires session cookies. + headers, + }); + return result; + } catch (error) { + const { message, code } = parseError(error); + throw { message, code }; + } + }); diff --git a/src/service/user.schema.ts b/src/service/user.schema.ts index f1000f7..928a405 100644 --- a/src/service/user.schema.ts +++ b/src/service/user.schema.ts @@ -1,14 +1,17 @@ import { m } from '@/paraglide/messages'; import z from 'zod'; +export const baseUser = z.object({ + id: z.string().nonempty(m.users_page_message_user_not_found()), +}); + export const userListSchema = z.object({ page: z.coerce.number().min(1).default(1), limit: z.coerce.number().min(10).max(100).default(10), keyword: z.string().optional(), }); -export const userSetPasswordSchema = z.object({ - id: z.string().nonempty(m.users_page_message_user_not_found()), +export const userSetPasswordSchema = baseUser.extend({ password: z .string() .min(5, m.users_page_message_user_min()) @@ -18,8 +21,7 @@ export const userSetPasswordSchema = z.object({ .regex(/[^a-zA-Z0-9]/, m.users_page_message_contain_special()), }); -export const userUpdateInfoSchema = z.object({ - id: z.string().nonempty(m.users_page_message_user_not_found()), +export const userUpdateInfoSchema = baseUser.extend({ name: z.string().nonempty( m.common_is_required({ field: m.profile_form_name(), @@ -29,13 +31,11 @@ export const userUpdateInfoSchema = z.object({ export const RoleEnum = z.enum(['admin', 'user']); -export const userUpdateRoleSchema = z.object({ - id: z.string().nonempty(m.users_page_message_user_not_found()), +export const userUpdateRoleSchema = baseUser.extend({ role: RoleEnum, }); -export const userBanSchema = z.object({ - id: z.string().nonempty(m.users_page_message_user_not_found()), +export const userBanSchema = baseUser.extend({ banReason: z.string().nonempty( m.common_is_required({ field: m.users_page_ui_form_ban_reason(),