Something went wrong. Try again.
FlockOff is a mass-block tool for the Atmosphere flockoff.app
Something went wrong. Try again.
4.5 kB · 121 lines
TSX
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122import { LinkButton } from '@/components/LinkButton'import { PostPreview } from '@/components/PostPreview'import type { BlockEventSummary, PostData } from '@/types'
interface EventCardProps { event: BlockEventSummary post?: PostData | null}
const STATUS_STYLE: Record<string, string> = { 'in-progress': 'border-sky-500/50 bg-sky-500/10 text-sky-600 dark:text-sky-400', partial: 'border-amber-500/50 bg-amber-500/10 text-amber-600 dark:text-amber-400', complete: 'border-green-500/50 bg-green-500/10 text-green-600 dark:text-green-400', undone: 'border-violet-500/50 bg-violet-500/10 text-violet-600 dark:text-violet-400', empty: 'border-border bg-muted text-[var(--fo-text3)]',}
const STATUS_LABEL: Record<string, string> = { 'in-progress': 'In Progress', partial: 'Partial', complete: 'Complete', undone: 'Undone', empty: 'Empty',}
function formatDateTime(iso: string): string { try { return new Date(iso).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit', }) } catch { return '' }}
export function EventCard({ event, post }: EventCardProps) { const { rkey, record } = event const { stats, blockedDids, blockedRkeys, failedDids, pendingDids, createdAt, sourcePostUrl } = record
const total = stats?.total ?? blockedDids.length + failedDids.length + pendingDids.length const completed = stats?.completed ?? blockedDids.length const failed = stats?.failed ?? failedDids.length const pending = pendingDids.length
const isUndone = !!record.undoneAt || (total > 0 && completed === 0 && failed === 0 && pending === 0 && blockedRkeys.length === 0)
const status = isUndone ? 'undone' : pending > 0 ? 'in-progress' : failed > 0 ? 'partial' : completed > 0 ? 'complete' : 'empty'
return ( <div className="bg-card border border-border rounded-xl p-5 flex flex-col gap-3" style={{ boxShadow: 'var(--fo-shadow-card)' }} > {/* Post preview + status badge */} <div className="flex items-start gap-3"> <div className="flex-1 min-w-0"> {post ? ( <PostPreview post={post} compact /> ) : ( <div className="bg-secondary/40 rounded-lg px-3 py-2.5"> {sourcePostUrl ? ( <a href={sourcePostUrl} target="_blank" rel="noopener noreferrer" className="text-sm font-mono text-[var(--fo-text2)] hover:underline truncate block" > {sourcePostUrl} </a> ) : ( <span className="text-sm text-[var(--fo-text3)] italic">Post unavailable</span> )} </div> )} </div> <span className={`shrink-0 text-xs font-mono font-medium uppercase tracking-wide px-2 py-0.5 rounded-full border ${STATUS_STYLE[status]}`}> {STATUS_LABEL[status]} </span> </div>
{/* Date */} {createdAt && ( <p className="text-xs font-mono text-[var(--fo-text3)]"> {formatDateTime(createdAt)} </p> )}
<div className="border-t border-border" />
{/* Stats + action row */} <div className="flex items-center justify-between gap-4"> <div className="flex items-center gap-4 text-sm font-mono flex-wrap"> {isUndone ? ( <span className="text-violet-600 dark:text-violet-400"> ↩ {(record.blockedDids?.length || total).toLocaleString()} previously blocked </span> ) : ( <> <span className="text-green-600 dark:text-green-400">✓ {completed.toLocaleString()}</span> {failed > 0 && <span className="text-destructive">✗ {failed.toLocaleString()}</span>} {pending > 0 && <span className="text-sky-600 dark:text-sky-400">⋯ {pending.toLocaleString()}</span>} {completed === 0 && failed === 0 && pending === 0 && ( <span className="text-[var(--fo-text3)]">No blocks recorded</span> )} </> )} </div> <LinkButton href={`/events/${rkey}`} variant="outline" size="sm" className="shrink-0 text-xs"> View details </LinkButton> </div> </div> )}