import { useEffect, useRef, useState } from "react"; import { useAuth } from "../hooks/useAuth"; import { usePublicProfile, useUpdateProfile } from "../hooks/useProfile"; import { apiUpload } from "../lib/api"; export function ProfileSection() { const { data: auth } = useAuth(); const userId = auth?.user?.id ?? null; const { data: profile } = usePublicProfile(userId); const updateProfile = useUpdateProfile(); const [displayName, setDisplayName] = useState(""); const [bio, setBio] = useState(""); const [avatarFilename, setAvatarFilename] = useState(null); const [avatarDisplayUrl, setAvatarDisplayUrl] = useState(null); const [dirty, setDirty] = useState(false); const [message, setMessage] = useState<{ type: "success" | "error"; text: string; } | null>(null); const [uploading, setUploading] = useState(false); const fileInputRef = useRef(null); useEffect(() => { if (profile && !dirty) { setDisplayName(profile.displayName ?? ""); setBio(profile.bio ?? ""); setAvatarFilename(profile.avatarUrl ?? null); setAvatarDisplayUrl(profile.avatarImageUrl ?? null); } }, [profile, dirty]); async function handleSave(e: React.FormEvent) { e.preventDefault(); setMessage(null); try { await updateProfile.mutateAsync({ displayName: displayName.trim() || undefined, avatarUrl: avatarFilename, bio: bio.trim() || undefined, }); setDirty(false); setMessage({ type: "success", text: "Profile updated" }); } catch (err) { setMessage({ type: "error", text: (err as Error).message }); } } async function handleAvatarUpload(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (!file) return; const maxSize = 5 * 1024 * 1024; const accepted = ["image/jpeg", "image/png", "image/webp"]; if (!accepted.includes(file.type)) { setMessage({ type: "error", text: "Please select a JPG, PNG, or WebP image.", }); return; } if (file.size > maxSize) { setMessage({ type: "error", text: "Image must be under 5MB." }); return; } const localPreview = URL.createObjectURL(file); setAvatarDisplayUrl(localPreview); setUploading(true); setMessage(null); try { const result = await apiUpload<{ filename: string }>("/api/images", file); setAvatarFilename(result.filename); setDirty(true); } catch { setAvatarDisplayUrl(null); setMessage({ type: "error", text: "Avatar upload failed." }); } finally { setUploading(false); if (fileInputRef.current) fileInputRef.current.value = ""; } } return (

Profile

Your public profile information

{/* Avatar */}
fileInputRef.current?.click()} className="relative w-16 h-16 rounded-full overflow-hidden cursor-pointer group shrink-0" > {avatarDisplayUrl ? ( Avatar ) : (
)} {uploading && (
)}
{avatarFilename && ( )}
{/* Display Name */}
{ setDisplayName(e.target.value); setDirty(true); }} maxLength={100} placeholder="Your display name" className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-gray-200" />
{/* Bio */}