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 ( @@ -375,13 +413,16 @@ function ProfileSection({ bio, setBio, username, setUsername, saving, saved, onS

- + setLocation(e.target.value)} /> +

+ Your hometown or local music scene +

@@ -399,6 +440,51 @@ function ProfileSection({ bio, setBio, username, setUsername, saving, saved, onS

+ + + {/* Social Handles Section */} +
+
+

Social Links

+

Connect your accounts (displayed on your public profile)

+
+
+
+ + setBlueskyHandle(e.target.value)} + /> +
+
+ + setMastodonHandle(e.target.value)} + /> +
+
+ + setInstagramHandle(e.target.value)} + /> +
+
+
+