feat(social): add social handles to settings page
Some checks failed
Deploy Fediversion / deploy (push) Failing after 1s
Some checks failed
Deploy Fediversion / deploy (push) Failing after 1s
This commit is contained in:
parent
a87c0cc8a3
commit
e07c23aceb
3 changed files with 116 additions and 8 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue