SkateSpots/src/components/RelatrSettings.tsx
SkateSpots ab87b8ab83 chore(lint): remove safely-detectable unused locals
Position-based cleanup (no name collisions) of unused catch bindings and unused
single-line pure consts across 12 files. Automated destructure/prop removals
were proven unsafe (shadowed names + typed shorthand), so those remain.

Warning backlog: 76 -> 60 (39 no-unused-vars, 14 exhaustive-deps, 7 react-refresh).
Gate green: 101 tests, 0 errors, boot smoke passing.
2026-09-26 21:58:03 +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 {
// 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>
);
}