From 468d55d291ebb6ede077a191bbac136fc27eb31f Mon Sep 17 00:00:00 2001 From: Sam Date: Sun, 1 Feb 2026 17:12:59 +0700 Subject: [PATCH] add permission --- .../form/admin-set-password-form.tsx | 5 +- src/components/user/ban-user-dialog.tsx | 108 ++++++++-------- src/components/user/change-role-dialog.tsx | 80 ++++++------ src/components/user/edit-user-dialog.tsx | 82 ++++++------ src/components/user/set-password-dialog.tsx | 82 ++++++------ src/components/user/unban-user-dialog.tsx | 119 ++++++++++-------- src/hooks/use-has-permission.ts | 29 +++++ 7 files changed, 289 insertions(+), 216 deletions(-) create mode 100644 src/hooks/use-has-permission.ts diff --git a/src/components/form/admin-set-password-form.tsx b/src/components/form/admin-set-password-form.tsx index a0a3f4c..3dfb475 100644 --- a/src/components/form/admin-set-password-form.tsx +++ b/src/components/form/admin-set-password-form.tsx @@ -66,7 +66,10 @@ const AdminSetPasswordForm = ({ data, onSubmit }: FormProps) => { {(field) => ( - + )} diff --git a/src/components/user/ban-user-dialog.tsx b/src/components/user/ban-user-dialog.tsx index bde71b7..90b91b0 100644 --- a/src/components/user/ban-user-dialog.tsx +++ b/src/components/user/ban-user-dialog.tsx @@ -1,6 +1,8 @@ +import useHasPermission from '@/hooks/use-has-permission'; import usePreventAutoFocus from '@/hooks/use-prevent-auto-focus'; import { m } from '@/paraglide/messages'; import { LockIcon } from '@phosphor-icons/react'; +import { useRouteContext } from '@tanstack/react-router'; import { UserWithRole } from 'better-auth/plugins'; import { createContext, useContext, useState } from 'react'; import BanUserForm from '../form/admin-ban-user-form'; @@ -39,6 +41,9 @@ type BanContextProps = { const BanContext = createContext(null); const BanUserAction = ({ data }: ChangeUserStatusProps) => { + const { session } = useRouteContext({ from: '__root__' }); + const isCurrentUser = session?.user.id === data.id; + const { hasPermission, isLoading } = useHasPermission('user', 'ban'); const [_open, _setOpen] = useState(false); const [_openConfirm, _setOpenConfirm] = useState(false); const [_confirmData, _setConfirmData] = useState({ @@ -48,56 +53,61 @@ const BanUserAction = ({ data }: ChangeUserStatusProps) => { }); const prevent = usePreventAutoFocus(); - return ( - - - - - - + + + + + + + e.preventDefault()} + > + + - {m.ui_ban_btn()} - - - - - - - - e.preventDefault()} - > - - - - {m.ui_ban_btn()} - - - {m.ui_change_role_btn()} - - - - - - - - ); + {m.ui_ban_btn()} + + + {m.ui_change_role_btn()} + + + + + + + + ); + } + return null; }; export default BanUserAction; diff --git a/src/components/user/change-role-dialog.tsx b/src/components/user/change-role-dialog.tsx index 374fa11..72b80ad 100644 --- a/src/components/user/change-role-dialog.tsx +++ b/src/components/user/change-role-dialog.tsx @@ -1,3 +1,4 @@ +import useHasPermission from '@/hooks/use-has-permission'; import usePreventAutoFocus from '@/hooks/use-prevent-auto-focus'; import { m } from '@/paraglide/messages'; import { UserGearIcon } from '@phosphor-icons/react'; @@ -23,45 +24,50 @@ type SetRoleProps = { const ChangeRoleAction = ({ data }: SetRoleProps) => { const [_open, _setOpen] = useState(false); const prevent = usePreventAutoFocus(); + const { hasPermission, isLoading } = useHasPermission('user', 'set-role'); - return ( - - - - - + + + + + + + e.preventDefault()} + > + + - {m.ui_change_role_btn()} - - - - - - - - e.preventDefault()} - > - - - - {m.ui_change_role_btn()} - - - {m.ui_change_role_btn()} - - - - - - ); + {m.ui_change_role_btn()} + + + {m.ui_change_role_btn()} + + + + + + ); + } }; export default ChangeRoleAction; diff --git a/src/components/user/edit-user-dialog.tsx b/src/components/user/edit-user-dialog.tsx index b44abd3..1f03e0f 100644 --- a/src/components/user/edit-user-dialog.tsx +++ b/src/components/user/edit-user-dialog.tsx @@ -1,3 +1,4 @@ +import useHasPermission from '@/hooks/use-has-permission'; import usePreventAutoFocus from '@/hooks/use-prevent-auto-focus'; import { m } from '@/paraglide/messages'; import { PenIcon } from '@phosphor-icons/react'; @@ -23,44 +24,51 @@ type EditUserProps = { const EditUserAction = ({ data }: EditUserProps) => { const [_open, _setOpen] = useState(false); const prevent = usePreventAutoFocus(); + const { hasPermission, isLoading } = useHasPermission('user', 'update'); - return ( - - - - - - - - - - - - e.preventDefault()} - > - - - {m.ui_edit_user_btn()} - - - {m.ui_edit_user_btn()} - - - - - - ); + if (isLoading) return null; + + if (hasPermission) { + return ( + + + + + + + + + + + + e.preventDefault()} + > + + + {m.ui_edit_user_btn()} + + + {m.ui_edit_user_btn()} + + + + + + ); + } + + return null; }; export default EditUserAction; diff --git a/src/components/user/set-password-dialog.tsx b/src/components/user/set-password-dialog.tsx index d4c618e..43ca1e6 100644 --- a/src/components/user/set-password-dialog.tsx +++ b/src/components/user/set-password-dialog.tsx @@ -1,3 +1,4 @@ +import useHasPermission from '@/hooks/use-has-permission'; import usePreventAutoFocus from '@/hooks/use-prevent-auto-focus'; import { m } from '@/paraglide/messages'; import { KeyIcon } from '@phosphor-icons/react'; @@ -23,45 +24,50 @@ type UpdatePasswordProps = { const SetPasswordAction = ({ data }: UpdatePasswordProps) => { const [_open, _setOpen] = useState(false); const prevent = usePreventAutoFocus(); + const { hasPermission, isLoading } = useHasPermission('user', 'set-password'); - return ( - - - - - - - - - - - - e.preventDefault()} - > - - - - {m.ui_update_password_btn()} - - - {m.ui_update_password_btn()} - - - - - - ); + if (isLoading) return null; + + if (hasPermission) { + return ( + + + + + + + + + + + + e.preventDefault()} + > + + + + {m.ui_update_password_btn()} + + + {m.ui_update_password_btn()} + + + + + + ); + } }; export default SetPasswordAction; diff --git a/src/components/user/unban-user-dialog.tsx b/src/components/user/unban-user-dialog.tsx index 1ce57b4..6e9a895 100644 --- a/src/components/user/unban-user-dialog.tsx +++ b/src/components/user/unban-user-dialog.tsx @@ -1,3 +1,4 @@ +import useHasPermission from '@/hooks/use-has-permission'; import usePreventAutoFocus from '@/hooks/use-prevent-auto-focus'; import { m } from '@/paraglide/messages'; import { usersQueries } from '@/service/queries'; @@ -5,6 +6,7 @@ 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 { useRouteContext } from '@tanstack/react-router'; import { UserWithRole } from 'better-auth/plugins'; import { useState } from 'react'; import { toast } from 'sonner'; @@ -28,6 +30,9 @@ type UnbanUserProps = { }; const UnbanUserAction = ({ data }: UnbanUserProps) => { + const { session } = useRouteContext({ from: '__root__' }); + const isCurrentUser = session?.user.id === data.id; + const { hasPermission, isLoading } = useHasPermission('user', 'ban'); const queryClient = useQueryClient(); const [_open, _setOpen] = useState(false); @@ -59,61 +64,67 @@ const UnbanUserAction = ({ data }: UnbanUserProps) => { 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, + })} + + + + + + - - - - - - - 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, - })} - - - - - - - -
-
- ); + + + + ); + } + + return null; }; export default UnbanUserAction; diff --git a/src/hooks/use-has-permission.ts b/src/hooks/use-has-permission.ts new file mode 100644 index 0000000..3a977a2 --- /dev/null +++ b/src/hooks/use-has-permission.ts @@ -0,0 +1,29 @@ +import { authClient } from '@/lib/auth-client'; +import { useEffect, useState } from 'react'; + +function useHasPermission(resource: string, action: string) { + const [hasPermission, setHasPermission] = useState(false); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const checkPermission = async () => { + try { + const access = await authClient.admin.hasPermission({ + permissions: { + [resource]: [action], + }, + }); + setHasPermission(access.data?.success ?? false); + } catch (error) { + console.error('Permission check failed:', error); + } finally { + setIsLoading(false); + } + }; + checkPermission(); + }, [resource, action]); + + return { hasPermission, isLoading }; +} + +export default useHasPermission; -- 2.49.1