Making progress on rewrite. Recreating queries and hooks now.
This commit is contained in:
@@ -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';
|
||||
|
@@ -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 };
|
||||
|
@@ -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 };
|
||||
|
Reference in New Issue
Block a user