mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 06:11:11 -05:00
145 lines
4.1 KiB
TypeScript
145 lines
4.1 KiB
TypeScript
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,
|
|
};
|
|
}
|