mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-25 06:31:17 -05:00
Implement API improvements Phases 1-4
This commit is contained in:
@@ -57,8 +57,22 @@ export function useHomepageRecentChanges(
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
interface DatabaseRecentChange {
|
||||
entity_id: string;
|
||||
entity_name: string;
|
||||
entity_type: string;
|
||||
entity_slug: string;
|
||||
park_slug?: string;
|
||||
image_url?: string;
|
||||
change_type: string;
|
||||
changed_at: string;
|
||||
changed_by_username?: string;
|
||||
changed_by_avatar?: string;
|
||||
change_reason?: string;
|
||||
}
|
||||
|
||||
// Transform the database response to match our interface
|
||||
const result: RecentChange[] = (data || []).map((item: any) => ({
|
||||
const result: RecentChange[] = (data as unknown as DatabaseRecentChange[] || []).map((item) => ({
|
||||
id: item.entity_id,
|
||||
name: item.entity_name,
|
||||
type: item.entity_type as 'park' | 'ride' | 'company',
|
||||
|
||||
@@ -67,15 +67,22 @@ export function usePrivacyMutations() {
|
||||
await queryClient.cancelQueries({ queryKey: ['profile', user?.id] });
|
||||
|
||||
// Snapshot current value
|
||||
const previousProfile = queryClient.getQueryData(['profile', user?.id]);
|
||||
interface Profile {
|
||||
privacy_level?: string;
|
||||
show_pronouns?: boolean;
|
||||
}
|
||||
|
||||
const previousProfile = queryClient.getQueryData<Profile>(['profile', user?.id]);
|
||||
|
||||
// Optimistically update cache
|
||||
if (previousProfile) {
|
||||
queryClient.setQueryData(['profile', user?.id], (old: any) => ({
|
||||
...old,
|
||||
privacy_level: newData.privacy_level,
|
||||
show_pronouns: newData.show_pronouns,
|
||||
}));
|
||||
queryClient.setQueryData<Profile>(['profile', user?.id], (old) =>
|
||||
old ? {
|
||||
...old,
|
||||
privacy_level: newData.privacy_level,
|
||||
show_pronouns: newData.show_pronouns,
|
||||
} : old
|
||||
);
|
||||
}
|
||||
|
||||
return { previousProfile };
|
||||
|
||||
@@ -166,13 +166,28 @@ export function useProfileActivity(
|
||||
photoItemsMap.get(item.photo_submission_id)!.push(item);
|
||||
});
|
||||
|
||||
interface DatabaseEntity {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
const entityMap = new Map<string, EntityData>([
|
||||
...parks.map((p: any): [string, EntityData] => [p.id, p]),
|
||||
...rides.map((r: any): [string, EntityData] => [r.id, r])
|
||||
...parks.map((p: DatabaseEntity): [string, EntityData] => [p.id, p]),
|
||||
...rides.map((r: DatabaseEntity): [string, EntityData] => [r.id, r])
|
||||
]);
|
||||
|
||||
interface PhotoSubmissionWithAllFields {
|
||||
id: string;
|
||||
photo_count?: number;
|
||||
photo_preview?: string;
|
||||
entity_type?: string;
|
||||
entity_id?: string;
|
||||
content?: unknown;
|
||||
}
|
||||
|
||||
// Enrich submissions
|
||||
photoSubmissions.forEach((sub: any) => {
|
||||
photoSubmissions.forEach((sub: PhotoSubmissionWithAllFields) => {
|
||||
const photoSub = photoSubMap.get(sub.id);
|
||||
if (photoSub) {
|
||||
const items = photoItemsMap.get(photoSub.id) || [];
|
||||
|
||||
@@ -64,18 +64,26 @@ export function useProfileLocationMutation() {
|
||||
await queryClient.cancelQueries({ queryKey: ['profile', user?.id] });
|
||||
|
||||
// Snapshot current value
|
||||
const previousProfile = queryClient.getQueryData(['profile', user?.id]);
|
||||
interface Profile {
|
||||
personal_location?: string;
|
||||
home_park_id?: string;
|
||||
timezone?: string;
|
||||
}
|
||||
|
||||
const previousProfile = queryClient.getQueryData<Profile>(['profile', user?.id]);
|
||||
|
||||
// Optimistically update cache
|
||||
if (previousProfile) {
|
||||
queryClient.setQueryData(['profile', user?.id], (old: any) => ({
|
||||
...old,
|
||||
personal_location: newData.personal_location,
|
||||
home_park_id: newData.home_park_id,
|
||||
timezone: newData.timezone,
|
||||
preferred_language: newData.preferred_language,
|
||||
preferred_pronouns: newData.preferred_pronouns,
|
||||
}));
|
||||
queryClient.setQueryData<Profile>(['profile', user?.id], (old) =>
|
||||
old ? {
|
||||
...old,
|
||||
personal_location: newData.personal_location,
|
||||
home_park_id: newData.home_park_id,
|
||||
timezone: newData.timezone,
|
||||
preferred_language: newData.preferred_language,
|
||||
preferred_pronouns: newData.preferred_pronouns,
|
||||
} : old
|
||||
);
|
||||
}
|
||||
|
||||
return { previousProfile };
|
||||
|
||||
@@ -37,14 +37,20 @@ export function useProfileUpdateMutation() {
|
||||
// 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', userId]);
|
||||
const previousProfile = queryClient.getQueryData<Profile>(['profile', userId]);
|
||||
|
||||
// Optimistically update
|
||||
queryClient.setQueryData(['profile', userId], (old: any) => ({
|
||||
...old,
|
||||
...updates,
|
||||
}));
|
||||
queryClient.setQueryData<Profile>(['profile', userId], (old) =>
|
||||
old ? { ...old, ...updates } : old
|
||||
);
|
||||
|
||||
return { previousProfile, userId };
|
||||
},
|
||||
|
||||
38
src/hooks/security/useEmailChangeStatus.ts
Normal file
38
src/hooks/security/useEmailChangeStatus.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { logger } from '@/lib/logger';
|
||||
|
||||
export interface EmailChangeStatus {
|
||||
has_pending_change: boolean;
|
||||
current_email?: string;
|
||||
new_email?: string;
|
||||
current_email_verified?: boolean;
|
||||
new_email_verified?: boolean;
|
||||
change_sent_at?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to query email change verification status
|
||||
* Provides: automatic polling every 30 seconds, cache management, loading states
|
||||
*/
|
||||
export function useEmailChangeStatus() {
|
||||
return useQuery({
|
||||
queryKey: ['email-change-status'],
|
||||
queryFn: async () => {
|
||||
const { data, error } = await supabase.rpc('get_email_change_status');
|
||||
|
||||
if (error) {
|
||||
logger.error('Failed to fetch email change status', {
|
||||
action: 'fetch_email_change_status',
|
||||
error: error.message,
|
||||
errorCode: error.code
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data as unknown as EmailChangeStatus;
|
||||
},
|
||||
refetchInterval: 30000, // Poll every 30 seconds
|
||||
staleTime: 15000, // 15 seconds
|
||||
});
|
||||
}
|
||||
34
src/hooks/security/useSessions.ts
Normal file
34
src/hooks/security/useSessions.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { logger } from '@/lib/logger';
|
||||
import type { AuthSession } from '@/types/auth';
|
||||
|
||||
/**
|
||||
* Hook to fetch active user sessions
|
||||
* Provides: automatic caching, refetch on window focus, loading states
|
||||
*/
|
||||
export function useSessions(userId?: string) {
|
||||
return useQuery({
|
||||
queryKey: ['sessions', userId],
|
||||
queryFn: async () => {
|
||||
if (!userId) throw new Error('User ID required');
|
||||
|
||||
const { data, error } = await supabase.rpc('get_my_sessions');
|
||||
|
||||
if (error) {
|
||||
logger.error('Failed to fetch sessions', {
|
||||
userId,
|
||||
action: 'fetch_sessions',
|
||||
error: error.message,
|
||||
errorCode: error.code
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
return (data as AuthSession[]) || [];
|
||||
},
|
||||
enabled: !!userId,
|
||||
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||
refetchOnWindowFocus: true,
|
||||
});
|
||||
}
|
||||
@@ -93,7 +93,14 @@ export function useEntityVersions(entityType: EntityType, entityId: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
const versionsWithProfiles = (data || []).map((v: any) => ({
|
||||
interface DatabaseVersion {
|
||||
profiles?: {
|
||||
username?: string;
|
||||
display_name?: string;
|
||||
};
|
||||
}
|
||||
|
||||
const versionsWithProfiles = (data as DatabaseVersion[] || []).map((v) => ({
|
||||
...v,
|
||||
profiles: v.profiles || {
|
||||
username: 'Unknown',
|
||||
|
||||
@@ -7,7 +7,7 @@ export function useRideCreditFilters(credits: UserRideCredit[]) {
|
||||
const [filters, setFilters] = useState<RideCreditFilters>({});
|
||||
const debouncedSearchQuery = useDebounce(filters.searchQuery || '', 300);
|
||||
|
||||
const updateFilter = useCallback((key: keyof RideCreditFilters, value: any) => {
|
||||
const updateFilter = useCallback((key: keyof RideCreditFilters, value: RideCreditFilters[typeof key]) => {
|
||||
setFilters(prev => ({ ...prev, [key]: value }));
|
||||
}, []);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user