← back to Stayclaim
src/components/Timeline.tsx
53 lines
import { TierBadge, type SourceTier, isPublicTier } from './TierBadge';
import { safeHttpUrl } from '@/lib/url-guards';
/**
* Timeline — vertical ownership/transaction/permit history.
* Every event carries TierBadge. D-tier events are filtered out at render.
*/
export type TimelineEvent = {
date: string; // ISO or "1954-06-12" / "1978" / "1940s"
type: string; // permit / sale / remodel / occupancy / demolition / historic-designation
summary: string;
tier: SourceTier;
sourceLabel?: string;
sourceUrl?: string;
};
export function Timeline({ events }: { events: TimelineEvent[] }) {
const visible = events.filter(e => isPublicTier(e.tier));
if (visible.length === 0) {
return (
<p className="text-sm text-ink/50 italic py-6">
No publicly-verifiable events recorded for this address yet.
</p>
);
}
return (
<ol className="relative border-l border-ink/15 ml-2">
{visible.map((e, i) => (
<li key={i} className="pl-6 py-4 relative">
<span className="absolute -left-[5px] top-6 h-2 w-2 rounded-full bg-moss" aria-hidden />
<div className="flex items-baseline justify-between gap-3 flex-wrap">
<time className="font-mono text-xs uppercase tracking-wider text-ink/60">{e.date}</time>
<span className="text-[10px] uppercase tracking-[0.15em] text-ink/40">{e.type}</span>
</div>
<p className="mt-1 text-base text-ink leading-relaxed">{e.summary}</p>
<div className="mt-2">
{(() => {
const safeUrl = safeHttpUrl(e.sourceUrl);
return safeUrl ? (
<a href={safeUrl} target="_blank" rel="noopener noreferrer" className="underline-offset-2 hover:underline">
<TierBadge tier={e.tier as 'A' | 'B' | 'C'} sourceLabel={e.sourceLabel} />
</a>
) : (
<TierBadge tier={e.tier as 'A' | 'B' | 'C'} sourceLabel={e.sourceLabel} />
);
})()}
</div>
</li>
))}
</ol>
);
}