Added auth.nostr1.com and wotr.relatr.xyz back as default relays. All 5 relays are now the default for all new users: - wss://auth.nostr1.com (auth) - wss://wotr.relatr.xyz (web of trust) - wss://relay.ditto.pub (general) - wss://goskatespots.nostr1.com (community - toggleable) - wss://purplepag.es (profiles - read only) Updated across: App.tsx, NostrProvider.tsx, NostrSync.tsx, TestApp.tsx Updated Settings page Default Relays info cards to show all 5 with descriptions. Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
676 lines
27 KiB
TypeScript
676 lines
27 KiB
TypeScript
import { useSeoMeta } from '@unhead/react';
|
|
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 { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
|
import { useAppContext } from '@/hooks/useAppContext';
|
|
import { toast } from '@/hooks/useToast';
|
|
|
|
import {
|
|
User,
|
|
Palette,
|
|
Radio,
|
|
Wallet,
|
|
Settings,
|
|
Wrench,
|
|
RotateCcw,
|
|
Shield,
|
|
ExternalLink,
|
|
Languages,
|
|
RefreshCw,
|
|
AlertCircle,
|
|
Database,
|
|
} from 'lucide-react';
|
|
|
|
// Fallback relays for fetching profile data
|
|
const PROFILE_FALLBACK_RELAYS = [
|
|
'wss://purplepag.es',
|
|
'wss://relay.damus.io',
|
|
'wss://nos.lol',
|
|
];
|
|
|
|
function ProfileTab() {
|
|
const { user, metadata } = useCurrentUser();
|
|
const { nostr } = useNostr();
|
|
const queryClient = useQueryClient();
|
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
|
|
const handleRefreshProfile = async () => {
|
|
if (!user) return;
|
|
|
|
setIsRefreshing(true);
|
|
console.log('[ProfileTab] Manual refresh for pubkey:', user.pubkey.slice(0, 8) + '...');
|
|
|
|
try {
|
|
// First invalidate the cache
|
|
queryClient.invalidateQueries({ queryKey: ['author', user.pubkey] });
|
|
|
|
// Try main relays
|
|
let events = await nostr.query(
|
|
[{ kinds: [0], authors: [user.pubkey], limit: 1 }],
|
|
{ signal: AbortSignal.timeout(8000) }
|
|
);
|
|
|
|
console.log('[ProfileTab] Main relays returned', events.length, 'events');
|
|
|
|
// If not found, try fallback relays
|
|
if (events.length === 0) {
|
|
console.log('[ProfileTab] Trying fallback relays...');
|
|
const fallbackGroup = nostr.group(PROFILE_FALLBACK_RELAYS);
|
|
events = await fallbackGroup.query(
|
|
[{ kinds: [0], authors: [user.pubkey], limit: 1 }],
|
|
{ signal: AbortSignal.timeout(8000) }
|
|
);
|
|
console.log('[ProfileTab] Fallback relays returned', events.length, 'events');
|
|
}
|
|
|
|
if (events.length > 0) {
|
|
const event = events[0];
|
|
try {
|
|
const profileMetadata = n.json().pipe(n.metadata()).parse(event.content);
|
|
queryClient.setQueryData(['author', user.pubkey], { metadata: profileMetadata, event });
|
|
toast({
|
|
title: 'Profile refreshed!',
|
|
description: `Found profile: ${profileMetadata.name || 'unnamed'}`,
|
|
});
|
|
} catch {
|
|
toast({
|
|
title: 'Profile found',
|
|
description: 'But metadata could not be parsed.',
|
|
variant: 'destructive',
|
|
});
|
|
}
|
|
} else {
|
|
toast({
|
|
title: 'Profile not found',
|
|
description: 'Your profile (kind 0 event) was not found on any relay. You may need to publish it.',
|
|
variant: 'destructive',
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('[ProfileTab] Refresh failed:', error);
|
|
toast({
|
|
title: 'Refresh failed',
|
|
description: 'Could not connect to relays. Check your internet connection.',
|
|
variant: 'destructive',
|
|
});
|
|
} finally {
|
|
setIsRefreshing(false);
|
|
}
|
|
};
|
|
|
|
if (!user) {
|
|
return (
|
|
<Card className="border-dashed">
|
|
<CardContent className="py-12 text-center">
|
|
<User className="h-12 w-12 mx-auto mb-4 text-muted-foreground/50" />
|
|
<h3 className="text-lg font-medium mb-2">Login Required</h3>
|
|
<p className="text-muted-foreground mb-6">
|
|
Log in to edit your profile.
|
|
</p>
|
|
<LoginArea className="justify-center" />
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Profile sync status - show if no metadata found */}
|
|
{!metadata?.name && (
|
|
<Card className="bg-yellow-950/30 border-2 border-yellow-500/50">
|
|
<CardContent className="py-4">
|
|
<div className="flex items-center gap-3">
|
|
<AlertCircle className="h-5 w-5 text-yellow-500 flex-shrink-0" />
|
|
<div className="flex-1">
|
|
<p className="text-sm text-yellow-200">
|
|
<strong>Profile not loaded.</strong> Your profile data wasn't found on the configured relays.
|
|
Try refreshing or make sure you've published your profile.
|
|
</p>
|
|
</div>
|
|
<Button
|
|
onClick={handleRefreshProfile}
|
|
disabled={isRefreshing}
|
|
size="sm"
|
|
variant="outline"
|
|
className="border-yellow-500/50 text-yellow-400 hover:bg-yellow-500/10 flex-shrink-0"
|
|
>
|
|
{isRefreshing ? (
|
|
<RefreshCw className="h-4 w-4 animate-spin" />
|
|
) : (
|
|
<RefreshCw className="h-4 w-4" />
|
|
)}
|
|
<span className="ml-2 hidden sm:inline">Refresh</span>
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
<EditProfileForm />
|
|
|
|
{/* Following & Followers */}
|
|
<FollowListManager />
|
|
|
|
{/* NIP-05 Providers Info */}
|
|
<Card className="bg-blue-950/30 border-2 border-blue-500/50">
|
|
<CardHeader>
|
|
<CardTitle className="text-blue-400">NIP-05 Verification Providers</CardTitle>
|
|
<CardDescription className="text-gray-400">
|
|
Get a verified Nostr address (like email@domain.com) to build trust. Required for creating events, coaching, and shops.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="grid md:grid-cols-2 gap-4">
|
|
{/* Free Providers */}
|
|
<div className="space-y-3">
|
|
<h4 className="font-bold text-green-400 uppercase text-sm flex items-center gap-2">
|
|
<span>✅ Free Options</span>
|
|
</h4>
|
|
<a href="https://nostr.directory" target="_blank" rel="noopener noreferrer" className="block p-3 rounded-lg bg-black/50 border border-green-500/30 hover:border-green-500/60 transition-all">
|
|
<div className="font-bold text-white">nostr.directory</div>
|
|
<div className="text-xs text-gray-400">Free NIP-05 addresses</div>
|
|
</a>
|
|
<a href="https://nip05.social" target="_blank" rel="noopener noreferrer" className="block p-3 rounded-lg bg-black/50 border border-green-500/30 hover:border-green-500/60 transition-all">
|
|
<div className="font-bold text-white">nip05.social</div>
|
|
<div className="text-xs text-gray-400">Free verification service</div>
|
|
</a>
|
|
</div>
|
|
|
|
{/* Paid Providers */}
|
|
<div className="space-y-3">
|
|
<h4 className="font-bold text-yellow-400 uppercase text-sm flex items-center gap-2">
|
|
<span>💎 Premium Options</span>
|
|
</h4>
|
|
<a href="https://nostrplebs.com" target="_blank" rel="noopener noreferrer" className="block p-3 rounded-lg bg-black/50 border border-yellow-500/30 hover:border-yellow-500/60 transition-all">
|
|
<div className="font-bold text-white">nostrplebs.com</div>
|
|
<div className="text-xs text-gray-400">~$3/month • Custom domains</div>
|
|
</a>
|
|
<a href="https://nostr.band" target="_blank" rel="noopener noreferrer" className="block p-3 rounded-lg bg-black/50 border border-yellow-500/30 hover:border-yellow-500/60 transition-all">
|
|
<div className="font-bold text-white">nostr.band</div>
|
|
<div className="text-xs text-gray-400">One-time fee • Analytics included</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="p-3 rounded-lg bg-yellow-950/30 border border-yellow-500/30 text-sm text-gray-300">
|
|
<p><strong className="text-yellow-400">💡 Tip:</strong> After getting your NIP-05, add it to your profile in the "NIP-05" field above and save.</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function AppearanceTab() {
|
|
const { t } = useTranslation();
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Language Settings */}
|
|
<Card className="bg-black/90 border-2 border-[#ccff00]/30">
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 text-white">
|
|
<Languages className="h-5 w-5 text-[#ccff00]" />
|
|
{t('settings.language')}
|
|
</CardTitle>
|
|
<CardDescription className="text-gray-400">
|
|
{t('settings.selectLanguage')}
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<LanguageSelector />
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Theme Settings */}
|
|
<Card className="bg-black/90 border-2 border-[#ff0040]/30">
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 text-white">
|
|
<Palette className="h-5 w-5 text-[#ff0040]" />
|
|
{t('settings.appearance')}
|
|
</CardTitle>
|
|
<CardDescription className="text-gray-400">
|
|
SkateSpots uses a dark neon aesthetic inspired by skate culture.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="p-6 rounded-lg bg-gradient-to-br from-[#ff0040]/10 to-[#ccff00]/10 border-2 border-[#ff0040]/30 text-center">
|
|
<div className="text-4xl mb-3">🌙</div>
|
|
<p className="font-bold text-white mb-2 uppercase tracking-wide">Dark Mode Only</p>
|
|
<p className="text-sm text-gray-300 leading-relaxed">
|
|
SkateSpots is designed with a dark neon aesthetic inspired by Tony Hawk's Underground 2 and skate culture.
|
|
The black background with neon pink, lime, and orange creates the signature skateboarding vibe.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="text-xs text-gray-400 italic text-center">
|
|
💡 Embrace the darkness - that's where skaters thrive
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function RelaysTab() {
|
|
const { user } = useCurrentUser();
|
|
const { config, updateConfig } = useAppContext();
|
|
const useSkateRelay = config.useSkateRelay ?? true;
|
|
|
|
const handleToggleCommunityRelay = () => {
|
|
updateConfig((current) => ({
|
|
...current,
|
|
useSkateRelay: !useSkateRelay,
|
|
}));
|
|
toast({
|
|
title: useSkateRelay ? 'Community relay disabled' : 'Community relay enabled',
|
|
description: useSkateRelay
|
|
? 'You will now only use your configured relays for skate content.'
|
|
: 'The SkateSpots community relay will be included for skate content.',
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Community Relay Setting - Only shown when logged in */}
|
|
{user && (
|
|
<Card className="bg-gradient-to-br from-[#ff0040]/10 to-[#ccff00]/10 border-2 border-[#ccff00]/50">
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 text-[#ccff00]">
|
|
🛹 Community Relay
|
|
</CardTitle>
|
|
<CardDescription className="text-gray-300">
|
|
Control whether to include the SkateSpots community relay for skate-specific content.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="flex items-center justify-between p-4 rounded-lg bg-black/50 border border-[#ccff00]/30">
|
|
<div className="flex-1">
|
|
<p className="font-bold text-white">Use Community Relay</p>
|
|
<p className="text-sm text-gray-400 mt-1">
|
|
{useSkateRelay
|
|
? 'Enabled - Your content syncs with the global skate community'
|
|
: 'Disabled - Using only your personal relays'}
|
|
</p>
|
|
</div>
|
|
<Button
|
|
variant={useSkateRelay ? 'default' : 'outline'}
|
|
onClick={handleToggleCommunityRelay}
|
|
className={useSkateRelay
|
|
? 'bg-[#ccff00] text-black hover:bg-[#ccff00]/80'
|
|
: 'border-gray-600 text-gray-400 hover:bg-gray-800'}
|
|
>
|
|
{useSkateRelay ? 'Enabled' : 'Disabled'}
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="p-3 rounded-lg bg-blue-950/30 border border-blue-500/30">
|
|
<p className="text-sm text-blue-200">
|
|
<strong>💡 What does this do?</strong>
|
|
</p>
|
|
<ul className="text-sm text-gray-300 mt-2 space-y-1 list-disc list-inside">
|
|
<li><strong>Enabled (default):</strong> Your spots, events, and profiles sync with the global SkateSpots community</li>
|
|
<li><strong>Disabled:</strong> Content only goes to your personal relays - you're fully decentralized but may not see all community content</li>
|
|
</ul>
|
|
</div>
|
|
|
|
{!useSkateRelay && (
|
|
<div className="p-3 rounded-lg bg-yellow-950/30 border border-yellow-500/30">
|
|
<p className="text-sm text-yellow-200">
|
|
<strong>⚠️ Note:</strong> With the community relay disabled, you'll only see skate content from relays you've configured below. Make sure you have at least one relay set up!
|
|
</p>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Radio className="h-5 w-5" />
|
|
Nostr Relays
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Manage which relays you connect to for sending and receiving data.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<RelayListManager />
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Default Relays Explanation */}
|
|
<Card className="bg-blue-950/30 border-2 border-blue-500/50">
|
|
<CardHeader>
|
|
<CardTitle className="text-blue-400">Default Relays</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="space-y-3">
|
|
{/* Community Relay */}
|
|
<div className="p-4 rounded-lg bg-blue-900/20 border border-blue-500/30">
|
|
<div className="flex items-start gap-2 mb-2">
|
|
<div className="text-lg">🛹</div>
|
|
<div className="flex-1">
|
|
<p className="font-bold text-white">goskatespots.nostr1.com</p>
|
|
<p className="text-xs text-gray-400 font-mono mt-1">wss://goskatespots.nostr1.com</p>
|
|
</div>
|
|
</div>
|
|
<p className="text-sm text-gray-300 leading-relaxed">
|
|
The dedicated SkateSpots community relay. Custom policies for spots, events, coaches, filmers, and shops. Toggle it on/off above.
|
|
</p>
|
|
<div className="mt-3 p-3 rounded bg-yellow-950/30 border border-yellow-500/30">
|
|
<p className="text-xs text-yellow-200">
|
|
<strong>⚠️ Allowlisted:</strong> Your npub must be approved to publish here. DM the relay owner to request access.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Auth relay */}
|
|
<div className="p-4 rounded-lg bg-green-900/20 border border-green-500/30">
|
|
<div className="flex items-start gap-2 mb-2">
|
|
<div className="text-lg">🔐</div>
|
|
<div className="flex-1">
|
|
<p className="font-bold text-white">auth.nostr1.com</p>
|
|
<p className="text-xs text-gray-400 font-mono mt-1">wss://auth.nostr1.com</p>
|
|
</div>
|
|
</div>
|
|
<p className="text-sm text-gray-300 leading-relaxed">
|
|
Authentication relay for Nostr identity verification and NIP-42 auth flows.
|
|
</p>
|
|
</div>
|
|
|
|
{/* WoTR relay */}
|
|
<div className="p-4 rounded-lg bg-purple-900/20 border border-purple-500/30">
|
|
<div className="flex items-start gap-2 mb-2">
|
|
<div className="text-lg">🛡️</div>
|
|
<div className="flex-1">
|
|
<p className="font-bold text-white">wotr.relatr.xyz</p>
|
|
<p className="text-xs text-gray-400 font-mono mt-1">wss://wotr.relatr.xyz</p>
|
|
</div>
|
|
</div>
|
|
<p className="text-sm text-gray-300 leading-relaxed">
|
|
Relatr's Web of Trust relay. Powers trust scores and broader Nostr network connectivity.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Ditto relay */}
|
|
<div className="p-4 rounded-lg bg-orange-900/20 border border-orange-500/30">
|
|
<div className="flex items-start gap-2 mb-2">
|
|
<div className="text-lg">🌐</div>
|
|
<div className="flex-1">
|
|
<p className="font-bold text-white">relay.ditto.pub</p>
|
|
<p className="text-xs text-gray-400 font-mono mt-1">wss://relay.ditto.pub</p>
|
|
</div>
|
|
</div>
|
|
<p className="text-sm text-gray-300 leading-relaxed">
|
|
General purpose relay with broad Nostr network coverage for maximum reach.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Profile Aggregator */}
|
|
<div className="p-4 rounded-lg bg-gray-900/20 border border-gray-500/30">
|
|
<div className="flex items-start gap-2 mb-2">
|
|
<div className="text-lg">👤</div>
|
|
<div className="flex-1">
|
|
<p className="font-bold text-white">purplepag.es</p>
|
|
<p className="text-xs text-gray-400 font-mono mt-1">wss://purplepag.es</p>
|
|
<span className="text-[10px] bg-gray-700 text-gray-300 px-1.5 py-0.5 rounded mt-1 inline-block">Read only</span>
|
|
</div>
|
|
</div>
|
|
<p className="text-sm text-gray-300 leading-relaxed">
|
|
Profile aggregator that indexes Nostr profiles from across the network. Used for fast profile lookups and discovery.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<p className="text-gray-400 text-sm">
|
|
Your content publishes to all write-enabled relays for maximum reliability. You can customise your relay list above using the relay manager.
|
|
</p>
|
|
|
|
<div className="p-3 rounded-lg bg-green-950/30 border border-green-500/30">
|
|
<p className="text-sm text-green-200">
|
|
<strong>💡 Want to run your own relay?</strong> Check out{' '}
|
|
<a
|
|
href="https://relay.tools"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-[#ccff00] hover:text-[#ccff00]/80 font-bold underline"
|
|
>
|
|
relay.tools
|
|
</a>
|
|
{' '}to easily spin up and manage your own Nostr relay!
|
|
</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function WalletTab() {
|
|
const { user } = useCurrentUser();
|
|
|
|
if (!user) {
|
|
return (
|
|
<Card className="border-dashed">
|
|
<CardContent className="py-12 text-center">
|
|
<Wallet className="h-12 w-12 mx-auto mb-4 text-muted-foreground/50" />
|
|
<h3 className="text-lg font-medium mb-2">Login Required</h3>
|
|
<p className="text-muted-foreground mb-6">
|
|
Log in to manage your wallet.
|
|
</p>
|
|
<LoginArea className="justify-center" />
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Redirect to Wallet Page */}
|
|
<Card className="bg-gradient-to-br from-orange-950/30 to-yellow-950/30 border-2 border-orange-500/50">
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 text-white">
|
|
<Wallet className="h-5 w-5 text-orange-400" />
|
|
Your Wallet
|
|
</CardTitle>
|
|
<CardDescription className="text-gray-300">
|
|
Manage your Cashu wallet and Lightning connections from the dedicated wallet page.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<a href="/wallet">
|
|
<Button className="gap-2 bg-orange-500 hover:bg-orange-600 w-full sm:w-auto">
|
|
<Wallet className="h-4 w-4" />
|
|
Go to Wallet
|
|
</Button>
|
|
</a>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* NWC Quick Connect */}
|
|
<Card className="bg-purple-950/30 border-2 border-purple-500/50">
|
|
<CardHeader>
|
|
<CardTitle className="text-purple-400 flex items-center gap-2">
|
|
<Shield className="h-5 w-5" />
|
|
Connect External Wallet (NWC)
|
|
</CardTitle>
|
|
<CardDescription className="text-gray-400">
|
|
Connect your existing Lightning wallet using Nostr Wallet Connect for one-click zaps.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<WalletModal>
|
|
<Button variant="outline" className="gap-2 border-purple-500/50 text-purple-400 hover:bg-purple-500/10 w-full sm:w-auto">
|
|
<ExternalLink className="h-4 w-4" />
|
|
Connect with NWC
|
|
</Button>
|
|
</WalletModal>
|
|
|
|
<div className="p-3 bg-black/50 rounded-lg border border-gray-700">
|
|
<p className="text-sm text-gray-400">
|
|
<strong className="text-gray-300">What is NWC?</strong> Nostr Wallet Connect lets you link your Lightning wallet (like Alby, Minibits, Zeus, or Coinos) to send zaps without copy-pasting invoices.
|
|
</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function AdvancedTab() {
|
|
const handleResetSasquatch = () => {
|
|
// Clear minimized state so Sasquatch appears expanded
|
|
localStorage.removeItem('sasquatch-minimized');
|
|
|
|
// Show confirmation and reload
|
|
alert('The Sasquatch has been expanded! 🦧 Refresh the page to see him.');
|
|
window.location.reload();
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Relatr Integration */}
|
|
<RelatrSettings />
|
|
|
|
{/* Sasquatch Guide */}
|
|
<Card className="bg-amber-950/30 border-2 border-amber-500/50">
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 text-amber-400">
|
|
🦧 The Sasquatch Guide
|
|
</CardTitle>
|
|
<CardDescription className="text-gray-400">
|
|
The Sasquatch is your onboarding guide - always available on home and onboarding pages.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="p-4 rounded-lg bg-black/50 border border-amber-500/30">
|
|
<p className="text-sm text-gray-300 mb-4">
|
|
The Sasquatch appears as a minimizable button in the bottom-left corner. Click to expand or minimize at any time.
|
|
If you want to reset it to expanded view, use the button below.
|
|
</p>
|
|
<Button
|
|
onClick={handleResetSasquatch}
|
|
variant="outline"
|
|
className="gap-2 border-amber-500 text-amber-400 hover:bg-amber-500/10"
|
|
>
|
|
<RotateCcw className="h-4 w-4" />
|
|
Expand The Sasquatch
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="text-xs text-gray-400 italic">
|
|
💡 The Sasquatch is always available - just look for the button in the bottom-left corner!
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function SettingsPage() {
|
|
useSeoMeta({
|
|
title: 'Settings - SkateSpots',
|
|
description: 'Manage your SkateSpots account settings.',
|
|
});
|
|
|
|
// Check URL params for tab selection
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const initialTab = urlParams.get('tab') || 'profile';
|
|
|
|
return (
|
|
<Layout>
|
|
<div className="container px-4 sm:px-6 py-6 sm:py-8">
|
|
<div className="mb-8">
|
|
<h1 className="text-3xl font-bold flex items-center gap-3">
|
|
<Settings className="h-8 w-8" />
|
|
Settings
|
|
</h1>
|
|
<p className="text-muted-foreground mt-1">
|
|
Manage your account and app preferences
|
|
</p>
|
|
</div>
|
|
|
|
<Tabs defaultValue={initialTab} className="space-y-6">
|
|
{/* Grid tabs for mobile, horizontal for larger screens */}
|
|
<TabsList className="grid grid-cols-4 sm:grid-cols-7 gap-1 p-1 h-auto">
|
|
<TabsTrigger value="profile" className="flex-col sm:flex-row gap-1 sm:gap-1.5 px-2 py-2 sm:py-1.5 h-auto">
|
|
<User className="h-4 w-4" />
|
|
<span className="text-[10px] sm:text-sm">Profile</span>
|
|
</TabsTrigger>
|
|
<TabsTrigger value="mydata" className="flex-col sm:flex-row gap-1 sm:gap-1.5 px-2 py-2 sm:py-1.5 h-auto">
|
|
<Database className="h-4 w-4" />
|
|
<span className="text-[10px] sm:text-sm">My Data</span>
|
|
</TabsTrigger>
|
|
<TabsTrigger value="security" className="flex-col sm:flex-row gap-1 sm:gap-1.5 px-2 py-2 sm:py-1.5 h-auto">
|
|
<Shield className="h-4 w-4" />
|
|
<span className="text-[10px] sm:text-sm">Security</span>
|
|
</TabsTrigger>
|
|
<TabsTrigger value="appearance" className="flex-col sm:flex-row gap-1 sm:gap-1.5 px-2 py-2 sm:py-1.5 h-auto">
|
|
<Palette className="h-4 w-4" />
|
|
<span className="text-[10px] sm:text-sm">Theme</span>
|
|
</TabsTrigger>
|
|
<TabsTrigger value="relays" className="flex-col sm:flex-row gap-1 sm:gap-1.5 px-2 py-2 sm:py-1.5 h-auto">
|
|
<Radio className="h-4 w-4" />
|
|
<span className="text-[10px] sm:text-sm">Relays</span>
|
|
</TabsTrigger>
|
|
<TabsTrigger value="wallet" className="flex-col sm:flex-row gap-1 sm:gap-1.5 px-2 py-2 sm:py-1.5 h-auto">
|
|
<Wallet className="h-4 w-4" />
|
|
<span className="text-[10px] sm:text-sm">Wallet</span>
|
|
</TabsTrigger>
|
|
<TabsTrigger value="advanced" className="flex-col sm:flex-row gap-1 sm:gap-1.5 px-2 py-2 sm:py-1.5 h-auto">
|
|
<Wrench className="h-4 w-4" />
|
|
<span className="text-[10px] sm:text-sm">Advanced</span>
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="profile">
|
|
<ProfileTab />
|
|
</TabsContent>
|
|
|
|
<TabsContent value="mydata">
|
|
<MyDataTab />
|
|
</TabsContent>
|
|
|
|
<TabsContent value="security">
|
|
<SecuritySettings />
|
|
</TabsContent>
|
|
|
|
<TabsContent value="appearance">
|
|
<AppearanceTab />
|
|
</TabsContent>
|
|
|
|
<TabsContent value="relays">
|
|
<RelaysTab />
|
|
</TabsContent>
|
|
|
|
<TabsContent value="wallet">
|
|
<WalletTab />
|
|
</TabsContent>
|
|
|
|
<TabsContent value="advanced">
|
|
<AdvancedTab />
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
</Layout>
|
|
);
|
|
}
|