import { useSeoMeta } from '@unhead/react'; import { Link, useSearchParams } from 'react-router-dom'; import { Layout } from '@/components/Layout'; import { EditProfileForm } from '@/components/EditProfileForm'; import { RelayListManager } from '@/components/RelayListManager'; import { FollowListManager } from '@/components/FollowListManager'; import { WalletModal } from '@/components/WalletModal'; import { SecuritySettings } from '@/components/SecuritySettings'; import { RelatrSettings } from '@/components/RelatrSettings'; import { LanguageSelector } from '@/components/LanguageToggle'; import { MyDataTab } from '@/components/MyDataTab'; import { useTranslation } from '@/contexts/LanguageContext'; import { useQueryClient } from '@tanstack/react-query'; import { useNostr } from '@nostrify/react'; import { NSchema as n } from '@nostrify/nostrify'; import { useState } from 'react'; import { LoginArea } from '@/components/auth/LoginArea'; import { Button } from '@/components/ui/button'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useAppContext } from '@/hooks/useAppContext'; import { toast } from '@/hooks/useToast'; import { cn } from '@/lib/utils'; import { User, Palette, Radio, Wallet, Settings, Wrench, RotateCcw, Shield, ExternalLink, Languages, RefreshCw, AlertCircle, Database, Package, ChevronRight, ChevronLeft, } from 'lucide-react'; // ─── Types ──────────────────────────────────────────────────────────────────── type Section = | null | 'profile' | 'follows' | 'nip05' | 'security' | 'mydata' | 'language' | 'appearance' | 'relays' | 'wallet' | 'stash' | 'advanced'; // ─── Back header ────────────────────────────────────────────────────────────── function BackHeader({ title, onBack }: { title: string; onBack: () => void }) { return (

{title}

); } // ─── Menu row ──────────────────────────────────────────────────────────────── function MenuRow({ icon: Icon, label, sublabel, iconColor, iconBg, onClick, href, badge, }: { icon: React.ComponentType<{ className?: string }>; label: string; sublabel?: string; iconColor: string; iconBg: string; onClick?: () => void; href?: string; badge?: string; }) { const inner = (

{label}

{sublabel &&

{sublabel}

}
{badge && ( {badge} )}
); if (href) { return {inner}; } return ; } // ─── Menu group ─────────────────────────────────────────────────────────────── function MenuGroup({ title, children }: { title: string; children: React.ReactNode }) { return (

{title}

{children}
); } // ─── Profile sub-screen ─────────────────────────────────────────────────────── const PROFILE_FALLBACK_RELAYS = ['wss://purplepag.es', 'wss://relay.damus.io', 'wss://nos.lol']; function ProfileScreen({ onBack }: { onBack: () => void }) { const { user, metadata } = useCurrentUser(); const { nostr } = useNostr(); const queryClient = useQueryClient(); const [isRefreshing, setIsRefreshing] = useState(false); const handleRefresh = async () => { if (!user) return; setIsRefreshing(true); try { queryClient.invalidateQueries({ queryKey: ['author', user.pubkey] }); let events = await nostr.query([{ kinds: [0], authors: [user.pubkey], limit: 1 }], { signal: AbortSignal.timeout(8000) }); if (events.length === 0) { const fallback = nostr.group(PROFILE_FALLBACK_RELAYS); events = await fallback.query([{ kinds: [0], authors: [user.pubkey], limit: 1 }], { signal: AbortSignal.timeout(8000) }); } if (events.length > 0) { const profileMetadata = n.json().pipe(n.metadata()).parse(events[0].content); queryClient.setQueryData(['author', user.pubkey], { metadata: profileMetadata, event: events[0] }); toast({ title: 'Profile refreshed!', description: `Found: ${profileMetadata.name || 'unnamed'}` }); } else { toast({ title: 'Profile not found', description: 'Not found on any relay.', variant: 'destructive' }); } } catch { toast({ title: 'Refresh failed', description: 'Could not connect to relays.', variant: 'destructive' }); } finally { setIsRefreshing(false); } }; return (
{!user ? (

Log in to edit your profile

) : (
{!metadata?.name && (

Profile not loaded from relays.

)}
)}
); } // ─── NIP-05 sub-screen ──────────────────────────────────────────────────────── function Nip05Screen({ onBack }: { onBack: () => void }) { const providers = [ { name: 'nostr.directory', desc: 'Free NIP-05 addresses', url: 'https://nostr.directory', free: true }, { name: 'nip05.social', desc: 'Free verification service', url: 'https://nip05.social', free: true }, { name: 'nostrplebs.com', desc: '~$3/month · Custom domains', url: 'https://nostrplebs.com', free: false }, { name: 'nostr.band', desc: 'One-time fee · Analytics included', url: 'https://nostr.band', free: false }, ]; return (

