import { useEffect, useMemo, useState, type FormEvent, type ReactNode } from "react" import { createFileRoute } from "@tanstack/react-router" import { authClient } from "@/lib/auth-client" import SettingsPanel from "@/components/Settings-panel" import { Check, ChevronDown, LogOut, Sparkles, UserRoundPen, MessageCircle, HelpCircle, Copy, ExternalLink, } from "lucide-react" type SectionId = "preferences" | "profile" | "streaming" | "billing" const PLAN_CARD_NOISE = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160' viewBox='0 0 160 160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.18'/%3E%3C/svg%3E" export const Route = createFileRoute("/settings")({ component: SettingsPage, ssr: false, }) // Feature flag: enable billing section const BILLING_ENABLED = false type Option = { value: string; label: string } function InlineSelect({ value, options, onChange, }: { value: string options: Option[] onChange: (value: string) => void }) { return (
) } function ToggleSwitch({ checked, onChange, }: { checked: boolean onChange: (next: boolean) => void }) { return ( ) } function SettingRow({ title, description, control, }: { title: string description: string control?: ReactNode }) { return (

{title}

{description}

{control ?
{control}
: null}
) } function SettingCard({ title, children, }: { title: string children: ReactNode }) { return (

{title}

{children}
) } function Modal({ title, description, onClose, children, }: { title: string description?: string onClose: () => void children: ReactNode }) { return (
e.stopPropagation()} >

{title}

{description ? (

{description}

) : null}
{children}
) } function SectionHeader({ title, description, }: { title: string description?: string }) { return (

{title}

{description ? (

{description}

) : null}
) } function PreferencesSection() { const [theme, setTheme] = useState("Dark") const [autoplay, setAutoplay] = useState(true) const [lowLatency, setLowLatency] = useState(true) const [chatTimestamps, setChatTimestamps] = useState(false) return (
} /> } /> } /> } />
) } function ProfileSection({ profile: sessionProfile, onLogout, onChangeEmail, }: { profile: { name?: string | null; email: string; username?: string | null; image?: string | null; bio?: string | null; website?: string | null } | null | undefined onLogout: () => Promise onChangeEmail: () => void }) { const [loading, setLoading] = useState(true) const [profile, setProfile] = useState(sessionProfile) const [username, setUsername] = useState("") const [name, setName] = useState("") const [image, setImage] = useState("") const [bio, setBio] = useState("") const [website, setWebsite] = useState("") const [saving, setSaving] = useState(false) const [error, setError] = useState(null) const [saved, setSaved] = useState(false) // Fetch full profile from API on mount useEffect(() => { const fetchProfile = async () => { try { const res = await fetch("/api/profile", { credentials: "include" }) if (res.ok) { const data = await res.json() setProfile(data) setUsername(data.username ?? "") setName(data.name ?? "") setImage(data.image ?? "") setBio(data.bio ?? "") setWebsite(data.website ?? "") } } catch { // Fall back to session data setUsername(sessionProfile?.username ?? "") setName(sessionProfile?.name ?? "") setImage(sessionProfile?.image ?? "") setBio(sessionProfile?.bio ?? "") setWebsite(sessionProfile?.website ?? "") } finally { setLoading(false) } } fetchProfile() }, [sessionProfile]) const initials = useMemo(() => { if (!profile) return "G" return ( profile.name?.slice(0, 1) ?? profile.email?.slice(0, 1)?.toUpperCase() ?? "G" ) }, [profile]) const avatarUrl = image || `https://api.dicebear.com/7.x/initials/svg?seed=${username || profile?.email || "user"}` const handleSaveProfile = async () => { setSaving(true) setError(null) setSaved(false) try { const res = await fetch("/api/profile", { method: "PUT", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ name, username: username.toLowerCase(), image: image || null, bio: bio || null, website: website || null, }), }) const data = await res.json() if (!res.ok) { setError(data.error || "Failed to save") } else { setSaved(true) setTimeout(() => setSaved(false), 2000) } } catch { setError("Network error") } finally { setSaving(false) } } const hasChanges = username !== (profile?.username ?? "") || name !== (profile?.name ?? "") || image !== (profile?.image ?? "") || bio !== (profile?.bio ?? "") || website !== (profile?.website ?? "") if (loading) { return (
) } return (
{initials}

{profile?.name ?? "Guest user"}

{profile?.email ?? "-"}

{/* Profile Picture */}
Profile
setImage(e.target.value)} placeholder="https://example.com/your-photo.jpg" className="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-white placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-teal-500 text-sm" />

Enter a URL to your profile picture

setName(e.target.value)} placeholder="Your name" className="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-white placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-teal-500" />
linsa.io/ setUsername(e.target.value.toLowerCase().replace(/[^a-z0-9_-]/g, ""))} placeholder="username" className="flex-1 bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-white placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-teal-500" />

This is your public stream URL. Only lowercase letters, numbers, hyphens, and underscores.