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) => {
-
-
+
{m.ui_ban_btn()}
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 (
+
+ );
+};
+
+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 (
+
+
+
+
+ );
+};
+
+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 (
-
- );
-};
-
-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 (
+
+ );
+};
+
+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(),