Files
thrilltrack-explorer/src/hooks/profile/useProfileUpdateMutation.ts
2025-10-31 12:53:45 +00:00

117 lines
3.2 KiB
TypeScript

import { useMutation, useQueryClient } from '@tanstack/react-query';
import { supabase } from '@/integrations/supabase/client';
import { toast } from 'sonner';
import { getErrorMessage } from '@/lib/errorHandler';
import { useQueryInvalidation } from '@/lib/queryInvalidation';
interface ProfileUpdateParams {
userId: string;
updates: {
display_name?: string;
bio?: string;
location_id?: string | null;
website?: string | null;
[key: string]: any;
};
}
/**
* Hook for profile update mutations
*
* Features:
* - Optimistic updates for instant UI feedback
* - Automatic rollback on error
* - Smart cache invalidation (profile, stats, activity)
* - Conditional search invalidation when name changes
* - Comprehensive error handling with toast notifications
*
* Modifies:
* - `profiles` table
*
* Cache Invalidation:
* - User profile data (`invalidateUserProfile`)
* - Profile stats (`invalidateProfileStats`)
* - Profile activity feed (`invalidateProfileActivity`)
* - User search results if name changed (`invalidateUserSearch`)
*
* @example
* ```tsx
* const mutation = useProfileUpdateMutation();
*
* mutation.mutate({
* userId: user.id,
* updates: {
* display_name: 'New Name',
* bio: 'Updated bio',
* website: 'https://example.com'
* }
* });
* ```
*/
export function useProfileUpdateMutation() {
const queryClient = useQueryClient();
const {
invalidateUserProfile,
invalidateProfileStats,
invalidateProfileActivity,
invalidateUserSearch
} = useQueryInvalidation();
return useMutation({
mutationFn: async ({ userId, updates }: ProfileUpdateParams) => {
const { error } = await supabase
.from('profiles')
.update(updates)
.eq('user_id', userId);
if (error) throw error;
},
onMutate: async ({ userId, updates }) => {
// Cancel outgoing refetches
await queryClient.cancelQueries({ queryKey: ['profile', userId] });
interface Profile {
display_name?: string;
bio?: string;
location_id?: string;
website?: string;
}
// Snapshot previous value
const previousProfile = queryClient.getQueryData<Profile>(['profile', userId]);
// Optimistically update
queryClient.setQueryData<Profile>(['profile', userId], (old) =>
old ? { ...old, ...updates } : old
);
return { previousProfile, userId };
},
onError: (error: unknown, _variables, context) => {
// Rollback on error
if (context?.previousProfile) {
queryClient.setQueryData(['profile', context.userId], context.previousProfile);
}
toast.error("Update Failed", {
description: getErrorMessage(error),
});
},
onSuccess: (_data, { userId, updates }) => {
// Invalidate all related caches
invalidateUserProfile(userId);
invalidateProfileStats(userId);
invalidateProfileActivity(userId);
// If display name or username changed, invalidate user search results
if (updates.display_name || updates.username) {
invalidateUserSearch();
}
toast.success("Profile Updated", {
description: "Your changes have been saved.",
});
},
});
}