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.
288 lines
10 KiB
TypeScript
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>
|
|
);
|
|
}
|