"use client" import { useState, useEffect } from "react" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Label } from "@/components/ui/label" import { Switch } from "@/components/ui/switch" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { Button } from "@/components/ui/button" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Separator } from "@/components/ui/separator" import { usePreferences } from "@/contexts/preferences-context" import { useAuth } from "@/contexts/auth-context" import { getApiUrl } from "@/lib/api-config" import { UserAvatar } from "@/components/ui/user-avatar" import { User, Palette, Bell, Eye, Shield, Sparkles, Check, ArrowLeft, Lock } from "lucide-react" import Link from "next/link" // Avatar color palette - Jewel Tones with XP unlock tiers const PRESET_COLORS = [ // Starter Tier (0 XP) - Always unlocked { value: "#0F4C81", name: "Sapphire", tier: "Starter", xpRequired: 0 }, { value: "#9B111E", name: "Ruby", tier: "Starter", xpRequired: 0 }, { value: "#50C878", name: "Emerald", tier: "Starter", xpRequired: 0 }, { value: "#9966CC", name: "Amethyst", tier: "Starter", xpRequired: 0 }, // Bronze Tier (100 XP) { value: "#0D98BA", name: "Topaz", tier: "Bronze", xpRequired: 100 }, { value: "#E0115F", name: "Rose Quartz", tier: "Bronze", xpRequired: 100 }, { value: "#082567", name: "Lapis", tier: "Bronze", xpRequired: 100 }, { value: "#FF7518", name: "Carnelian", tier: "Bronze", xpRequired: 100 }, // Silver Tier (500 XP) { value: "#006B3C", name: "Jade", tier: "Silver", xpRequired: 500 }, { value: "#1C1C1C", name: "Onyx", tier: "Silver", xpRequired: 500 }, // Gold Tier (1000 XP) { value: "#E6E200", name: "Citrine", tier: "Gold", xpRequired: 1000 }, { value: "#702963", name: "Garnet", tier: "Gold", xpRequired: 1000 }, ] export default function SettingsPage() { const { preferences, updatePreferences, loading } = usePreferences() const { user, refreshUser } = useAuth() // 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) // Avatar state const [avatarBgColor, setAvatarBgColor] = useState("#0F4C81") const [avatarText, setAvatarText] = useState("") const [avatarSaving, setAvatarSaving] = useState(false) const [avatarSaved, setAvatarSaved] = useState(false) const [avatarError, setAvatarError] = useState("") // Privacy state const [privacySettings, setPrivacySettings] = useState({ profile_public: true, show_attendance_public: true, appear_in_leaderboards: true }) useEffect(() => { if (user) { 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({ profile_public: extUser.profile_public ?? true, show_attendance_public: extUser.show_attendance_public ?? true, appear_in_leaderboards: extUser.appear_in_leaderboards ?? true }) } }, [user]) const handleSaveProfile = async () => { setProfileSaving(true) setProfileSaved(false) const token = localStorage.getItem("token") try { await fetch(`${getApiUrl()}/users/me`, { method: "PATCH", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }, body: JSON.stringify({ bio, username, location, bluesky_handle: blueskyHandle, mastodon_handle: mastodonHandle, instagram_handle: instagramHandle }) }) setProfileSaved(true) setTimeout(() => setProfileSaved(false), 2000) } catch (e) { console.error(e) } finally { setProfileSaving(false) } } const handleAvatarTextChange = (value: string) => { const cleaned = value.replace(/[^A-Za-z0-9]/g, '').slice(0, 3).toUpperCase() setAvatarText(cleaned) setAvatarError("") } const handleSaveAvatar = async () => { setAvatarSaving(true) setAvatarSaved(false) setAvatarError("") try { const token = localStorage.getItem("token") const res = await fetch(`${getApiUrl()}/users/me/avatar`, { method: "PATCH", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify({ bg_color: avatarBgColor, text: avatarText || null, }), }) if (!res.ok) { const data = await res.json() throw new Error(data.detail || "Failed to save") } setAvatarSaved(true) refreshUser?.() setTimeout(() => setAvatarSaved(false), 2000) } catch (e: any) { setAvatarError(e.message || "Failed to save avatar") } finally { setAvatarSaving(false) } } const handlePrivacyChange = async (key: string, value: boolean) => { // Optimistic update setPrivacySettings(prev => ({ ...prev, [key]: value })) try { const token = localStorage.getItem("token") await fetch(`${getApiUrl()}/users/me/privacy`, { method: "PATCH", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify({ [key]: value }), }) } catch (e) { // Revert on error setPrivacySettings(prev => ({ ...prev, [key]: !value })) console.error("Failed to update privacy setting:", e) } } if (loading) { return (
You need to be logged in to access settings.
Manage your account and preferences
This will be shown on your profile and comments
Your hometown or local music scene
Connect your accounts (displayed on your public profile)
Leave empty to show first letter of username
Earn XP by attending shows, writing reviews, and rating performances
{error}
)}Account deletion is permanent and cannot be undone.
{description}