'use client'; import { useEffect, useState } from 'react'; import { Gauge } from 'lucide-react'; import { fetchCachedCredBlueScore, type CredBlueScore } from '@/utils/credBlueScore'; import { CRED_BLUE_BASE } from '@/utils/atproto/config'; type State = | { status: 'loading' } | { status: 'ready'; score: CredBlueScore | null }; type Props = { /** Handle or DID โ€” passed straight to api.cred.blue. */ identifier: string; /** Optional handle used for the cred.blue profile link. Defaults to identifier. */ linkHandle?: string; }; export default function CredBlueScoreBadge({ identifier, linkHandle }: Props) { // Keyed by identifier: when the prop changes, the derived `state` below // falls back to loading on its own โ€” no synchronous reset-setState in the // effect, so a stale score never flashes for the new identifier either. const [entry, setEntry] = useState<{ id: string; state: State } | null>(null); useEffect(() => { let cancelled = false; fetchCachedCredBlueScore(identifier).then((score) => { if (!cancelled) setEntry({ id: identifier, state: { status: 'ready', score } }); }); return () => { cancelled = true; }; }, [identifier]); const state: State = entry && entry.id === identifier ? entry.state : { status: 'loading' }; if (state.status === 'loading') return null; const handleForLink = (linkHandle || identifier).replace(/^@/, ''); const credUrl = `${CRED_BLUE_BASE}/${encodeURIComponent(handleForLink)}`; if (!state.score) { return ( { e.currentTarget.style.color = 'var(--text-accent)'; }} onMouseLeave={(e) => { e.currentTarget.style.color = 'var(--text-tertiary)'; }} > Not yet scored on cred.blue โ†’ ); } const { combined } = state.score.scores; return ( { e.currentTarget.style.borderColor = 'var(--text-accent)'; }} onMouseLeave={(e) => { e.currentTarget.style.borderColor = 'var(--border-medium)'; }} > cred.blue {combined.toLocaleString()} ); }