SkateSpots/src/components/RelatrSettings.tsx
SkateSpots 0b796440f1 feat(i18n): Spanish batch 2 — nav/chrome, events/shops/tours, crews/DMs, settings/auth, wallet/stash/newsletter/stats
~3,470 new keys (5,770 total, EN=ES), ~115 more files wired.
- nav + chromeUi: header, More menu, burger menu, footer, map toolbar widgets
  (sort/trust/weather), spot-type label helper.
- placesUi: events, shops, tours, food. socialUi: crews, DMs, notifications,
  collectives, crew chat/coordinator. settingsUi: settings, auth/signup,
  onboarding, profile edit, legal. walletUi: wallet/Cashu, stash, mints,
  app shop, newsletter, community stats, leaderboards, profile.
- Fixed 738 usages where an agent dropped the socialUi. prefix (caught by check:i18n).
2026-10-02 10:11:06 +07:00

290 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';
import { useTranslation } from '@/contexts/language';
/**
* 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 { t } = useTranslation();
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: t('settingsUi.relatr.searchingTitle'),
description: t('settingsUi.relatr.searchingDesc'),
});
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: t('settingsUi.relatr.foundTitle'),
description: t('settingsUi.relatr.foundDesc', { name: metadata.name || t('settingsUi.relatr.unknown') }),
});
return;
}
} catch {
// Skip invalid JSON
}
}
toast({
title: t('settingsUi.relatr.notFoundTitle'),
description: t('settingsUi.relatr.notFoundDesc'),
variant: 'destructive',
});
} catch (error) {
console.error('[RelatrSettings] Auto-discovery failed:', error);
toast({
title: t('settingsUi.relatr.searchFailedTitle'),
description: t('settingsUi.relatr.searchFailedDesc'),
variant: 'destructive',
});
} finally {
setIsSearching(false);
}
};
const handleEnable = () => {
if (!pubkey || pubkey.length !== 64) {
toast({
title: t('settingsUi.relatr.invalidPubkeyTitle'),
description: t('settingsUi.relatr.invalidPubkeyDesc'),
variant: 'destructive',
});
return;
}
enableRelatr(pubkey, relay);
toast({
title: t('settingsUi.relatr.enabledTitle'),
description: t('settingsUi.relatr.enabledDesc'),
});
};
const handleDisable = () => {
disableRelatr();
toast({
title: t('settingsUi.relatr.disabledTitle'),
description: t('settingsUi.relatr.disabledDesc'),
});
};
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" />
{t('settingsUi.relatr.title')}
</CardTitle>
<CardDescription>
{t('settingsUi.relatr.desc')}
</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">{t('settingsUi.relatr.status')}</Label>
<Badge variant={config.enabled ? 'default' : 'outline'} className="gap-2">
{config.enabled ? (
<>
<CheckCircle2 className="h-3 w-3" />
{t('settingsUi.relatr.active')}
</>
) : (
t('settingsUi.relatr.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]">{t('settingsUi.relatr.activeStrong')}</strong> {t('settingsUi.relatr.activeDesc')}
</>
) : (
<>
<strong className="text-[#ccff00]">{t('settingsUi.relatr.disabledStrong')}</strong> {t('settingsUi.relatr.disabledHint')}
</>
)}
</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"
>
{t('settingsUi.relatr.disable')}
</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" />
{t('settingsUi.relatr.enable')}
</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" />}
{t('settingsUi.relatr.advancedSettings')}
</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">
{t('settingsUi.relatr.servicePubkey')}
</Label>
<div className="flex gap-2">
<Input
id="relatr-pubkey"
placeholder={t('settingsUi.relatr.pubkeyPlaceholder')}
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 ? t('settingsUi.relatr.searching') : t('settingsUi.relatr.autoDiscover')}
</Button>
</div>
<p className="text-xs text-gray-500">
{t('settingsUi.relatr.pubkeyDefault')}
</p>
</div>
{/* Relay Input */}
<div className="space-y-2">
<Label htmlFor="relatr-relay" className="text-sm font-bold text-gray-300">
{t('settingsUi.relatr.relayLabel')}
</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">
{t('settingsUi.relatr.relayDefault')}
</p>
</div>
{/* Apply Changes Button */}
<Button
onClick={handleEnable}
variant="outline"
className="w-full gap-2"
disabled={!pubkey || pubkey.length !== 64}
>
{t('settingsUi.relatr.applyChanges')}
</Button>
{/* Documentation Links */}
<div className="pt-4 border-t border-gray-700 space-y-2">
<p className="text-sm font-bold text-gray-300">{t('settingsUi.relatr.learnMore')}</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" />
{t('settingsUi.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" />
{t('settingsUi.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" />
{t('settingsUi.relatr.nip85Spec')}
</a>
</div>
</div>
</CollapsibleContent>
</Collapsible>
</CardContent>
</Card>
);
}