Updated Nav bar avatar to use hooks

This commit is contained in:
Gabriel Brown 2025-05-20 16:11:41 -05:00
parent 408bb140ba
commit 259aa46ef8
6 changed files with 49 additions and 48 deletions

View File

@ -1,5 +1,5 @@
'use client'; 'use client';
import { useProfile } from '@/lib/hooks/useProfile'; import { useProfile } from '@/lib/hooks';
import { AvatarUpload, ProfileForm } from '@/components/default/profile'; import { AvatarUpload, ProfileForm } from '@/components/default/profile';
import { import {
Card, Card,

View File

@ -1,5 +1,6 @@
'use client'; 'use client';
import Link from 'next/link';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { import {
Avatar, Avatar,
@ -12,60 +13,41 @@ import {
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@/components/ui'; } from '@/components/ui';
import { getSignedUrl, getProfile, signOut } from '@/lib/actions'; import { useProfile, useAvatar } from '@/lib/hooks'
import { signOut } from '@/lib/actions';
import type { Profile } from '@/utils/supabase'; import { User } from 'lucide-react';
import Link from 'next/link';
const AvatarDropdown = () => { const AvatarDropdown = () => {
const [profile, setProfile] = useState<Profile | undefined>(undefined); const { profile } = useProfile();
const [signedUrl, setSignedUrl] = useState<string | undefined>(undefined); const { avatarUrl, isLoading } = useAvatar(profile);
const handleSignOut = async () => { const handleSignOut = async () => {
await signOut(); await signOut();
}; };
useEffect(() => { if (isLoading) {
const handleGetProfile = async () => { return (
try { <Avatar>
const profileResponse = await getProfile(); <AvatarFallback className='animate-pulse'>
if (!profileResponse.success) <User size={32} />
throw new Error('Profile response unsuccessful'); </AvatarFallback>
setProfile(profileResponse.data); </Avatar>
} catch (error) { );
console.error('Error getting profile:', error); }
}
};
handleGetProfile().catch((error) => {
console.error('Error getting profile:', error);
})
}, []);
useEffect(() => {
const handleGetSignedUrl = async () => {
try {
const response = await getSignedUrl({
bucket: 'avatars',
url: profile?.avatar_url ?? '',
});
if (response.success) {
setSignedUrl(response.data);
}
} catch (error) {
console.error('Error getting signed URL:', error);
}
};
handleGetSignedUrl().catch((error) => {
console.error('Error getting signed URL:', error);
});
}, [profile]);
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger> <DropdownMenuTrigger>
<Avatar> <Avatar className='cursor-pointer'>
<AvatarImage src={signedUrl ?? '/favicon.ico'} /> {avatarUrl ? (
<AvatarFallback>AN</AvatarFallback> <AvatarImage src={avatarUrl} />
) : (
<AvatarFallback className="text-2xl">
{profile?.full_name
? profile.full_name.split(' ').map(n => n[0]).join('').toUpperCase()
: <User size={32} />}
</AvatarFallback>
)}
</Avatar> </Avatar>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>

View File

@ -10,7 +10,7 @@ type AvatarUploadProps = {
}; };
export const AvatarUpload = ({ profile, onAvatarUploaded }: AvatarUploadProps) => { export const AvatarUpload = ({ profile, onAvatarUploaded }: AvatarUploadProps) => {
const { avatarUrl } = useAvatar(profile); const { avatarUrl, isLoading } = useAvatar(profile);
const { isUploading, fileInputRef, uploadToStorage } = useFileUpload(); const { isUploading, fileInputRef, uploadToStorage } = useFileUpload();
const handleAvatarClick = () => { const handleAvatarClick = () => {
@ -27,6 +27,22 @@ export const AvatarUpload = ({ profile, onAvatarUploaded }: AvatarUploadProps) =
} }
}; };
if (isLoading) {
return (
<div className="flex flex-col items-center">
<div className="relative group cursor-pointer mb-4">
<Avatar>
<AvatarFallback className="text-2xl">
{profile?.full_name
? profile.full_name.split(' ').map(n => n[0]).join('').toUpperCase()
: <User size={32} />}
</AvatarFallback>
</Avatar>
</div>
</div>
);
}
return ( return (
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<div className="relative group cursor-pointer mb-4" onClick={handleAvatarClick}> <div className="relative group cursor-pointer mb-4" onClick={handleAvatarClick}>

View File

@ -92,7 +92,7 @@ export function ProfileForm({ profile, isLoading, onSubmit }: ProfileFormProps)
)} )}
/> />
<div className="flex justify-end"> <div className="flex justify-center">
<Button type='submit' disabled={isLoading}> <Button type='submit' disabled={isLoading}>
{isLoading ? ( {isLoading ? (
<> <>

3
src/lib/hooks/index.ts Normal file
View File

@ -0,0 +1,3 @@
export * from './useAvatar';
export * from './useFileUpload';
export * from './useProfile';

View File

@ -3,7 +3,7 @@ import { getProfile, updateProfile } from '@/lib/actions';
import type { Profile } from '@/utils/supabase'; import type { Profile } from '@/utils/supabase';
import { toast } from 'sonner'; import { toast } from 'sonner';
export function useProfile() { export const useProfile = () => {
const [profile, setProfile] = useState<Profile | undefined>(undefined); const [profile, setProfile] = useState<Profile | undefined>(undefined);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
@ -15,7 +15,7 @@ export function useProfile() {
if (!profileResponse.success) if (!profileResponse.success)
throw new Error('Profile response unsuccessful'); throw new Error('Profile response unsuccessful');
setProfile(profileResponse.data); setProfile(profileResponse.data);
} catch (error) { } catch {
setProfile(undefined); setProfile(undefined);
} finally { } finally {
setIsLoading(false); setIsLoading(false);