Not even sure but I'm sure it's better

This commit is contained in:
2025-05-20 16:38:40 -05:00
parent 259aa46ef8
commit 0f92f7eb7f
19 changed files with 331 additions and 575 deletions

View File

@ -13,7 +13,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui';
import { useProfile, useAvatar } from '@/lib/hooks'
import { useProfile, useAvatar } from '@/lib/hooks';
import { signOut } from '@/lib/actions';
import { User } from 'lucide-react';
@ -42,10 +42,16 @@ const AvatarDropdown = () => {
{avatarUrl ? (
<AvatarImage src={avatarUrl} />
) : (
<AvatarFallback className="text-2xl">
{profile?.full_name
? profile.full_name.split(' ').map(n => n[0]).join('').toUpperCase()
: <User size={32} />}
<AvatarFallback className='text-2xl'>
{profile?.full_name ? (
profile.full_name
.split(' ')
.map((n) => n[0])
.join('')
.toUpperCase()
) : (
<User size={32} />
)}
</AvatarFallback>
)}
</Avatar>

View File

@ -9,7 +9,10 @@ type AvatarUploadProps = {
onAvatarUploaded: (path: string) => Promise<void>;
};
export const AvatarUpload = ({ profile, onAvatarUploaded }: AvatarUploadProps) => {
export const AvatarUpload = ({
profile,
onAvatarUploaded,
}: AvatarUploadProps) => {
const { avatarUrl, isLoading } = useAvatar(profile);
const { isUploading, fileInputRef, uploadToStorage } = useFileUpload();
@ -20,7 +23,7 @@ export const AvatarUpload = ({ profile, onAvatarUploaded }: AvatarUploadProps) =
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const result = await uploadToStorage(file, 'avatars');
if (result.success && result.path) {
await onAvatarUploaded(result.path);
@ -29,59 +32,77 @@ 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 className='flex flex-col items-center'>
<div className='mb-4'>
<Avatar className='h-32 w-32'>
<AvatarFallback className='text-2xl'>
{profile?.full_name ? (
profile.full_name
.split(' ')
.map((n) => n[0])
.join('')
.toUpperCase()
) : (
<User size={32} />
)}
</AvatarFallback>
</Avatar>
</div>
</div>
</div>
);
}
return (
<div className="flex flex-col items-center">
<div className="relative group cursor-pointer mb-4" onClick={handleAvatarClick}>
<Avatar className="h-32 w-32">
<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={profile?.full_name ?? 'User'} />
) : (
<AvatarFallback className="text-2xl">
{profile?.full_name
? profile.full_name.split(' ').map(n => n[0]).join('').toUpperCase()
: <User size={32} />}
<AvatarFallback className='text-2xl'>
{profile?.full_name ? (
profile.full_name
.split(' ')
.map((n) => n[0])
.join('')
.toUpperCase()
) : (
<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"
<div
className='absolute inset-0 rounded-full bg-black/0 group-hover:bg-black/50
transition-all flex items-center justify-center'
>
<Pencil className="text-white opacity-0 group-hover:opacity-100
transition-opacity" size={24}
<Pencil
className='text-white opacity-0 group-hover:opacity-100
transition-opacity'
size={24}
/>
</div>
<input
ref={fileInputRef}
type="file"
accept="image/*"
className="hidden"
type='file'
accept='image/*'
className='hidden'
onChange={handleFileChange}
disabled={isUploading}
/>
</div>
{isUploading && (
<div className="flex items-center text-sm text-gray-500 mt-2">
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
<div className='flex items-center text-sm text-gray-500 mt-2'>
<Loader2 className='h-4 w-4 mr-2 animate-spin' />
Uploading...
</div>
)}
<p className="text-sm text-gray-500 mt-2">
<p className='text-sm text-gray-500 mt-2'>
Click on the avatar to upload a new image
</p>
</div>
);
}
};

View File

@ -18,7 +18,7 @@ import { useEffect } from 'react';
const formSchema = z.object({
full_name: z.string().min(5, {
message: 'Full name is required & must be at least 5 characters.'
message: 'Full name is required & must be at least 5 characters.',
}),
email: z.string().email(),
});
@ -29,7 +29,11 @@ type ProfileFormProps = {
onSubmit: (values: z.infer<typeof formSchema>) => Promise<void>;
};
export function ProfileForm({ profile, isLoading, onSubmit }: ProfileFormProps) {
export function ProfileForm({
profile,
isLoading,
onSubmit,
}: ProfileFormProps) {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
@ -54,10 +58,7 @@ export function ProfileForm({ profile, isLoading, onSubmit }: ProfileFormProps)
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(handleSubmit)}
className='space-y-6'
>
<form onSubmit={form.handleSubmit(handleSubmit)} className='space-y-6'>
<FormField
control={form.control}
name='full_name'
@ -67,14 +68,12 @@ export function ProfileForm({ profile, isLoading, onSubmit }: ProfileFormProps)
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
Your public display name.
</FormDescription>
<FormDescription>Your public display name.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='email'
@ -91,12 +90,12 @@ export function ProfileForm({ profile, isLoading, onSubmit }: ProfileFormProps)
</FormItem>
)}
/>
<div className="flex justify-center">
<div className='flex justify-center'>
<Button type='submit' disabled={isLoading}>
{isLoading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Loader2 className='mr-2 h-4 w-4 animate-spin' />
Saving...
</>
) : (