diff --git a/src/hooks/useUserRole.ts b/src/hooks/useUserRole.ts index 0d43f45f..2c85c06a 100644 --- a/src/hooks/useUserRole.ts +++ b/src/hooks/useUserRole.ts @@ -1,6 +1,7 @@ -import { useState, useEffect } from 'react'; +import { useQuery } from '@tanstack/react-query'; import { supabase } from '@/integrations/supabase/client'; import { useAuth } from '@/hooks/useAuth'; +import { queryKeys } from '@/lib/queryKeys'; export type UserRole = 'admin' | 'moderator' | 'user' | 'superuser'; @@ -15,53 +16,54 @@ export interface UserPermissions { export function useUserRole() { const { user } = useAuth(); - const [roles, setRoles] = useState([]); - const [permissions, setPermissions] = useState(null); - const [loading, setLoading] = useState(true); - useEffect(() => { - if (!user) { - setRoles([]); - setLoading(false); - return; - } - - const fetchRoles = async () => { - try { - // Fetch user roles - const { data: rolesData, error: rolesError } = await supabase - .from('user_roles') - .select('role') - .eq('user_id', user.id); - - if (rolesError) { - console.error('Error fetching user roles:', rolesError); - setRoles([]); - } else { - setRoles(rolesData?.map(r => r.role as UserRole) || []); - } - - // Fetch user permissions using the new function - const { data: permissionsData, error: permissionsError } = await supabase - .rpc('get_user_management_permissions', { _user_id: user.id }); - - if (permissionsError) { - console.error('Error fetching user permissions:', permissionsError); - setPermissions(null); - } else { - setPermissions(permissionsData as unknown as UserPermissions); - } - } catch (error: unknown) { + // Fetch user roles with TanStack Query for automatic caching + const rolesQuery = useQuery({ + queryKey: queryKeys.userRoles(user?.id), + queryFn: async () => { + if (!user) return []; + + const { data, error } = await supabase + .from('user_roles') + .select('role') + .eq('user_id', user.id); + + if (error) { console.error('Error fetching user roles:', error); - setRoles([]); - setPermissions(null); - } finally { - setLoading(false); + return []; } - }; + + return data?.map(r => r.role as UserRole) || []; + }, + enabled: !!user, + staleTime: 5 * 60 * 1000, // 5 minutes - roles don't change often + gcTime: 10 * 60 * 1000, // 10 minutes garbage collection + }); - fetchRoles(); - }, [user]); + // Fetch user permissions with TanStack Query for automatic caching + const permissionsQuery = useQuery({ + queryKey: queryKeys.userPermissions(user?.id), + queryFn: async () => { + if (!user) return null; + + const { data, error } = await supabase + .rpc('get_user_management_permissions', { _user_id: user.id }); + + if (error) { + console.error('Error fetching user permissions:', error); + return null; + } + + return data as unknown as UserPermissions; + }, + enabled: !!user, + staleTime: 5 * 60 * 1000, // 5 minutes - permissions don't change often + gcTime: 10 * 60 * 1000, // 10 minutes garbage collection + }); + + const roles = rolesQuery.data || []; + const permissions = permissionsQuery.data || null; + const loading = rolesQuery.isLoading || permissionsQuery.isLoading; const hasRole = (role: UserRole) => roles.includes(role); const isModerator = () => hasRole('admin') || hasRole('moderator') || hasRole('superuser'); diff --git a/src/lib/queryInvalidation.ts b/src/lib/queryInvalidation.ts new file mode 100644 index 00000000..7384e5db --- /dev/null +++ b/src/lib/queryInvalidation.ts @@ -0,0 +1,72 @@ +/** + * Query invalidation helpers for TanStack Query + * + * Use these helpers to invalidate cached queries when data changes. + * This ensures UI stays in sync with backend state. + */ + +import { useQueryClient } from '@tanstack/react-query'; +import { queryKeys } from './queryKeys'; + +/** + * Hook providing query invalidation helpers + */ +export function useQueryInvalidation() { + const queryClient = useQueryClient(); + + return { + /** + * Invalidate user roles cache + * Call this after assigning/revoking roles + */ + invalidateUserRoles: (userId?: string) => { + if (userId) { + queryClient.invalidateQueries({ queryKey: queryKeys.userRoles(userId) }); + } else { + queryClient.invalidateQueries({ queryKey: ['user-roles'] }); + } + }, + + /** + * Invalidate user permissions cache + * Call this after role changes that affect permissions + */ + invalidateUserPermissions: (userId?: string) => { + if (userId) { + queryClient.invalidateQueries({ queryKey: queryKeys.userPermissions(userId) }); + } else { + queryClient.invalidateQueries({ queryKey: ['user-permissions'] }); + } + }, + + /** + * Invalidate both roles and permissions for a user + * Use this as a convenience method after role updates + */ + invalidateUserAuth: (userId?: string) => { + if (userId) { + queryClient.invalidateQueries({ queryKey: queryKeys.userRoles(userId) }); + queryClient.invalidateQueries({ queryKey: queryKeys.userPermissions(userId) }); + } else { + queryClient.invalidateQueries({ queryKey: ['user-roles'] }); + queryClient.invalidateQueries({ queryKey: ['user-permissions'] }); + } + }, + + /** + * Invalidate moderation queue + * Call this after moderation actions + */ + invalidateModerationQueue: () => { + queryClient.invalidateQueries({ queryKey: ['moderation-queue'] }); + }, + + /** + * Invalidate moderation stats + * Call this after queue changes + */ + invalidateModerationStats: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.moderationStats() }); + }, + }; +} diff --git a/src/lib/queryKeys.ts b/src/lib/queryKeys.ts new file mode 100644 index 00000000..dd9618bd --- /dev/null +++ b/src/lib/queryKeys.ts @@ -0,0 +1,18 @@ +/** + * Centralized query key definitions for TanStack Query + * + * This ensures consistent query keys across the application + * and makes cache invalidation easier to manage. + */ + +export const queryKeys = { + // User-related queries + userRoles: (userId?: string) => ['user-roles', userId] as const, + userPermissions: (userId?: string) => ['user-permissions', userId] as const, + + // Moderation queue queries + moderationQueue: (config: Record) => ['moderation-queue', config] as const, + moderationStats: () => ['moderation-stats'] as const, + + // Add more query keys as needed +} as const;