Making progress on rewrite. Recreating queries and hooks now.

This commit is contained in:
2025-06-24 15:56:44 -05:00
parent 13cf089870
commit fbb24185df
13 changed files with 840 additions and 34 deletions

View File

@@ -1,4 +1,5 @@
export { AuthContextProvider, useAuth } from './use-auth';
export { useIsMobile } from './use-mobile';
export { ReactQueryClientProvider } from './use-query';
export { QueryClientProvider, QueryErrorCodes } from './use-query';
export { ThemeProvider, ThemeToggle } from './use-theme';
export { TVModeProvider, useTVMode, TVToggle } from './use-tv-mode';

View File

@@ -1,11 +1,146 @@
'use client';
import React, {
type ReactNode,
createContext,
useCallback,
useContext,
useEffect,
useRef,
useState,
} from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useQuery as useSupabaseQuery } from '@supabase-cache-helpers/postgrest-react-query';
import { QueryErrorCodes } from '@/lib/hooks/context';
import { type User, type Profile, useSupabaseClient } from '@/utils/supabase';
import { toast } from 'sonner';
import {
getAvatar,
getCurrentUser,
getProfile,
updateProfile as updateProfileQuery
} from '@/lib/queries';
type AuthContextType = {
user: User | null;
profile: Profile | null;
avatar: string | null;
loading: boolean;
isAuthenticated: boolean;
updateProfile: (data: {
full_name?: string;
email?: string;
avatar_url?: string;
}) => Promise<{ data?: Profile; error?: unknown }>;
refreshUser: () => Promise<void>;
};
const AuthContext = createContext<AuthContextType | undefined>(undefined);
const AuthContextProvider = ({ children }: { children: ReactNode }) => {
const queryClient = useQueryClient();
const supabase = useSupabaseClient();
if (!supabase) throw new Error('Supabase client not found!');
// User query
const {
data: userData,
isLoading: userLoading,
error: userError,
} = useQuery({
queryKey: ['auth', 'user'],
queryFn: async () => {
const result = await getCurrentUser(supabase);
if (result.error) throw result.error;
return result.data.user as User | null;
},
retry: false,
meta: { errCode: QueryErrorCodes.FETCH_USER_FAILED },
});
// Profile query
const {
data: profileData,
isLoading: profileLoading,
} = useSupabaseQuery(
getProfile(supabase, userData?.id ?? ''),
{
enabled: !!userData?.id,
meta: { errCode: QueryErrorCodes.FETCH_PROFILE_FAILED },
}
);
// Avatar query
const {
data: avatarData,
} = useQuery({
queryKey: ['auth', 'avatar', profileData?.avatar_url],
queryFn: async () => {
if (!profileData?.avatar_url) return null;
const result = await getAvatar(supabase, profileData.avatar_url);
if (result.error) throw result.error;
return result.data.signedUrl as string | null;
},
enabled: !!profileData?.avatar_url,
meta: { errCode: QueryErrorCodes.FETCH_AVATAR_FAILED },
});
// Update profile mutation
const updateProfileMutation = useMutation({
mutationFn: async (updates: Partial<Profile>) => {
if (!userData?.id) throw new Error('User ID is required!');
const result = await updateProfileQuery(supabase, userData.id, updates);
if (result.error) throw result.error;
return result.data;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['auth'] })
.catch((error) => console.error('Error invalidating auth queries:', error));
toast.success('Profile updated successfully!');
},
meta: { errCode: QueryErrorCodes.UPDATE_PROFILE_FAILED },
});
useEffect(() => {
const {
data: { subscription },
} = supabase.auth.onAuthStateChange(async (event, _session) => {
if (event === 'SIGNED_IN' || event === 'SIGNED_OUT' || event === 'TOKEN_REFRESHED') {
await queryClient.invalidateQueries({ queryKey: ['auth'] });
}
});
return () => subscription.unsubscribe();
}, [supabase.auth, queryClient]);
const handleUpdateProfile = async (data: Partial<Profile>) => {
try {
const result = await updateProfileMutation.mutateAsync(data);
return { data: result };
} catch (error) {
return { error };
}
};
const refreshUser = async () => {
await queryClient.invalidateQueries({ queryKey: ['auth'] });
};
const value: AuthContextType = {
user: userData ?? null,
profile: profileData ?? null,
avatar: avatarData ?? null,
loading: userLoading || profileLoading,
isAuthenticated: !!userData && !userError,
updateProfile: handleUpdateProfile,
refreshUser,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
const useAuth = () => {
const context = useContext(AuthContext);
if (!context || context === undefined) {
throw new Error('useAuth must be used within an AuthContextProvider');
}
return context;
};
export { AuthContextProvider, useAuth };

View File

@@ -1,22 +1,78 @@
'use client'
'use client';
import {
QueryClient,
QueryClientProvider as ReactQueryClientProvider,
QueryCache,
MutationCache,
} from '@tanstack/react-query';
import { useState } from 'react';
import { toast } from 'sonner';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useState } from 'react'
const enum QueryErrorCodes {
FETCH_USER_FAILED = 'FETCH_USER_FAILED',
FETCH_PROFILE_FAILED = 'FETCH_PROFILE_FAILED',
FETCH_AVATAR_FAILED = 'FETCH_AVATAR_FAILED',
UPDATE_PROFILE_FAILED = 'UPDATE_PROFILE_FAILED',
UPLOAD_PHOTO_FAILED = 'UPLOAD_PHOTO_FAILED',
};
const ReactQueryClientProvider = ({ children }: { children: React.ReactNode }) => {
const queryCacheOnError = (error: unknown, query: any) => {
const errorMessage = error instanceof Error ? error.message : error as string;
switch (query.meta?.errCode) {
case QueryErrorCodes.FETCH_USER_FAILED:
toast.error('Failed to fetch user!');
break;
case QueryErrorCodes.FETCH_PROFILE_FAILED:
toast.error('Failed to fetch profile!');
break;
case QueryErrorCodes.FETCH_AVATAR_FAILED:
console.warn('Failed to fetch avatar. User may not have one!')
break;
default:
console.error('Query error:', error);
break;
}
};
const mutationCacheOnError = (
error: unknown,
variables: unknown,
context: unknown,
mutation: any,
) => {
const errorMessage = error instanceof Error ? error.message : error as string;
switch (mutation.meta?.errCode) {
case QueryErrorCodes.UPDATE_PROFILE_FAILED:
toast.error(`Failed to update user profile: ${errorMessage}`)
break;
case QueryErrorCodes.UPLOAD_PHOTO_FAILED:
toast.error(`Failed to upload photo: ${errorMessage}`)
break;
default:
console.error('Mutation error:', error);
break;
}
};
const QueryClientProvider = ({ children }: { children: React.ReactNode }) => {
const [queryClient] = useState(
() =>
new QueryClient({
queryCache: new QueryCache({
onError: queryCacheOnError,
}),
mutationCache: new MutationCache({
onError: mutationCacheOnError,
}),
defaultOptions: {
queries: {
// With SSR, we usually want to set some default staleTime
// above 0 to avoid refetching immediately on the client
staleTime: 60 * 1000,
},
},
})
)
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
return <ReactQueryClientProvider client={queryClient}>{children}</ReactQueryClientProvider>
};
export { ReactQueryClientProvider };
export { QueryClientProvider, QueryErrorCodes };