SkateSpots/src/components/RelatrSettings.tsx
shakespeare.diy e29d35f1e4 fix: restore full skate-spots source tree from misplaced src/src/ copy
The entire skate-spots codebase was accidentally copied into src/src/ and
public/public/ instead of src/ and public/ directly. This left the real
src/ directory as blank MKStack template stubs, explaining why the landing
page showed the default template instead of SkateSpots.

Changes made:
- Copied all files from src/src/ up to src/ (App.tsx, AppRouter.tsx, main.tsx,
  all pages, components, hooks, contexts, lib, i18n, test)
- Copied all files from public/public/ up to public/ (images, manifest, etc.)
- Deleted src/src/ and public/public/ nested directories
- Removed HelloWorld.tsx template stub from pages/
- Retained the previously fixed index.css (Tailwind v3 directives)

Full skate-spots feature set is now active:
- All 30+ routes (map, events, spots, coaches, marketplace, wallet, etc.)
- NWCProvider, LanguageProvider, PWAInstallPrompt, AmberCallbackHandler
- Skate community relays (goskatespots.nostr1.com, wotr.relatr.xyz, etc.)
- Outfit Variable font, Amber pre-check in main.tsx
- All hooks (useSkateSpots, useCoaches, useZaps, etc.)
- i18n support (en/es)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 22:18:28 +07:00

288 lines
10 KiB
TypeScript

