feat(social): add social handles to settings page
Some checks failed
Deploy Fediversion / deploy (push) Failing after 1s

This commit is contained in:
fullsizemalt 2025-12-29 21:39:53 -08:00
parent a87c0cc8a3
commit e07c23aceb
3 changed files with 116 additions and 8 deletions

View file

@ -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)

View file

@ -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

View file

@ -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 (
<Card id="profile">
<CardHeader>
@ -375,13 +413,16 @@ function ProfileSection({ bio, setBio, username, setUsername, saving, saved, onS
</p>
</div>
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Label htmlFor="location">Location / Local Scene</Label>
<Input
id="email"
disabled
value="(cannot be changed)"
className="bg-muted"
id="location"
placeholder="e.g. Portland, OR"
value={location}
onChange={(e) => setLocation(e.target.value)}
/>
<p className="text-xs text-muted-foreground">
Your hometown or local music scene
</p>
</div>
</div>
@ -399,6 +440,51 @@ function ProfileSection({ bio, setBio, username, setUsername, saving, saved, onS
</p>
</div>
<Separator />
{/* Social Handles Section */}
<div className="space-y-4">
<div>
<h4 className="font-medium text-sm mb-1">Social Links</h4>
<p className="text-xs text-muted-foreground">Connect your accounts (displayed on your public profile)</p>
</div>
<div className="grid gap-4 md:grid-cols-3">
<div className="space-y-2">
<Label htmlFor="bluesky" className="flex items-center gap-2">
<span className="text-sky-500">🦋</span> BlueSky
</Label>
<Input
id="bluesky"
placeholder="handle.bsky.social"
value={blueskyHandle}
onChange={(e) => setBlueskyHandle(e.target.value)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="mastodon" className="flex items-center gap-2">
<span className="text-purple-500">🐘</span> Mastodon
</Label>
<Input
id="mastodon"
placeholder="@user@instance.social"
value={mastodonHandle}
onChange={(e) => setMastodonHandle(e.target.value)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="instagram" className="flex items-center gap-2">
<span className="text-pink-500">📷</span> Instagram
</Label>
<Input
id="instagram"
placeholder="@username"
value={instagramHandle}
onChange={(e) => setInstagramHandle(e.target.value)}
/>
</div>
</div>
</div>
<div className="flex justify-end">
<Button onClick={onSave} disabled={saving}>
{saving ? "Saving..." : saved ? "Saved ✓" : "Save Changes"}