From 18f4a3bf71ee11cf667a508365bfb7c97ee278db Mon Sep 17 00:00:00 2001 From: Nikita Date: Thu, 25 Dec 2025 02:43:16 -0800 Subject: [PATCH] Update profile API to handle bio, website, and image fields; enhance profile UI with avatar, bio, and website inputs; fetch full profile data on component mount for better state management. --- packages/web/src/routes/api/profile.ts | 15 +++- packages/web/src/routes/settings.tsx | 116 +++++++++++++++++++++++-- 2 files changed, 123 insertions(+), 8 deletions(-) diff --git a/packages/web/src/routes/api/profile.ts b/packages/web/src/routes/api/profile.ts index 69387166..665edde9 100644 --- a/packages/web/src/routes/api/profile.ts +++ b/packages/web/src/routes/api/profile.ts @@ -73,6 +73,8 @@ const getProfile = async ({ request }: { request: Request }) => { email: user.email, username: user.username, image: user.image, + bio: user.bio, + website: user.website, stream: stream ? { id: stream.id, @@ -110,7 +112,13 @@ const updateProfile = async ({ request }: { request: Request }) => { try { const body = await request.json() - const { name, username } = body as { name?: string; username?: string } + const { name, username, image, bio, website } = body as { + name?: string + username?: string + image?: string | null + bio?: string | null + website?: string | null + } const database = getDb(resolveDatabaseUrl(request)) @@ -142,9 +150,12 @@ const updateProfile = async ({ request }: { request: Request }) => { } // Update user - const updates: Record = { updatedAt: new Date().toISOString() } + const updates: Record = { updatedAt: new Date().toISOString() } if (name !== undefined) updates.name = name if (username !== undefined) updates.username = username + if (image !== undefined) updates.image = image + if (bio !== undefined) updates.bio = bio + if (website !== undefined) updates.website = website await database .update(users) diff --git a/packages/web/src/routes/settings.tsx b/packages/web/src/routes/settings.tsx index 22e63885..817697c0 100644 --- a/packages/web/src/routes/settings.tsx +++ b/packages/web/src/routes/settings.tsx @@ -272,22 +272,55 @@ function PreferencesSection() { } function ProfileSection({ - profile, + profile: sessionProfile, onLogout, onChangeEmail, onChangePassword, }: { - profile: { name?: string | null; email: string; username?: string | null } | null | undefined + profile: { name?: string | null; email: string; username?: string | null; image?: string | null; bio?: string | null; website?: string | null } | null | undefined onLogout: () => Promise onChangeEmail: () => void onChangePassword: () => void }) { - const [username, setUsername] = useState(profile?.username ?? "") - const [name, setName] = useState(profile?.name ?? "") + const [loading, setLoading] = useState(true) + const [profile, setProfile] = useState(sessionProfile) + const [username, setUsername] = useState("") + const [name, setName] = useState("") + const [image, setImage] = useState("") + const [bio, setBio] = useState("") + const [website, setWebsite] = useState("") const [saving, setSaving] = useState(false) const [error, setError] = useState(null) const [saved, setSaved] = useState(false) + // Fetch full profile from API on mount + useEffect(() => { + const fetchProfile = async () => { + try { + const res = await fetch("/api/profile", { credentials: "include" }) + if (res.ok) { + const data = await res.json() + setProfile(data) + setUsername(data.username ?? "") + setName(data.name ?? "") + setImage(data.image ?? "") + setBio(data.bio ?? "") + setWebsite(data.website ?? "") + } + } catch { + // Fall back to session data + setUsername(sessionProfile?.username ?? "") + setName(sessionProfile?.name ?? "") + setImage(sessionProfile?.image ?? "") + setBio(sessionProfile?.bio ?? "") + setWebsite(sessionProfile?.website ?? "") + } finally { + setLoading(false) + } + } + fetchProfile() + }, [sessionProfile]) + const initials = useMemo(() => { if (!profile) return "G" return ( @@ -297,6 +330,8 @@ function ProfileSection({ ) }, [profile]) + const avatarUrl = image || `https://api.dicebear.com/7.x/initials/svg?seed=${username || profile?.email || "user"}` + const handleSaveProfile = async () => { setSaving(true) setError(null) @@ -306,7 +341,13 @@ function ProfileSection({ method: "PUT", headers: { "Content-Type": "application/json" }, credentials: "include", - body: JSON.stringify({ name, username: username.toLowerCase() }), + body: JSON.stringify({ + name, + username: username.toLowerCase(), + image: image || null, + bio: bio || null, + website: website || null, + }), }) const data = await res.json() if (!res.ok) { @@ -322,7 +363,27 @@ function ProfileSection({ } } - const hasChanges = username !== (profile?.username ?? "") || name !== (profile?.name ?? "") + const hasChanges = + username !== (profile?.username ?? "") || + name !== (profile?.name ?? "") || + image !== (profile?.image ?? "") || + bio !== (profile?.bio ?? "") || + website !== (profile?.website ?? "") + + if (loading) { + return ( +
+ +
+
+
+
+
+ ) + } return (
@@ -355,6 +416,29 @@ function ProfileSection({
+ {/* Profile Picture */} +
+ +
+ Profile +
+ setImage(e.target.value)} + placeholder="https://example.com/your-photo.jpg" + className="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-white placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-teal-500 text-sm" + /> +

+ Enter a URL to your profile picture +

+
+
+
+
+ +