import {Badge, Button, Collapsible, CollapsibleContent, CollapsibleTrigger} from '@plunk/ui'; import type {Activity} from '@plunk/types'; import {memo, useState} from 'react'; import {EmailPreviewModal} from './EmailPreviewModal'; import { AlertCircle, Calendar, CheckCheck, CheckCircle, ChevronRight, Eye, Inbox, MousePointerClick, Send, ShieldAlert, Workflow, XCircle, Zap, } from 'lucide-react'; import Link from 'next/link'; /** * Simple relative time formatter for past events */ function getRelativeTime(date: Date): string { const now = new Date(); const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000); if (diffInSeconds < 60) { return 'just now'; } const diffInMinutes = Math.floor(diffInSeconds / 60); if (diffInMinutes < 60) { return `${diffInMinutes} ${diffInMinutes === 1 ? 'minute' : 'minutes'} ago`; } const diffInHours = Math.floor(diffInMinutes / 60); if (diffInHours < 24) { return `${diffInHours} ${diffInHours === 1 ? 'hour' : 'hours'} ago`; } const diffInDays = Math.floor(diffInHours / 24); if (diffInDays < 30) { return `${diffInDays} ${diffInDays === 1 ? 'day' : 'days'} ago`; } const diffInMonths = Math.floor(diffInDays / 30); if (diffInMonths < 12) { return `${diffInMonths} ${diffInMonths === 1 ? 'month' : 'months'} ago`; } const diffInYears = Math.floor(diffInMonths / 12); return `${diffInYears} ${diffInYears === 1 ? 'year' : 'years'} ago`; } /** * Format upcoming time (for future events) */ function getUpcomingTime(date: Date): string { const now = new Date(); const diffInSeconds = Math.floor((date.getTime() - now.getTime()) / 1000); if (diffInSeconds < 60) { return 'in a moment'; } const diffInMinutes = Math.floor(diffInSeconds / 60); if (diffInMinutes < 60) { return `in ${diffInMinutes} ${diffInMinutes === 1 ? 'minute' : 'minutes'}`; } const diffInHours = Math.floor(diffInMinutes / 60); if (diffInHours < 24) { return `in ${diffInHours} ${diffInHours === 1 ? 'hour' : 'hours'}`; } const diffInDays = Math.floor(diffInHours / 24); if (diffInDays === 1) { return `tomorrow at ${date.toLocaleTimeString('en-US', {hour: 'numeric', minute: '2-digit', hour12: true})}`; } if (diffInDays < 7) { return `in ${diffInDays} days`; } if (diffInDays < 30) { const weeks = Math.floor(diffInDays / 7); return `in ${weeks} ${weeks === 1 ? 'week' : 'weeks'}`; } const diffInMonths = Math.floor(diffInDays / 30); return `in ${diffInMonths} ${diffInMonths === 1 ? 'month' : 'months'}`; } /** * Check if an activity is an email activity */ function isEmailActivity(type: string): boolean { return [ 'email.sent', 'email.delivered', 'email.received', 'email.opened', 'email.clicked', 'email.bounced', 'email.complaint', ].includes(type); } interface ActivityItemProps { activity: Activity; status?: 'upcoming' | 'completed'; } interface ActivityConfig { icon: React.ComponentType<{className?: string}>; color: string; bgColor: string; title: string; description?: string; badge?: { label: string; variant: 'default' | 'secondary' | 'destructive' | 'outline'; }; jsonData?: Record; } function getActivityConfig(activity: Activity): ActivityConfig { const {type, metadata} = activity; switch (type) { case 'event.triggered': return { icon: Zap, color: 'text-amber-700', bgColor: 'bg-amber-50', title: (typeof metadata.eventName === 'string' ? metadata.eventName : undefined) || 'Event triggered', description: undefined, badge: { label: 'Event', variant: 'default', }, jsonData: metadata.eventData && typeof metadata.eventData === 'object' && !Array.isArray(metadata.eventData) ? (metadata.eventData as Record) : undefined, }; case 'email.sent': return { icon: Send, color: 'text-neutral-700', bgColor: 'bg-neutral-100', title: (typeof metadata.subject === 'string' ? metadata.subject : undefined) || 'Email sent', description: metadata.campaignName ? `Campaign: ${String(metadata.campaignName)}` : metadata.workflowName ? `Workflow: ${String(metadata.workflowName)}` : typeof metadata.sourceType === 'string' ? metadata.sourceType : undefined, badge: { label: 'Sent', variant: 'default', }, }; case 'email.delivered': return { icon: CheckCircle, color: 'text-emerald-700', bgColor: 'bg-emerald-50', title: (typeof metadata.subject === 'string' ? metadata.subject : undefined) || 'Email delivered', description: metadata.campaignName ? `Campaign: ${String(metadata.campaignName)}` : metadata.workflowName ? `Workflow: ${String(metadata.workflowName)}` : undefined, badge: { label: 'Delivered', variant: 'default', }, }; case 'email.received': return { icon: Inbox, color: 'text-neutral-600', bgColor: 'bg-neutral-100', title: (typeof metadata.subject === 'string' ? metadata.subject : undefined) || 'Email received', description: typeof metadata.from === 'string' ? `From: ${metadata.from}` : 'Inbound email', badge: { label: 'Received', variant: 'default', }, }; case 'email.opened': return { icon: Eye, color: 'text-emerald-700', bgColor: 'bg-emerald-50', title: (typeof metadata.subject === 'string' ? metadata.subject : undefined) || 'Email opened', description: typeof metadata.totalOpens === 'number' && metadata.totalOpens > 1 ? `Opened ${metadata.totalOpens} times` : metadata.campaignName ? `Campaign: ${String(metadata.campaignName)}` : metadata.workflowName ? `Workflow: ${String(metadata.workflowName)}` : undefined, badge: { label: 'Opened', variant: 'secondary', }, }; case 'email.clicked': return { icon: MousePointerClick, color: 'text-sky-700', bgColor: 'bg-sky-50', title: (typeof metadata.subject === 'string' ? metadata.subject : undefined) || 'Email clicked', description: typeof metadata.totalClicks === 'number' && metadata.totalClicks > 1 ? `Clicked ${metadata.totalClicks} times` : metadata.campaignName ? `Campaign: ${String(metadata.campaignName)}` : metadata.workflowName ? `Workflow: ${String(metadata.workflowName)}` : undefined, badge: { label: 'Clicked', variant: 'default', }, }; case 'email.bounced': return { icon: XCircle, color: 'text-red-700', bgColor: 'bg-red-50', title: (typeof metadata.subject === 'string' ? metadata.subject : undefined) || 'Email bounced', description: (typeof metadata.error === 'string' ? metadata.error : undefined) || 'Email failed to deliver', badge: { label: 'Bounced', variant: 'destructive', }, }; case 'email.complaint': return { icon: ShieldAlert, color: 'text-red-700', bgColor: 'bg-red-50', title: (typeof metadata.subject === 'string' ? metadata.subject : undefined) || 'Spam complaint', description: metadata.campaignName ? `Campaign: ${String(metadata.campaignName)}` : metadata.workflowName ? `Workflow: ${String(metadata.workflowName)}` : 'Recipient marked as spam', badge: { label: 'Complaint', variant: 'destructive', }, }; case 'workflow.started': return { icon: Workflow, color: 'text-amber-700', bgColor: 'bg-amber-50', title: (typeof metadata.workflowName === 'string' ? metadata.workflowName : undefined) || 'Workflow started', description: `Status: ${String(metadata.status || 'unknown')}`, badge: { label: 'Workflow', variant: 'default', }, }; case 'workflow.completed': return { icon: CheckCheck, color: 'text-amber-700', bgColor: 'bg-amber-50', title: (typeof metadata.workflowName === 'string' ? metadata.workflowName : undefined) || 'Workflow completed', description: metadata.exitReason ? `Exit: ${String(metadata.exitReason)}` : `Status: ${String(metadata.status || 'unknown')}`, badge: { label: 'Completed', variant: 'default', }, }; case 'campaign.scheduled': return { icon: Calendar, color: 'text-sky-700', bgColor: 'bg-sky-50', title: (typeof metadata.campaignName === 'string' ? metadata.campaignName : undefined) || 'Campaign scheduled', description: metadata.subject ? `${String(metadata.subject)}${metadata.totalRecipients ? ` • ${metadata.totalRecipients} recipients` : ''}` : metadata.totalRecipients ? `${metadata.totalRecipients} recipients` : undefined, badge: { label: 'Scheduled', variant: 'outline', }, }; case 'workflow.email.scheduled': return { icon: Calendar, color: 'text-amber-700', bgColor: 'bg-amber-50', title: (typeof metadata.stepName === 'string' ? metadata.stepName : undefined) || 'Workflow email scheduled', description: metadata.workflowName ? `Workflow: ${String(metadata.workflowName)}${metadata.subject ? ` • ${String(metadata.subject)}` : ''}` : typeof metadata.subject === 'string' ? metadata.subject : undefined, badge: { label: 'Scheduled', variant: 'outline', }, }; default: return { icon: AlertCircle, color: 'text-neutral-600', bgColor: 'bg-neutral-100', title: 'Unknown activity', badge: { label: 'Unknown', variant: 'outline', }, }; } } export const ActivityItem = memo(function ActivityItem({activity, status = 'completed'}: ActivityItemProps) { const [showPreviewModal, setShowPreviewModal] = useState(false); const config = getActivityConfig(activity); const Icon = config.icon; const timestamp = new Date(activity.timestamp); const isUpcoming = status === 'upcoming'; const relativeTime = isUpcoming ? getUpcomingTime(timestamp) : getRelativeTime(timestamp); return (
{/* Icon */}
{/* Content */}

{config.title}

{config.badge && {config.badge.label}} {isEmailActivity(activity.type) && activity.metadata.subject && activity.metadata.body ? ( ) : null}
{config.description &&

{config.description}

} {activity.contactEmail && (
{activity.contactId ? ( {activity.contactEmail} ) : ( {activity.contactEmail} )}
)} {/* Collapsible JSON Data */} {config.jsonData && ( Event Data
                    {JSON.stringify(config.jsonData, null, 2)}
                  
)}
{relativeTime}
{/* Email Preview Modal */} {showPreviewModal && activity.metadata.subject && activity.metadata.body ? ( ) : null}
); });