← back to Stayclaim
src/components/SourceLink.tsx
69 lines
import Link from 'next/link';
import { safeHttpUrl } from '@/lib/url-guards';
/**
* SourceLink — every fact in the archive must show its source.
*
* Renders a small "source: NAME ↗" link to the actual source document
* (Socrata permit row, IA scanned page, Assessor portal, etc.). Steve's
* editorial rule (2026-04-29): "real links back to the original source
* showing the actual ocr piece of paper etc."
*/
export function SourceLink({
label,
url,
tier,
date,
className = '',
}: {
label: string;
url?: string | null;
tier?: string | null; // 'A' | 'B' | 'C' | 'D'
date?: string | null;
className?: string;
}) {
const tierLabel = tier ? { A: 'gov', B: 'verified', C: 'community', D: 'inferred' }[tier] ?? tier : null;
const tierColor = tier ? {
A: 'text-moss',
B: 'text-blue-700',
C: 'text-amber-700',
D: 'text-ink/40',
}[tier] ?? 'text-ink/40' : '';
const safeUrl = safeHttpUrl(url);
return (
<span className={`font-mono text-[10px] uppercase tracking-[0.15em] text-ink/45 ${className}`}>
source:{' '}
{safeUrl ? (
<a
href={safeUrl}
target="_blank"
rel="noreferrer noopener"
className="hover:text-coral underline-offset-2 hover:underline transition"
>
{label} ↗
</a>
) : (
<span className="text-ink/65">{label}</span>
)}
{tierLabel && (
<span className={`ml-2 ${tierColor}`}>tier {tier} · {tierLabel}</span>
)}
{date && <span className="ml-2">· {date.slice(0, 10)}</span>}
</span>
);
}
/** Compact sourcing block for tabular contexts (permit/event rows). */
export function SourceLinkInline({ label, url }: { label: string; url?: string | null }) {
const safeUrl = safeHttpUrl(url);
return safeUrl ? (
<a href={safeUrl} target="_blank" rel="noreferrer noopener"
className="font-mono text-[10px] uppercase tracking-[0.1em] text-coral hover:underline">
{label} ↗
</a>
) : (
<span className="font-mono text-[10px] uppercase tracking-[0.1em] text-ink/40">{label}</span>
);
}