import { useState } from 'react';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { useToast } from '@/hooks/useToast';
import { enableRelatr, getRelatrConfig, disableRelatr } from '@/hooks/useRelatrScore';
import { useNostr } from '@nostrify/react';
import { Shield, ExternalLink, Search, CheckCircle2, ChevronDown, ChevronUp } from 'lucide-react';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
/**
* RelatrSettings component for configuring Relatr global trust score integration
*
* Relatr publishes trust scores as NIP-85 Trusted Assertion events (kind 30382).
* Auto-enabled by default for all users with ContextVM's official service.
*/
export function RelatrSettings() {
const { toast } = useToast();
const { nostr } = useNostr();
const config = getRelatrConfig();
const [pubkey, setPubkey] = useState(config.servicePubkey);
const [relay, setRelay] = useState(config.relay);
const [isSearching, setIsSearching] = useState(false);
const [isAdvancedOpen, setIsAdvancedOpen] = useState(false);
const handleAutoDiscover = async () => {
setIsSearching(true);
toast({
title: 'Searching for Relatr service...',
description: 'Querying Nostr for Relatr profiles',
});
try {
// Search for profiles mentioning "relatr" or "contextvm"
const profiles = await nostr.query(
[{ kinds: [0], search: 'relatr', limit: 10 }],
{ signal: AbortSignal.timeout(10000) }
);
console.log('[RelatrSettings] Found profiles:', profiles);
// Look for official Relatr service
for (const profile of profiles) {
try {
const metadata = JSON.parse(profile.content);
const name = metadata.name?.toLowerCase() || '';
const about = metadata.about?.toLowerCase() || '';
const website = metadata.website?.toLowerCase() || '';
if (
name.includes('relatr') ||
about.includes('trust score') ||
website.includes('relatr.xyz')
) {
setPubkey(profile.pubkey);
toast({
title: 'Found Relatr service!',
description: `Service: ${metadata.name || 'Unknown'}`,
});
return;
}
} catch (e) {
// Skip invalid JSON
}
}
toast({
title: 'No Relatr service found',
description: 'Try entering the pubkey manually or check https://www.relatr.xyz/',
variant: 'destructive',
});
} catch (error) {
console.error('[RelatrSettings] Auto-discovery failed:', error);
toast({
title: 'Search failed',
description: 'Could not query Nostr for Relatr profiles',
variant: 'destructive',
});
} finally {
setIsSearching(false);
}
};
const handleEnable = () => {
if (!pubkey || pubkey.length !== 64) {
toast({
title: 'Invalid pubkey',
description: 'Please enter a valid 64-character hex pubkey',
variant: 'destructive',
});
return;
}
enableRelatr(pubkey, relay);
toast({
title: 'Relatr enabled!',
description: 'Global trust scores are now active.',
});
};
const handleDisable = () => {
disableRelatr();
toast({
title: 'Relatr disabled',
description: 'Trust scores are no longer being fetched.',
});
};
return (
<Card className="bg-black/90 border-2 border-[#ccff00]/30">
<CardHeader>
<CardTitle className="text-xl font-black uppercase text-[#ccff00] flex items-center gap-2">
<Shield className="h-5 w-5" />
Relatr Trust Scores
</CardTitle>
<CardDescription>
Global trust scores help identify reliable users and reduce spam
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{/* Status - Always visible */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<Label className="text-sm font-bold text-gray-300">Status</Label>
<Badge variant={config.enabled ? 'default' : 'outline'} className="gap-2">
{config.enabled ? (
<>
<CheckCircle2 className="h-3 w-3" />
Active
</>
) : (
'Disabled'
)}
</Badge>
</div>
{/* Info Alert */}
<Alert className="bg-[#ccff00]/10 border-[#ccff00]/50">
<Shield className="h-4 w-4 text-[#ccff00]" />
<AlertDescription className="text-sm text-gray-300">
{config.enabled ? (
<>
<strong className="text-[#ccff00]">Relatr is active.</strong> Trust scores from ContextVM's global network analysis help identify reliable skaters and reduce spam.
</>
) : (
<>
<strong className="text-[#ccff00]">Relatr is disabled.</strong> Enable it to get trust scores from the global Nostr network.
</>
)}
</AlertDescription>
</Alert>
{/* Quick Actions */}
{config.enabled ? (
<Button
onClick={handleDisable}
variant="outline"
className="w-full gap-2 border-red-500/50 text-red-400 hover:bg-red-500/10"
>
Disable Relatr
</Button>
) : (
<Button
onClick={handleEnable}
className="w-full gap-2 bg-[#ccff00] text-black hover:bg-[#ccff00]/90 font-bold uppercase"
disabled={!pubkey || pubkey.length !== 64}
>
<Shield className="h-4 w-4" />
Enable Relatr
</Button>
)}
</div>
{/* Advanced Settings - Collapsible */}
<Collapsible open={isAdvancedOpen} onOpenChange={setIsAdvancedOpen}>
<CollapsibleTrigger asChild>
<Button
variant="ghost"
className="w-full gap-2 text-gray-400 hover:text-white"
>
{isAdvancedOpen ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
Advanced Settings
</Button>
</CollapsibleTrigger>
<CollapsibleContent className="space-y-4 pt-4">
{/* Service Pubkey Input */}
<div className="space-y-2">
<Label htmlFor="relatr-pubkey" className="text-sm font-bold text-gray-300">
Relatr Service Pubkey
</Label>
<div className="flex gap-2">
<Input
id="relatr-pubkey"
placeholder="Enter 64-character hex pubkey..."
value={pubkey}
onChange={(e) => setPubkey(e.target.value)}
className="font-mono text-xs"
maxLength={64}
/>
<Button
variant="outline"
size="sm"
onClick={handleAutoDiscover}
disabled={isSearching}
className="gap-2 whitespace-nowrap"
>
<Search className="h-4 w-4" />
{isSearching ? 'Searching...' : 'Auto-Discover'}
</Button>
</div>
<p className="text-xs text-gray-500">
Default: ContextVM's official Relatr service (npub1dvmc...)
</p>
</div>
{/* Relay Input */}
<div className="space-y-2">
<Label htmlFor="relatr-relay" className="text-sm font-bold text-gray-300">
Relay (where Relatr publishes scores)
</Label>
<Input
id="relatr-relay"
placeholder="wss://relay.example.com"
value={relay}
onChange={(e) => setRelay(e.target.value)}
className="font-mono text-xs"
/>
<p className="text-xs text-gray-500">
Default: wss://relay.contextvm.org (ContextVM's official relay)
</p>
</div>
{/* Apply Changes Button */}
<Button
onClick={handleEnable}
variant="outline"
className="w-full gap-2"
disabled={!pubkey || pubkey.length !== 64}
>
Apply Changes
</Button>
{/* Documentation Links */}
<div className="pt-4 border-t border-gray-700 space-y-2">
<p className="text-sm font-bold text-gray-300">Learn More:</p>
<div className="flex flex-col gap-2">
<a
href="https://www.relatr.xyz/"
target="_blank"
rel="noopener noreferrer"
className="text-sm text-[#ccff00] hover:underline inline-flex items-center gap-1"
>
<ExternalLink className="h-3 w-3" />
Relatr Website
</a>
<a
href="https://github.com/ContextVM/relatr"
target="_blank"
rel="noopener noreferrer"
className="text-sm text-[#ccff00] hover:underline inline-flex items-center gap-1"
>
<ExternalLink className="h-3 w-3" />
Relatr GitHub
</a>
<a
href="https://github.com/nostr-protocol/nips/blob/master/85.md"
target="_blank"
rel="noopener noreferrer"
className="text-sm text-[#ccff00] hover:underline inline-flex items-center gap-1"
>
<ExternalLink className="h-3 w-3" />
NIP-85 Specification
</a>
</div>
</div>
</CollapsibleContent>
</Collapsible>
</CardContent>
</Card>
);
}