"use client"; import type React from "react"; import { useState, useRef } from "react"; import { z } from "zod"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import type { User } from "@/src/models/users/users.model"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/app/_components/ui/form"; import { Avatar, AvatarFallback, AvatarImage, } from "@/app/_components/ui/avatar"; import { Input } from "@/app/_components/ui/input"; import { Textarea } from "@/app/_components/ui/textarea"; import { Button } from "@/app/_components/ui/button"; import { Label } from "@/app/_components/ui/label"; import { ImageIcon, Loader2 } from "lucide-react"; import { createClient } from "@/utils/supabase/client"; // Profile update form schema const profileFormSchema = z.object({ first_name: z.string().nullable().optional(), last_name: z.string().nullable().optional(), bio: z.string().nullable().optional(), avatar: z.string().nullable().optional(), }); type ProfileFormValues = z.infer; interface ProfileFormProps { user: User | null; onSuccess?: () => void; } export function ProfileForm({ user, onSuccess }: ProfileFormProps) { const [isLoading, setIsLoading] = useState(false); const [avatarPreview, setAvatarPreview] = useState( user?.profile?.avatar || null ); const fileInputRef = useRef(null); const supabase = createClient(); // Use profile data with fallbacks const firstName = user?.profile?.first_name || ""; const lastName = user?.profile?.last_name || ""; const userEmail = user?.email || ""; const userBio = user?.profile?.bio || ""; const getFullName = () => { return `${firstName} ${lastName}`.trim() || "User"; }; // Generate initials for avatar fallback const getInitials = () => { if (firstName && lastName) { return `${firstName[0]}${lastName[0]}`.toUpperCase(); } if (firstName) { return firstName[0].toUpperCase(); } if (userEmail) { return userEmail[0].toUpperCase(); } return "U"; }; // Setup form with react-hook-form and zod validation const form = useForm({ resolver: zodResolver(profileFormSchema), defaultValues: { first_name: firstName || "", last_name: lastName || "", bio: userBio || "", avatar: user?.profile?.avatar || "", }, }); // Handle avatar file upload const handleFileChange = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file || !user?.id) return; try { setIsLoading(true); // Create a preview of the selected image const objectUrl = URL.createObjectURL(file); setAvatarPreview(objectUrl); // Upload to Supabase Storage const fileExt = file.name.split(".").pop(); const fileName = `${user.id}-${Date.now()}.${fileExt}`; const filePath = `avatars/${fileName}`; const { error: uploadError, data } = await supabase.storage .from("profiles") .upload(filePath, file, { upsert: true, contentType: file.type, }); if (uploadError) { throw uploadError; } // Get the public URL const { data: { publicUrl }, } = supabase.storage.from("profiles").getPublicUrl(filePath); // Update the form value form.setValue("avatar", publicUrl); } catch (error) { console.error("Error uploading avatar:", error); // Revert to previous avatar if upload fails setAvatarPreview(user?.profile?.avatar || null); } finally { setIsLoading(false); } }; // Trigger file input click const handleAvatarClick = () => { fileInputRef.current?.click(); }; // Handle form submission async function onSubmit(data: ProfileFormValues) { try { if (!user?.id) return; // Update profile in database const { error } = await supabase .from("profiles") .update({ first_name: data.first_name, last_name: data.last_name, bio: data.bio, avatar: data.avatar, }) .eq("user_id", user.id); if (error) throw error; // Call success callback onSuccess?.(); } catch (error) { console.error("Error updating profile:", error); } } return (
{/* Avatar upload section at the top */}
{avatarPreview ? ( ) : ( {getInitials()} )}
{isLoading ? ( ) : ( )}
( First Name )} /> ( Last Name )} /> ( Bio