Refactor & clean up code.

This commit is contained in:
2025-07-17 15:20:59 -05:00
parent dabc248010
commit fefe7e8717
31 changed files with 473 additions and 295 deletions

View File

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

View File

@@ -1,92 +1,73 @@
'use client';
import React, {
type ReactNode,
createContext,
useContext,
useEffect,
useState
} from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import {
useQuery as useSupabaseQuery,
useUpdateMutation,
} from '@supabase-cache-helpers/postgrest-react-query';
import { QueryErrorCodes } from '@/lib/hooks/context';
import { type User, type Profile, useSupabaseClient } from '@/utils/supabase';
import { type User, type Profile } from '@/utils/supabase';
import { SupabaseClient } 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;
provider?: string;
}) => Promise<{ data?: Profile | null; error?: { message: string } | null }>;
updateProfile: (data: Partial<Profile>) => Promise<void>;
refreshUser: () => Promise<void>;
};
const AuthContext = createContext<AuthContextType | undefined>(undefined);
const AuthContextProvider = ({ children }: { children: ReactNode }) => {
export const AuthContextProvider = ({
children,
initialUser,
}: {
children: React.ReactNode;
initialUser?: User | null;
}) => {
const queryClient = useQueryClient();
const supabase = useSupabaseClient();
const supabase = SupabaseClient();
if (!supabase) throw new Error('Supabase client not found!');
// User query
// Initialize with server-side user data
const [user, setUser] = useState<User | null>(initialUser ?? null);
// User query with initial data
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;
const { data: { user } } = await supabase.auth.getUser();
return user;
},
retry: false,
meta: { errCode: QueryErrorCodes.FETCH_USER_FAILED },
initialData: initialUser,
staleTime: 5 * 60 * 1000, // 5 minutes
});
// Profile query
// Profile query using Supabase Cache Helpers
const {
data: profileData,
isLoading: profileLoading,
} = useSupabaseQuery(
getProfile(supabase, userData?.id ?? ''),
supabase
.from('profiles')
.select('*')
.eq('id', userData?.id ?? '')
.single(),
{
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 = useUpdateMutation(
supabase.from('profiles'),
@@ -95,47 +76,31 @@ const AuthContextProvider = ({ children }: { children: ReactNode }) => {
{
onSuccess: () => toast.success('Profile updated successfully!'),
onError: (error) => toast.error(`Failed to update profile: ${error.message}`),
meta: { errCode: QueryErrorCodes.UPDATE_PROFILE_FAILED },
},
}
);
//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 },
//});
// Auth state listener
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'] });
const { data: { subscription } } = supabase.auth.onAuthStateChange(
async (event, session) => {
setUser(session?.user ?? null);
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>) => {
if (!userData?.id) throw new Error('User ID is required!');
try {
const result = await updateProfileMutation.mutateAsync({
...data,
id: userData.id,
});
return { data: result, error: null };
} catch (error) {
return { data: null, error };
}
await updateProfileMutation.mutateAsync({
...data,
id: userData.id,
});
};
const refreshUser = async () => {
@@ -145,22 +110,23 @@ const AuthContextProvider = ({ children }: { children: ReactNode }) => {
const value: AuthContextType = {
user: userData ?? null,
profile: profileData ?? null,
avatar: avatarData ?? null,
loading: userLoading || profileLoading,
isAuthenticated: !!userData && !userError,
isAuthenticated: !!userData,
updateProfile: handleUpdateProfile,
refreshUser,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
};
const useAuth = () => {
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context || context === undefined) {
if (!context) {
throw new Error('useAuth must be used within an AuthContextProvider');
}
return context;
};
export { AuthContextProvider, useAuth };

View File

@@ -66,7 +66,10 @@ const QueryClientProvider = ({ children }: { children: React.ReactNode }) => {
defaultOptions: {
queries: {
refetchOnWindowFocus: true,
staleTime: 60 * 1000,
// Supabase cache helpers recommends Infinity.
// React Query Recommends 1 minute.
staleTime: 10 * (60 * 1000), // We'll be in between with 10 minutes
gcTime: Infinity,
},
},
})

View File

@@ -71,4 +71,4 @@ const ThemeToggle = ({
);
};
export { ThemeProvider, ThemeToggle };
export { ThemeProvider, ThemeToggle, type ThemeToggleProps };