From e07c23acebe239a4bde434e81302ca1a3cc679dd Mon Sep 17 00:00:00 2001
From: fullsizemalt <106900403+fullsizemalt@users.noreply.github.com>
Date: Mon, 29 Dec 2025 21:39:53 -0800
Subject: [PATCH] feat(social): add social handles to settings page
---
backend/routers/users.py | 19 ++++++-
backend/schemas.py | 5 ++
frontend/app/settings/page.tsx | 100 ++++++++++++++++++++++++++++++---
3 files changed, 116 insertions(+), 8 deletions(-)
diff --git a/backend/routers/users.py b/backend/routers/users.py
index 5b365ed..f6debe4 100644
--- a/backend/routers/users.py
+++ b/backend/routers/users.py
@@ -16,6 +16,11 @@ class UserProfileUpdate(BaseModel):
display_name: Optional[str] = None
avatar_bg_color: Optional[str] = None
avatar_text: Optional[str] = None
+ # Social handles
+ bluesky_handle: Optional[str] = None
+ mastodon_handle: Optional[str] = None
+ instagram_handle: Optional[str] = None
+ location: Optional[str] = None
# Preset avatar colors - Jewel Tones (Primary Set)
AVATAR_COLORS = [
@@ -46,7 +51,7 @@ def update_my_profile(
current_user: User = Depends(get_current_user),
session: Session = Depends(get_session)
):
- """Update current user's bio, avatar, and primary profile"""
+ """Update current user's bio, avatar, social handles, and primary profile"""
if update.bio is not None:
current_user.bio = update.bio
if update.avatar is not None:
@@ -61,6 +66,18 @@ def update_my_profile(
if len(update.avatar_text) <= 3 and re.match(r'^[A-Za-z0-9]*$', update.avatar_text):
current_user.avatar_text = update.avatar_text if update.avatar_text else None
+ # Social handles (simple sanitization)
+ if update.bluesky_handle is not None:
+ current_user.bluesky_handle = update.bluesky_handle.strip() or None
+ if update.mastodon_handle is not None:
+ current_user.mastodon_handle = update.mastodon_handle.strip() or None
+ if update.instagram_handle is not None:
+ # Remove @ prefix if user includes it
+ handle = update.instagram_handle.strip().lstrip('@')
+ current_user.instagram_handle = handle or None
+ if update.location is not None:
+ current_user.location = update.location.strip() or None
+
if update.username or update.display_name:
# Find or create primary profile
query = select(Profile).where(Profile.user_id == current_user.id)
diff --git a/backend/schemas.py b/backend/schemas.py
index 09830a1..fbcac53 100644
--- a/backend/schemas.py
+++ b/backend/schemas.py
@@ -21,6 +21,11 @@ class UserRead(SQLModel):
bio: Optional[str] = None
username: Optional[str] = None
joined_at: Optional[datetime] = None
+ # Social handles
+ bluesky_handle: Optional[str] = None
+ mastodon_handle: Optional[str] = None
+ instagram_handle: Optional[str] = None
+ location: Optional[str] = None
class Token(SQLModel):
access_token: str
diff --git a/frontend/app/settings/page.tsx b/frontend/app/settings/page.tsx
index 3cf96d8..6d7b4f0 100644
--- a/frontend/app/settings/page.tsx
+++ b/frontend/app/settings/page.tsx
@@ -53,6 +53,10 @@ export default function SettingsPage() {
// Profile state
const [bio, setBio] = useState("")
const [username, setUsername] = useState("")
+ const [location, setLocation] = useState("")
+ const [blueskyHandle, setBlueskyHandle] = useState("")
+ const [mastodonHandle, setMastodonHandle] = useState("")
+ const [instagramHandle, setInstagramHandle] = useState("")
const [profileSaving, setProfileSaving] = useState(false)
const [profileSaved, setProfileSaved] = useState(false)
@@ -75,6 +79,10 @@ export default function SettingsPage() {
const extUser = user as any
setBio(extUser.bio || "")
setUsername(extUser.email?.split('@')[0] || "")
+ setLocation(extUser.location || "")
+ setBlueskyHandle(extUser.bluesky_handle || "")
+ setMastodonHandle(extUser.mastodon_handle || "")
+ setInstagramHandle(extUser.instagram_handle || "")
setAvatarBgColor(extUser.avatar_bg_color || "#0F4C81")
setAvatarText(extUser.avatar_text || "")
setPrivacySettings({
@@ -96,7 +104,14 @@ export default function SettingsPage() {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`
},
- body: JSON.stringify({ bio, username })
+ body: JSON.stringify({
+ bio,
+ username,
+ location,
+ bluesky_handle: blueskyHandle,
+ mastodon_handle: mastodonHandle,
+ instagram_handle: instagramHandle
+ })
})
setProfileSaved(true)
setTimeout(() => setProfileSaved(false), 2000)
@@ -221,6 +236,14 @@ export default function SettingsPage() {
setBio={setBio}
username={username}
setUsername={setUsername}
+ location={location}
+ setLocation={setLocation}
+ blueskyHandle={blueskyHandle}
+ setBlueskyHandle={setBlueskyHandle}
+ mastodonHandle={mastodonHandle}
+ setMastodonHandle={setMastodonHandle}
+ instagramHandle={instagramHandle}
+ setInstagramHandle={setInstagramHandle}
saving={profileSaving}
saved={profileSaved}
onSave={handleSaveProfile}
@@ -285,6 +308,14 @@ export default function SettingsPage() {
setBio={setBio}
username={username}
setUsername={setUsername}
+ location={location}
+ setLocation={setLocation}
+ blueskyHandle={blueskyHandle}
+ setBlueskyHandle={setBlueskyHandle}
+ mastodonHandle={mastodonHandle}
+ setMastodonHandle={setMastodonHandle}
+ instagramHandle={instagramHandle}
+ setInstagramHandle={setInstagramHandle}
saving={profileSaving}
saved={profileSaved}
onSave={handleSaveProfile}
@@ -348,7 +379,14 @@ function SidebarLink({ icon: Icon, label, href, active }: {
}
// Profile Section
-function ProfileSection({ bio, setBio, username, setUsername, saving, saved, onSave }: any) {
+function ProfileSection({
+ bio, setBio, username, setUsername,
+ location, setLocation,
+ blueskyHandle, setBlueskyHandle,
+ mastodonHandle, setMastodonHandle,
+ instagramHandle, setInstagramHandle,
+ saving, saved, onSave
+}: any) {
return (
+ Your hometown or local music scene +
Connect your accounts (displayed on your public profile)
+