"use client" import Link from 'next/link' import { useState } from 'react' import { MessageSquare, UserPlus, Briefcase, Radio, Zap, Inbox, } from 'lucide-react' import type { ComponentType } from 'react' import type { ActivityItem, ActivityKind } from '@/lib/dashboard/types' import { cn } from '@/lib/utils' import { EmptyState } from './empty-state' import { Skeleton } from './skeleton' interface ActivityFeedProps { items: ActivityItem[] | null loading: boolean } const PAGE_SIZES = [5, 10, 20, 50] as const type PageSize = (typeof PAGE_SIZES)[number] interface KindTheme { icon: ComponentType<{ className?: string }> /** Tailwind classes for the round icon badge + label color. */ badge: string } const KIND_THEME: Record = { message: { icon: MessageSquare, badge: 'bg-blue-500/10 text-blue-400' }, contact: { icon: UserPlus, badge: 'bg-primary/10 text-primary' }, deal: { icon: Briefcase, badge: 'bg-primary/10 text-primary' }, broadcast: { icon: Radio, badge: 'bg-amber-500/10 text-amber-400' }, automation: { icon: Zap, badge: 'bg-rose-500/10 text-rose-400' }, } export function ActivityFeed({ items, loading }: ActivityFeedProps) { // Start at 5 — a quick scan of the most recent events without // dominating vertical real estate. User expands explicitly via the // footer control when they want deeper history. const [pageSize, setPageSize] = useState(5) const totalLoaded = items?.length ?? 0 const visible = items?.slice(0, pageSize) ?? [] // A size option is "useful" if picking it would reveal rows the // smaller option doesn't already show. With PAGE_SIZES=[5,10,20,50]: // "10" is useful only once we've loaded ≥6 items, "20" once ≥11, etc. // The smallest option is always enabled. const isSizeUseful = (size: PageSize, i: number) => i === 0 || totalLoaded > PAGE_SIZES[i - 1] return (

Recent Activity

View all →
{loading || !items ? (
{Array.from({ length: 5 }).map((_, i) => ( ))}
) : items.length === 0 ? (
) : ( <>
    {visible.map((it, i) => { const theme = KIND_THEME[it.kind] const Icon = theme.icon // Alternating row background for scanability. bg-muted/40 // keeps the stripe visible in both light and dark modes // (bg-card/40 vanishes against a white card surface in light). const stripe = i % 2 === 0 ? 'bg-transparent' : 'bg-muted/40' const row = (
    {it.text} {relativeTime(it.at)}
    ) return (
  • {it.href ? ( {row} ) : ( row )}
  • ) })}
)}
) } function relativeTime(iso: string): string { const then = new Date(iso).getTime() if (Number.isNaN(then)) return '' const diffSec = Math.round((Date.now() - then) / 1000) if (diffSec < 60) return `${Math.max(1, diffSec)}s ago` if (diffSec < 3600) return `${Math.floor(diffSec / 60)}m ago` if (diffSec < 86400) return `${Math.floor(diffSec / 3600)}h ago` if (diffSec < 2_592_000) return `${Math.floor(diffSec / 86400)}d ago` return new Date(iso).toLocaleDateString() }