|
|
|
@@ -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 };
|
|
|
|
|