From a66d65a835bd8969868618ee9f3b7b20ce5c339b Mon Sep 17 00:00:00 2001 From: guangyang1206 Date: Fri, 22 May 2026 12:08:00 +0800 Subject: [PATCH] refactor: extract shared hasPermission helper (MODSetter/SurfSense#1366) - Add canPerform() helper function to members-query.atoms.ts - Add usePermissionGate() hook for convenience - Update team-content.tsx to use canPerform() - Update roles-manager.tsx to use canPerform() - Eliminates duplicated permission check logic - Centralizes permission policy in one location Fixes #1366 --- .../[search_space_id]/team/team-content.tsx | 9 ++--- .../atoms/members/members-query.atoms.ts | 35 +++++++++++++++++++ .../components/settings/roles-manager.tsx | 5 ++- 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx index f003dde1b..17c1dd121 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx @@ -31,7 +31,7 @@ import { deleteMemberMutationAtom, updateMemberMutationAtom, } from "@/atoms/members/members-mutation.atoms"; -import { membersAtom, myAccessAtom } from "@/atoms/members/members-query.atoms"; +import { membersAtom, myAccessAtom, canPerform } from "@/atoms/members/members-query.atoms"; import { AlertDialog, AlertDialogAction, @@ -126,14 +126,9 @@ export function TeamContent({ searchSpaceId }: TeamContentProps) { const { data: access = null, isLoading: accessLoading } = useAtomValue(myAccessAtom); const hasPermission = useCallback( - (permission: string) => { - if (!access) return false; - if (access.is_owner) return true; - return access.permissions?.includes(permission) ?? false; - }, + (permission: string) => canPerform(access, permission), [access] ); - const { data: members = [], isLoading: membersLoading } = useAtomValue(membersAtom); const { mutateAsync: updateMember } = useAtomValue(updateMemberMutationAtom); diff --git a/surfsense_web/atoms/members/members-query.atoms.ts b/surfsense_web/atoms/members/members-query.atoms.ts index c08a7a337..f8e4b2cf6 100644 --- a/surfsense_web/atoms/members/members-query.atoms.ts +++ b/surfsense_web/atoms/members/members-query.atoms.ts @@ -39,3 +39,38 @@ export const myAccessAtom = atomWithQuery((get) => { }, }; }); + +/** + * Helper function to check if the current user has a specific permission. + * + * @param access - The access object from useAtomValue(myAccessAtom) + * @param permission - The permission string to check + * @returns boolean indicating if the user has the permission + * + * @example + * const access = useAtomValue(myAccessAtom); + * if (canPerform(access, 'manage_members')) { ... } + */ +export function canPerform( + access: { is_owner: boolean; permissions?: string[] } | null | undefined, + permission: string +): boolean { + if (!access) return false; + if (access.is_owner) return true; + return access.permissions?.includes(permission) ?? false; +} + +/** + * Hook wrapper for canPerform that reads from myAccessAtom internally. + * Use this if you want to avoid calling useAtomValue(myAccessAtom) separately. + * + * @param permission - The permission string to check + * @returns boolean indicating if the user has the permission + * + * @example + * const canManageMembers = usePermissionGate('manage_members'); + */ +export function usePermissionGate(permission: string): boolean { + const access = useAtomValue(myAccessAtom); + return canPerform(access, permission); +} diff --git a/surfsense_web/components/settings/roles-manager.tsx b/surfsense_web/components/settings/roles-manager.tsx index ee32f6e69..65f47bc43 100644 --- a/surfsense_web/components/settings/roles-manager.tsx +++ b/surfsense_web/components/settings/roles-manager.tsx @@ -26,7 +26,7 @@ import { } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; -import { myAccessAtom } from "@/atoms/members/members-query.atoms"; +import { myAccessAtom, canPerform } from "@/atoms/members/members-query.atoms"; import { permissionsAtom } from "@/atoms/permissions/permissions-query.atoms"; import { createRoleMutationAtom, @@ -257,6 +257,9 @@ export function RolesManager({ searchSpaceId }: { searchSpaceId: number }) { const { data: access = null } = useAtomValue(myAccessAtom); const hasPermission = useCallback( + (permission: string) => canPerform(access, permission), + [access] + ); (permission: string) => { if (!access) return false; if (access.is_owner) return true;