mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 16:11:12 -05:00
feat: Implement complete API optimization plan
This commit is contained in:
@@ -1,52 +1,72 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { logger } from '@/lib/logger';
|
||||
import type { UserBlock } from '@/types/privacy';
|
||||
|
||||
/**
|
||||
* Hook for querying blocked users
|
||||
* Provides: list of blocked users with profile information
|
||||
* Hook to fetch blocked users for the authenticated user
|
||||
* Provides: automatic caching, refetch on window focus, and loading states
|
||||
*/
|
||||
export function useBlockedUsers() {
|
||||
const { user } = useAuth();
|
||||
|
||||
export function useBlockedUsers(userId?: string) {
|
||||
return useQuery({
|
||||
queryKey: ['blocked-users', user?.id],
|
||||
queryKey: ['blocked-users', userId],
|
||||
queryFn: async () => {
|
||||
if (!user) return [];
|
||||
if (!userId) throw new Error('User ID required');
|
||||
|
||||
// First get the blocked user IDs
|
||||
// Fetch blocked user IDs
|
||||
const { data: blocks, error: blocksError } = await supabase
|
||||
.from('user_blocks')
|
||||
.select('id, blocked_id, reason, created_at')
|
||||
.eq('blocker_id', user.id)
|
||||
.eq('blocker_id', userId)
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (blocksError) throw blocksError;
|
||||
if (blocksError) {
|
||||
logger.error('Failed to fetch user blocks', {
|
||||
userId,
|
||||
action: 'fetch_blocked_users',
|
||||
error: blocksError.message,
|
||||
errorCode: blocksError.code
|
||||
});
|
||||
throw blocksError;
|
||||
}
|
||||
|
||||
if (!blocks || blocks.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Then get the profile information for blocked users
|
||||
// Fetch profile information for blocked users
|
||||
const blockedIds = blocks.map(b => b.blocked_id);
|
||||
const { data: profiles, error: profilesError } = await supabase
|
||||
.from('profiles')
|
||||
.select('user_id, username, display_name, avatar_url')
|
||||
.in('user_id', blockedIds);
|
||||
|
||||
if (profilesError) throw profilesError;
|
||||
if (profilesError) {
|
||||
logger.error('Failed to fetch blocked user profiles', {
|
||||
userId,
|
||||
action: 'fetch_blocked_user_profiles',
|
||||
error: profilesError.message,
|
||||
errorCode: profilesError.code
|
||||
});
|
||||
throw profilesError;
|
||||
}
|
||||
|
||||
// Combine the data
|
||||
const blockedUsersWithProfiles: UserBlock[] = blocks.map(block => ({
|
||||
...block,
|
||||
blocker_id: user.id,
|
||||
blocker_id: userId,
|
||||
blocked_profile: profiles?.find(p => p.user_id === block.blocked_id)
|
||||
}));
|
||||
|
||||
logger.info('Blocked users fetched successfully', {
|
||||
userId,
|
||||
action: 'fetch_blocked_users',
|
||||
count: blockedUsersWithProfiles.length
|
||||
});
|
||||
|
||||
return blockedUsersWithProfiles;
|
||||
},
|
||||
enabled: !!user,
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
enabled: !!userId,
|
||||
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||
});
|
||||
}
|
||||
|
||||
50
src/hooks/rides/useRideCreditsMutation.ts
Normal file
50
src/hooks/rides/useRideCreditsMutation.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
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 ReorderCreditParams {
|
||||
creditId: string;
|
||||
newPosition: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for ride credits mutations
|
||||
* Provides: reorder ride credits with automatic cache invalidation
|
||||
*/
|
||||
export function useRideCreditsMutation() {
|
||||
const queryClient = useQueryClient();
|
||||
const { invalidateRideDetail } = useQueryInvalidation();
|
||||
|
||||
const reorderCredit = useMutation({
|
||||
mutationFn: async ({ creditId, newPosition }: ReorderCreditParams) => {
|
||||
const { error } = await supabase.rpc('reorder_ride_credit', {
|
||||
p_credit_id: creditId,
|
||||
p_new_position: newPosition
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
return { creditId, newPosition };
|
||||
},
|
||||
onError: (error: unknown) => {
|
||||
toast.error("Reorder Failed", {
|
||||
description: getErrorMessage(error),
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
// Invalidate ride credits queries
|
||||
queryClient.invalidateQueries({ queryKey: ['ride-credits'] });
|
||||
|
||||
toast.success("Order Updated", {
|
||||
description: "Ride credit order has been saved.",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
reorderCredit,
|
||||
isReordering: reorderCredit.isPending,
|
||||
};
|
||||
}
|
||||
83
src/hooks/security/useEmailChangeMutation.ts
Normal file
83
src/hooks/security/useEmailChangeMutation.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { toast } from 'sonner';
|
||||
import { getErrorMessage } from '@/lib/errorHandler';
|
||||
import { useQueryInvalidation } from '@/lib/queryInvalidation';
|
||||
import { notificationService } from '@/lib/notificationService';
|
||||
import { logger } from '@/lib/logger';
|
||||
|
||||
interface EmailChangeParams {
|
||||
newEmail: string;
|
||||
currentEmail: string;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for email change mutations
|
||||
* Provides: email changes with automatic audit logging and cache invalidation
|
||||
*/
|
||||
export function useEmailChangeMutation() {
|
||||
const { invalidateAuditLogs } = useQueryInvalidation();
|
||||
|
||||
const changeEmail = useMutation({
|
||||
mutationFn: async ({ newEmail, currentEmail, userId }: EmailChangeParams) => {
|
||||
// Update email address
|
||||
const { error: updateError } = await supabase.auth.updateUser({
|
||||
email: newEmail
|
||||
});
|
||||
|
||||
if (updateError) throw updateError;
|
||||
|
||||
// Log the email change attempt
|
||||
await supabase.from('admin_audit_log').insert({
|
||||
admin_user_id: userId,
|
||||
target_user_id: userId,
|
||||
action: 'email_change_initiated',
|
||||
details: {
|
||||
old_email: currentEmail,
|
||||
new_email: newEmail,
|
||||
timestamp: new Date().toISOString(),
|
||||
}
|
||||
});
|
||||
|
||||
// Send security notifications (non-blocking)
|
||||
if (notificationService.isEnabled()) {
|
||||
notificationService.trigger({
|
||||
workflowId: 'security-alert',
|
||||
subscriberId: userId,
|
||||
payload: {
|
||||
alert_type: 'email_change_initiated',
|
||||
old_email: currentEmail,
|
||||
new_email: newEmail,
|
||||
timestamp: new Date().toISOString(),
|
||||
}
|
||||
}).catch(error => {
|
||||
logger.error('Failed to send security notification', {
|
||||
userId,
|
||||
action: 'email_change_notification',
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return { newEmail };
|
||||
},
|
||||
onError: (error: unknown) => {
|
||||
toast.error("Update Failed", {
|
||||
description: getErrorMessage(error),
|
||||
});
|
||||
},
|
||||
onSuccess: (_data, { userId }) => {
|
||||
invalidateAuditLogs(userId);
|
||||
|
||||
toast.success("Email Change Initiated", {
|
||||
description: "Check both email addresses for confirmation links.",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
changeEmail,
|
||||
isChanging: changeEmail.isPending,
|
||||
};
|
||||
}
|
||||
87
src/hooks/security/usePasswordUpdateMutation.ts
Normal file
87
src/hooks/security/usePasswordUpdateMutation.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { toast } from 'sonner';
|
||||
import { getErrorMessage } from '@/lib/errorHandler';
|
||||
import { useQueryInvalidation } from '@/lib/queryInvalidation';
|
||||
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
||||
import { logger } from '@/lib/logger';
|
||||
|
||||
interface PasswordUpdateParams {
|
||||
password: string;
|
||||
hasMFA: boolean;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for password update mutations
|
||||
* Provides: password updates with automatic audit logging and cache invalidation
|
||||
*/
|
||||
export function usePasswordUpdateMutation() {
|
||||
const { invalidateAuditLogs } = useQueryInvalidation();
|
||||
|
||||
const updatePassword = useMutation({
|
||||
mutationFn: async ({ password, hasMFA, userId }: PasswordUpdateParams) => {
|
||||
// Update password
|
||||
const { error: updateError } = await supabase.auth.updateUser({
|
||||
password
|
||||
});
|
||||
|
||||
if (updateError) throw updateError;
|
||||
|
||||
// Log audit trail
|
||||
await supabase.from('admin_audit_log').insert({
|
||||
admin_user_id: userId,
|
||||
target_user_id: userId,
|
||||
action: 'password_changed',
|
||||
details: {
|
||||
timestamp: new Date().toISOString(),
|
||||
method: hasMFA ? 'password_with_mfa' : 'password_only',
|
||||
user_agent: navigator.userAgent
|
||||
}
|
||||
});
|
||||
|
||||
// Send security notification (non-blocking)
|
||||
try {
|
||||
await invokeWithTracking(
|
||||
'trigger-notification',
|
||||
{
|
||||
workflowId: 'security-alert',
|
||||
subscriberId: userId,
|
||||
payload: {
|
||||
alert_type: 'password_changed',
|
||||
timestamp: new Date().toISOString(),
|
||||
device: navigator.userAgent.split(' ')[0]
|
||||
}
|
||||
},
|
||||
userId
|
||||
);
|
||||
} catch (notifError) {
|
||||
logger.error('Failed to send password change notification', {
|
||||
userId,
|
||||
action: 'password_change_notification',
|
||||
error: getErrorMessage(notifError)
|
||||
});
|
||||
// Don't fail the password update if notification fails
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
},
|
||||
onError: (error: unknown) => {
|
||||
toast.error("Update Failed", {
|
||||
description: getErrorMessage(error),
|
||||
});
|
||||
},
|
||||
onSuccess: (_data, { userId }) => {
|
||||
invalidateAuditLogs(userId);
|
||||
|
||||
toast.success("Password Updated", {
|
||||
description: "Your password has been successfully changed.",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
updatePassword,
|
||||
isUpdating: updatePassword.isPending,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user