Cleanup. Stuff from yesterday idk
This commit is contained in:
@@ -14,11 +14,7 @@ import {
|
||||
getUser,
|
||||
updateProfile as updateProfileAction,
|
||||
} from '@/lib/hooks';
|
||||
import {
|
||||
type User,
|
||||
type Profile,
|
||||
createClient,
|
||||
} from '@/utils/supabase';
|
||||
import { type User, type Profile, createClient } from '@/utils/supabase';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
type AuthContextType = {
|
||||
@@ -45,65 +41,68 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
const fetchingRef = useRef(false);
|
||||
|
||||
const fetchUserData = useCallback(async (showLoading = true) => {
|
||||
if (fetchingRef.current) return;
|
||||
fetchingRef.current = true;
|
||||
const fetchUserData = useCallback(
|
||||
async (showLoading = true) => {
|
||||
if (fetchingRef.current) return;
|
||||
fetchingRef.current = true;
|
||||
|
||||
try {
|
||||
// Only show loading for initial load or manual refresh
|
||||
if (showLoading) {
|
||||
setIsLoading(true);
|
||||
}
|
||||
try {
|
||||
// Only show loading for initial load or manual refresh
|
||||
if (showLoading) {
|
||||
setIsLoading(true);
|
||||
}
|
||||
|
||||
const userResponse = await getUser();
|
||||
const profileResponse = await getProfile();
|
||||
const userResponse = await getUser();
|
||||
const profileResponse = await getProfile();
|
||||
|
||||
if (!userResponse.success || !profileResponse.success) {
|
||||
setUser(null);
|
||||
setProfile(null);
|
||||
setAvatarUrl(null);
|
||||
return;
|
||||
}
|
||||
if (!userResponse.success || !profileResponse.success) {
|
||||
setUser(null);
|
||||
setProfile(null);
|
||||
setAvatarUrl(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setUser(userResponse.data);
|
||||
setProfile(profileResponse.data);
|
||||
setUser(userResponse.data);
|
||||
setProfile(profileResponse.data);
|
||||
|
||||
// Get avatar URL if available
|
||||
if (profileResponse.data.avatar_url) {
|
||||
const avatarResponse = await getSignedUrl({
|
||||
bucket: 'avatars',
|
||||
url: profileResponse.data.avatar_url,
|
||||
});
|
||||
if (avatarResponse.success) {
|
||||
setAvatarUrl(avatarResponse.data);
|
||||
// Get avatar URL if available
|
||||
if (profileResponse.data.avatar_url) {
|
||||
const avatarResponse = await getSignedUrl({
|
||||
bucket: 'avatars',
|
||||
url: profileResponse.data.avatar_url,
|
||||
});
|
||||
if (avatarResponse.success) {
|
||||
setAvatarUrl(avatarResponse.data);
|
||||
} else {
|
||||
setAvatarUrl(null);
|
||||
}
|
||||
} else {
|
||||
setAvatarUrl(null);
|
||||
}
|
||||
} else {
|
||||
setAvatarUrl(null);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'Auth fetch error: ',
|
||||
error instanceof Error
|
||||
? `${error.message}`
|
||||
: 'Failed to load user data!',
|
||||
);
|
||||
if (!isInitialized) {
|
||||
toast.error('Failed to load user data!');
|
||||
}
|
||||
} finally {
|
||||
if (showLoading) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
setIsInitialized(true);
|
||||
fetchingRef.current = false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'Auth fetch error: ',
|
||||
error instanceof Error ?
|
||||
`${error.message}` :
|
||||
'Failed to load user data!'
|
||||
);
|
||||
if (!isInitialized) {
|
||||
toast.error('Failed to load user data!');
|
||||
}
|
||||
} finally {
|
||||
if (showLoading) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
setIsInitialized(true);
|
||||
fetchingRef.current = false;
|
||||
}
|
||||
}, [isInitialized]);
|
||||
},
|
||||
[isInitialized],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const supabase = createClient();
|
||||
|
||||
|
||||
// Initial fetch with loading
|
||||
fetchUserData(true).catch((error) => {
|
||||
console.error('💥 Initial fetch error:', error);
|
||||
@@ -113,7 +112,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
data: { subscription },
|
||||
} = supabase.auth.onAuthStateChange(async (event, session) => {
|
||||
console.log('Auth state change:', event); // Debug log
|
||||
|
||||
|
||||
if (event === 'SIGNED_IN') {
|
||||
// Background refresh without loading state
|
||||
await fetchUserData(false);
|
||||
@@ -133,37 +132,42 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
};
|
||||
}, [fetchUserData]);
|
||||
|
||||
const updateProfile = useCallback(async (data: {
|
||||
full_name?: string;
|
||||
email?: string;
|
||||
avatar_url?: string;
|
||||
}) => {
|
||||
try {
|
||||
const result = await updateProfileAction(data);
|
||||
if (!result.success) {
|
||||
throw new Error(result.error ?? 'Failed to update profile');
|
||||
}
|
||||
setProfile(result.data);
|
||||
|
||||
// If avatar was updated, refresh the avatar URL
|
||||
if (data.avatar_url && result.data.avatar_url) {
|
||||
const avatarResponse = await getSignedUrl({
|
||||
bucket: 'avatars',
|
||||
url: result.data.avatar_url,
|
||||
transform: { width: 128, height: 128 },
|
||||
});
|
||||
if (avatarResponse.success) {
|
||||
setAvatarUrl(avatarResponse.data);
|
||||
const updateProfile = useCallback(
|
||||
async (data: {
|
||||
full_name?: string;
|
||||
email?: string;
|
||||
avatar_url?: string;
|
||||
}) => {
|
||||
try {
|
||||
const result = await updateProfileAction(data);
|
||||
if (!result.success) {
|
||||
throw new Error(result.error ?? 'Failed to update profile');
|
||||
}
|
||||
setProfile(result.data);
|
||||
|
||||
// If avatar was updated, refresh the avatar URL
|
||||
if (data.avatar_url && result.data.avatar_url) {
|
||||
const avatarResponse = await getSignedUrl({
|
||||
bucket: 'avatars',
|
||||
url: result.data.avatar_url,
|
||||
transform: { width: 128, height: 128 },
|
||||
});
|
||||
if (avatarResponse.success) {
|
||||
setAvatarUrl(avatarResponse.data);
|
||||
}
|
||||
}
|
||||
toast.success('Profile updated successfully!');
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
console.error('Error updating profile:', error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : 'Failed to update profile',
|
||||
);
|
||||
return { success: false, error };
|
||||
}
|
||||
toast.success('Profile updated successfully!');
|
||||
return { success: true, data: result.data };
|
||||
} catch (error) {
|
||||
console.error('Error updating profile:', error);
|
||||
toast.error(error instanceof Error ? error.message : 'Failed to update profile');
|
||||
return { success: false, error };
|
||||
}
|
||||
}, []);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const refreshUserData = useCallback(async () => {
|
||||
await fetchUserData(true); // Manual refresh shows loading
|
||||
@@ -179,11 +183,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
refreshUserData,
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={value}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||
};
|
||||
|
||||
export const useAuth = () => {
|
||||
|
||||
@@ -15,9 +15,7 @@ export const StatusMessage = ({ message }: { message: Message }) => {
|
||||
</div>
|
||||
)}
|
||||
{'error' in message && (
|
||||
<div className='text-destructive'>
|
||||
{message.error}
|
||||
</div>
|
||||
<div className='text-destructive'>{message.error}</div>
|
||||
)}
|
||||
{'message' in message && (
|
||||
<div className='text-foreground'>{message.message}</div>
|
||||
|
||||
@@ -17,9 +17,9 @@ export const SignInWithApple = () => {
|
||||
try {
|
||||
setStatusMessage('');
|
||||
setIsSigningIn(true);
|
||||
|
||||
|
||||
const result = await signInWithApple();
|
||||
|
||||
|
||||
if (result?.success && result.data) {
|
||||
// Redirect to Apple OAuth page
|
||||
window.location.href = result.data;
|
||||
@@ -28,7 +28,7 @@ export const SignInWithApple = () => {
|
||||
}
|
||||
} catch (error) {
|
||||
setStatusMessage(
|
||||
`Error: ${error instanceof Error ? error.message : 'Could not sign in!'}`
|
||||
`Error: ${error instanceof Error ? error.message : 'Could not sign in!'}`,
|
||||
);
|
||||
} finally {
|
||||
setIsSigningIn(false);
|
||||
@@ -38,28 +38,25 @@ export const SignInWithApple = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSignInWithApple}
|
||||
className='my-4'
|
||||
>
|
||||
<form onSubmit={handleSignInWithApple} className='my-4'>
|
||||
<SubmitButton
|
||||
className='w-full cursor-pointer'
|
||||
disabled={isLoading || isSigningIn}
|
||||
pendingText='Redirecting...'
|
||||
type="submit"
|
||||
type='submit'
|
||||
>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Image src='/icons/apple.svg'
|
||||
<Image
|
||||
src='/icons/apple.svg'
|
||||
alt='Apple logo'
|
||||
className='invert-75 dark:invert-25'
|
||||
width={22} height={22}
|
||||
width={22}
|
||||
height={22}
|
||||
/>
|
||||
<p className='text-[1.0rem]'>Sign in with Apple</p>
|
||||
</div>
|
||||
</SubmitButton>
|
||||
{statusMessage && (
|
||||
<StatusMessage message={{ error: statusMessage }} />
|
||||
)}
|
||||
{statusMessage && <StatusMessage message={{ error: statusMessage }} />}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -15,9 +15,9 @@ export const SignInWithMicrosoft = () => {
|
||||
try {
|
||||
setStatusMessage('');
|
||||
setIsSigningIn(true);
|
||||
|
||||
|
||||
const result = await signInWithMicrosoft();
|
||||
|
||||
|
||||
if (result?.success && result.data) {
|
||||
// Redirect to Microsoft OAuth page
|
||||
window.location.href = result.data;
|
||||
@@ -26,30 +26,30 @@ export const SignInWithMicrosoft = () => {
|
||||
}
|
||||
} catch (error) {
|
||||
setStatusMessage(
|
||||
`Error: ${error instanceof Error ? error.message : 'Could not sign in!'}`
|
||||
`Error: ${error instanceof Error ? error.message : 'Could not sign in!'}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSignInWithMicrosoft}
|
||||
className='my-4'
|
||||
>
|
||||
<form onSubmit={handleSignInWithMicrosoft} className='my-4'>
|
||||
<SubmitButton
|
||||
className='w-full cursor-pointer'
|
||||
disabled={isLoading || isSigningIn}
|
||||
pendingText='Redirecting...'
|
||||
type="submit"
|
||||
type='submit'
|
||||
>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Image src='/icons/microsoft.svg' alt='Microsoft logo' width={20} height={20} />
|
||||
<Image
|
||||
src='/icons/microsoft.svg'
|
||||
alt='Microsoft logo'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
<p className='text-[1.0rem]'>Sign in with Microsoft</p>
|
||||
</div>
|
||||
</SubmitButton>
|
||||
{statusMessage && (
|
||||
<StatusMessage message={{ error: statusMessage }} />
|
||||
)}
|
||||
{statusMessage && <StatusMessage message={{ error: statusMessage }} />}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
export { StatusMessage, type Message } from '@/components/default/StatusMessage';
|
||||
export {
|
||||
StatusMessage,
|
||||
type Message,
|
||||
} from '@/components/default/StatusMessage';
|
||||
export { SubmitButton } from '@/components/default/SubmitButton';
|
||||
|
||||
@@ -31,7 +31,8 @@ const AvatarDropdown = () => {
|
||||
|
||||
const getInitials = (name: string | null | undefined): string => {
|
||||
if (!name) return '';
|
||||
return name.split(' ')
|
||||
return name
|
||||
.split(' ')
|
||||
.map((n) => n[0])
|
||||
.join('')
|
||||
.toUpperCase();
|
||||
@@ -42,12 +43,19 @@ const AvatarDropdown = () => {
|
||||
<DropdownMenuTrigger>
|
||||
<Avatar className='cursor-pointer'>
|
||||
{avatarUrl ? (
|
||||
<AvatarImage src={avatarUrl} alt={getInitials(profile?.full_name)} width={64} height={64} />
|
||||
<AvatarImage
|
||||
src={avatarUrl}
|
||||
alt={getInitials(profile?.full_name)}
|
||||
width={64}
|
||||
height={64}
|
||||
/>
|
||||
) : (
|
||||
<AvatarFallback className='text-sm'>
|
||||
{profile?.full_name
|
||||
? getInitials(profile.full_name)
|
||||
: <User size={32} />}
|
||||
{profile?.full_name ? (
|
||||
getInitials(profile.full_name)
|
||||
) : (
|
||||
<User size={32} />
|
||||
)}
|
||||
</AvatarFallback>
|
||||
)}
|
||||
</Avatar>
|
||||
@@ -56,13 +64,19 @@ const AvatarDropdown = () => {
|
||||
<DropdownMenuLabel>{profile?.full_name}</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href='/profile' className='w-full justify-center cursor-pointer'>
|
||||
<Link
|
||||
href='/profile'
|
||||
className='w-full justify-center cursor-pointer'
|
||||
>
|
||||
Edit profile
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator className='h-[2px]' />
|
||||
<DropdownMenuItem asChild>
|
||||
<button onClick={handleSignOut} className='w-full justify-center cursor-pointer'>
|
||||
<button
|
||||
onClick={handleSignOut}
|
||||
className='w-full justify-center cursor-pointer'
|
||||
>
|
||||
Log out
|
||||
</button>
|
||||
</DropdownMenuItem>
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { useFileUpload } from '@/lib/hooks/useFileUpload';
|
||||
import { useAuth } from '@/components/context/auth';
|
||||
import { Avatar, AvatarFallback, AvatarImage, CardContent } from '@/components/ui';
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
CardContent,
|
||||
} from '@/components/ui';
|
||||
import { Loader2, Pencil, Upload, User } from 'lucide-react';
|
||||
|
||||
type AvatarUploadProps = {
|
||||
@@ -28,16 +33,17 @@ export const AvatarUpload = ({ onAvatarUploaded }: AvatarUploadProps) => {
|
||||
maxHeight: 500,
|
||||
quality: 0.8,
|
||||
},
|
||||
replace: {replace: true, path: profile?.avatar_url ?? file.name},
|
||||
replace: { replace: true, path: profile?.avatar_url ?? file.name },
|
||||
});
|
||||
if (result.success && result.path) {
|
||||
await onAvatarUploaded(result.path);
|
||||
if (result.success && result.data) {
|
||||
await onAvatarUploaded(result.data);
|
||||
}
|
||||
};
|
||||
|
||||
const getInitials = (name: string | null | undefined): string => {
|
||||
if (!name) return '';
|
||||
return name.split(' ')
|
||||
return name
|
||||
.split(' ')
|
||||
.map((n) => n[0])
|
||||
.join('')
|
||||
.toUpperCase();
|
||||
@@ -45,23 +51,29 @@ export const AvatarUpload = ({ onAvatarUploaded }: AvatarUploadProps) => {
|
||||
|
||||
return (
|
||||
<CardContent>
|
||||
|
||||
<div className='flex flex-col items-center'>
|
||||
<div
|
||||
className='relative group cursor-pointer mb-4'
|
||||
onClick={handleAvatarClick}
|
||||
>
|
||||
<Avatar className='h-32 w-32'>
|
||||
{avatarUrl ? (
|
||||
<AvatarImage src={avatarUrl} alt={getInitials(profile?.full_name)} width={128} height={128} />
|
||||
) : (
|
||||
<AvatarFallback className='text-4xl'>
|
||||
{profile?.full_name
|
||||
? getInitials(profile.full_name)
|
||||
: <User size={32} />}
|
||||
</AvatarFallback>
|
||||
)}
|
||||
</Avatar>
|
||||
<div className='flex flex-col items-center'>
|
||||
<div
|
||||
className='relative group cursor-pointer mb-4'
|
||||
onClick={handleAvatarClick}
|
||||
>
|
||||
<Avatar className='h-32 w-32'>
|
||||
{avatarUrl ? (
|
||||
<AvatarImage
|
||||
src={avatarUrl}
|
||||
alt={getInitials(profile?.full_name)}
|
||||
width={128}
|
||||
height={128}
|
||||
/>
|
||||
) : (
|
||||
<AvatarFallback className='text-4xl'>
|
||||
{profile?.full_name ? (
|
||||
getInitials(profile.full_name)
|
||||
) : (
|
||||
<User size={32} />
|
||||
)}
|
||||
</AvatarFallback>
|
||||
)}
|
||||
</Avatar>
|
||||
<div
|
||||
className='absolute inset-0 rounded-full bg-black/0 group-hover:bg-black/50
|
||||
transition-all flex items-center justify-center'
|
||||
@@ -88,13 +100,13 @@ export const AvatarUpload = ({ onAvatarUploaded }: AvatarUploadProps) => {
|
||||
onChange={handleFileChange}
|
||||
disabled={isUploading}
|
||||
/>
|
||||
{isUploading && (
|
||||
<div className='flex items-center text-sm text-gray-500 mt-2'>
|
||||
<Loader2 className='h-4 w-4 mr-2 animate-spin' />
|
||||
Uploading...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isUploading && (
|
||||
<div className='flex items-center text-sm text-gray-500 mt-2'>
|
||||
<Loader2 className='h-4 w-4 mr-2 animate-spin' />
|
||||
Uploading...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@ type ProfileFormProps = {
|
||||
onSubmit: (values: z.infer<typeof formSchema>) => Promise<void>;
|
||||
};
|
||||
|
||||
export const ProfileForm = ({onSubmit}: ProfileFormProps) => {
|
||||
export const ProfileForm = ({ onSubmit }: ProfileFormProps) => {
|
||||
const { profile, isLoading } = useAuth();
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
@@ -89,10 +89,7 @@ export const ProfileForm = ({onSubmit}: ProfileFormProps) => {
|
||||
/>
|
||||
|
||||
<div className='flex justify-center'>
|
||||
<SubmitButton
|
||||
disabled={isLoading}
|
||||
pendingText='Saving...'
|
||||
>
|
||||
<SubmitButton disabled={isLoading} pendingText='Saving...'>
|
||||
Save Changes
|
||||
</SubmitButton>
|
||||
</div>
|
||||
@@ -100,4 +97,4 @@ export const ProfileForm = ({onSubmit}: ProfileFormProps) => {
|
||||
</Form>
|
||||
</CardContent>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -69,7 +69,7 @@ export const ResetPasswordForm = ({
|
||||
}
|
||||
} catch (error) {
|
||||
setStatusMessage(
|
||||
error instanceof Error ? error.message : 'Password was not updated!'
|
||||
error instanceof Error ? error.message : 'Password was not updated!',
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -86,7 +86,7 @@ export const ResetPasswordForm = ({
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form {...form}>
|
||||
<form
|
||||
<form
|
||||
onSubmit={form.handleSubmit(handleUpdatePassword)}
|
||||
className='space-y-6'
|
||||
>
|
||||
@@ -123,10 +123,11 @@ export const ResetPasswordForm = ({
|
||||
)}
|
||||
/>
|
||||
{statusMessage && (
|
||||
<div
|
||||
<div
|
||||
className={`text-sm text-center ${
|
||||
statusMessage.includes('Error') || statusMessage.includes('failed')
|
||||
? 'text-destructive'
|
||||
statusMessage.includes('Error') ||
|
||||
statusMessage.includes('failed')
|
||||
? 'text-destructive'
|
||||
: 'text-green-600'
|
||||
}`}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user