mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 02:11:14 -05:00
Refactor UserRoleManager mutations
This commit is contained in:
144
src/hooks/users/useRoleMutations.ts
Normal file
144
src/hooks/users/useRoleMutations.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { queryKeys } from '@/lib/queryKeys';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
type ValidRole = 'admin' | 'moderator' | 'user';
|
||||
|
||||
interface GrantRoleParams {
|
||||
userId: string;
|
||||
role: ValidRole;
|
||||
}
|
||||
|
||||
interface RevokeRoleParams {
|
||||
roleId: string;
|
||||
}
|
||||
|
||||
interface UserWithRoles {
|
||||
id: string;
|
||||
user_id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
display_name: string | null;
|
||||
avatar_url: string | null;
|
||||
banned: boolean;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* useRoleMutations Hook
|
||||
*
|
||||
* Provides TanStack Query mutations for granting and revoking user roles
|
||||
* with optimistic updates for instant UI feedback.
|
||||
*
|
||||
* Features:
|
||||
* - Optimistic updates for immediate UI response
|
||||
* - Automatic cache invalidation on success
|
||||
* - Error handling with rollback
|
||||
* - Toast notifications
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { grantRole, revokeRole } = useRoleMutations();
|
||||
*
|
||||
* grantRole.mutate({ userId: 'user-id', role: 'moderator' });
|
||||
* revokeRole.mutate({ roleId: 'role-id' });
|
||||
* ```
|
||||
*/
|
||||
export function useRoleMutations() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const grantRole = useMutation({
|
||||
mutationFn: async ({ userId, role }: GrantRoleParams) => {
|
||||
const { data, error } = await supabase
|
||||
.from('user_roles')
|
||||
.insert([{ user_id: userId, role }])
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) throw error;
|
||||
return data;
|
||||
},
|
||||
onMutate: async ({ userId, role }) => {
|
||||
// Cancel outgoing refetches
|
||||
await queryClient.cancelQueries({ queryKey: queryKeys.users.roles() });
|
||||
|
||||
// Snapshot previous value
|
||||
const previousUsers = queryClient.getQueryData<UserWithRoles[]>(queryKeys.users.roles());
|
||||
|
||||
// Optimistically update cache - add role to user
|
||||
queryClient.setQueryData<UserWithRoles[]>(queryKeys.users.roles(), (old) => {
|
||||
if (!old) return old;
|
||||
return old.map((user) =>
|
||||
user.user_id === userId
|
||||
? { ...user, role } // Optimistically assign role
|
||||
: user
|
||||
);
|
||||
});
|
||||
|
||||
return { previousUsers };
|
||||
},
|
||||
onError: (error, variables, context) => {
|
||||
// Rollback on error
|
||||
if (context?.previousUsers) {
|
||||
queryClient.setQueryData(queryKeys.users.roles(), context.previousUsers);
|
||||
}
|
||||
toast.error(`Failed to grant role: ${error.message}`);
|
||||
},
|
||||
onSuccess: (data, { role }) => {
|
||||
toast.success(`Role ${role} granted successfully`);
|
||||
},
|
||||
onSettled: () => {
|
||||
// Refetch to ensure consistency
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.users.roles() });
|
||||
queryClient.invalidateQueries({ queryKey: ['user-roles'] });
|
||||
},
|
||||
});
|
||||
|
||||
const revokeRole = useMutation({
|
||||
mutationFn: async ({ roleId }: RevokeRoleParams) => {
|
||||
const { error } = await supabase
|
||||
.from('user_roles')
|
||||
.delete()
|
||||
.eq('id', roleId);
|
||||
|
||||
if (error) throw error;
|
||||
},
|
||||
onMutate: async ({ roleId }) => {
|
||||
// Cancel outgoing refetches
|
||||
await queryClient.cancelQueries({ queryKey: queryKeys.users.roles() });
|
||||
|
||||
// Snapshot previous value
|
||||
const previousUsers = queryClient.getQueryData<UserWithRoles[]>(queryKeys.users.roles());
|
||||
|
||||
// Optimistically remove role from cache
|
||||
queryClient.setQueryData<UserWithRoles[]>(queryKeys.users.roles(), (old) => {
|
||||
if (!old) return old;
|
||||
// Remove the user from the list since they no longer have a role
|
||||
return old.filter((user) => user.id !== roleId);
|
||||
});
|
||||
|
||||
return { previousUsers };
|
||||
},
|
||||
onError: (error, variables, context) => {
|
||||
// Rollback on error
|
||||
if (context?.previousUsers) {
|
||||
queryClient.setQueryData(queryKeys.users.roles(), context.previousUsers);
|
||||
}
|
||||
toast.error(`Failed to revoke role: ${error.message}`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success('Role revoked successfully');
|
||||
},
|
||||
onSettled: () => {
|
||||
// Refetch to ensure consistency
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.users.roles() });
|
||||
queryClient.invalidateQueries({ queryKey: ['user-roles'] });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
grantRole,
|
||||
revokeRole,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user