Get a verified Nostr address (like name@domain.com) to build trust. Required for creating events, coaching profiles, and shops.

{providers.map((p) => (

{p.name}

{p.desc}

{p.free ? 'Free' : 'Paid'}
))}
After getting your NIP-05, add it in the Profile field and save.
); } // ─── Relays sub-screen ──────────────────────────────────────────────────────── function RelaysScreen({ onBack }: { onBack: () => void }) { const { user } = useCurrentUser(); const { config, updateConfig } = useAppContext(); const useSkateRelay = config.useSkateRelay ?? true; const handleToggle = () => { updateConfig((current) => ({ ...current, useSkateRelay: !useSkateRelay })); toast({ title: useSkateRelay ? 'Community relay disabled' : 'Community relay enabled', description: useSkateRelay ? 'Using only your configured relays.' : 'SkateSpots relay included.', }); }; return (
{user && (

Community Relay

{useSkateRelay ? 'Syncing with global skate community' : 'Using only personal relays'}

)}
); } // ─── Appearance sub-screen ──────────────────────────────────────────────────── function AppearanceScreen({ onBack }: { onBack: () => void }) { const { t } = useTranslation(); return (

Language

🌙

Dark Mode Only

SkateSpots runs a dark neon aesthetic inspired by skate culture. Black background, neon pink, lime, and orange.

); } // ─── Advanced sub-screen ────────────────────────────────────────────────────── function AdvancedScreen({ onBack }: { onBack: () => void }) { const handleResetSasquatch = () => { localStorage.removeItem('sasquatch-minimized'); alert('The Sasquatch has been expanded! 🦧 Refresh the page to see him.'); window.location.reload(); }; return (

🦧 Sasquatch Guide

Reset the onboarding guide to expanded view

); } // ─── Wallet sub-screen ──────────────────────────────────────────────────────── function WalletScreen({ onBack }: { onBack: () => void }) { const { user } = useCurrentUser(); return (
{!user ? (

Log in to manage your wallet

) : (

Open Wallet

Balance, send, receive

)}
); } // ─── Main settings menu ─────────────────────────────────────────────────────── function SettingsMenu({ onNavigate }: { onNavigate: (s: Section) => void }) { const { user, metadata } = useCurrentUser(); return (
{/* Account */} onNavigate('profile')} /> onNavigate('security')} /> onNavigate('nip05')} /> {/* App */} onNavigate('wallet')} /> onNavigate('relays')} /> onNavigate('appearance')} /> {/* Data & Advanced */} onNavigate('mydata')} /> onNavigate('advanced')} /> {/* Login area */} {!user && (
)}
); } // ─── Page ───────────────────────────────────────────────────────────────────── export default function SettingsPage() { useSeoMeta({ title: 'Settings - SkateSpots', description: 'Manage your SkateSpots account settings.', }); const [searchParams] = useSearchParams(); // Allow deep-linking into a specific section via ?section=profile etc. const initialSection = (searchParams.get('section') as Section) ?? null; const [section, setSection] = useState
(initialSection); const handleNavigate = (s: Section) => setSection(s); const handleBack = () => setSection(null); const sectionTitles: Record, string> = { profile: 'Edit Profile', follows: 'Follows', nip05: 'NIP-05 Verification', security: 'Security', mydata: 'My Data', language: 'Language', appearance: 'Appearance', relays: 'Relays', wallet: 'Wallet', stash: 'My Stash', advanced: 'Advanced', }; return (
{/* Header — only shown on the main menu */} {!section && (

SETTINGS

Account and app preferences

)} {/* Sub-screens */} {section === 'profile' && } {section === 'security' && (
)} {section === 'nip05' && } {section === 'mydata' && (
)} {section === 'relays' && } {section === 'appearance' && } {section === 'wallet' && } {section === 'advanced' && } {/* Main menu */} {!section && }
); }