'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()}
);
}