Initial push of Plunk Next
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@plunk/ui';
|
||||
import {DashboardLayout} from '../../components/DashboardLayout';
|
||||
import {ActivityFeed} from '../../components/ActivityFeed';
|
||||
import {Eye, MousePointerClick, Send, Zap} from 'lucide-react';
|
||||
import {NextSeo} from 'next-seo';
|
||||
import {useState} from 'react';
|
||||
import useSWR from 'swr';
|
||||
|
||||
interface ActivityStats {
|
||||
totalEvents: number;
|
||||
totalEmailsSent: number;
|
||||
totalEmailsOpened: number;
|
||||
totalEmailsClicked: number;
|
||||
totalWorkflowsStarted: number;
|
||||
openRate: number;
|
||||
clickRate: number;
|
||||
}
|
||||
|
||||
export default function ActivityPage() {
|
||||
const [typeFilter, setTypeFilter] = useState<string>('ALL');
|
||||
const [dateRange, setDateRange] = useState<string>('30');
|
||||
|
||||
// Fetch activity stats
|
||||
const {data: stats} = useSWR<ActivityStats>(`/activity/stats`, {
|
||||
revalidateOnFocus: false,
|
||||
});
|
||||
|
||||
const statsCards = [
|
||||
{
|
||||
name: 'Events Triggered',
|
||||
value: stats?.totalEvents?.toLocaleString() || '0',
|
||||
icon: Zap,
|
||||
description: 'Last 30 days',
|
||||
color: 'text-blue-600',
|
||||
bgColor: 'bg-blue-100',
|
||||
},
|
||||
{
|
||||
name: 'Emails Sent',
|
||||
value: stats?.totalEmailsSent?.toLocaleString() || '0',
|
||||
icon: Send,
|
||||
description: 'Last 30 days',
|
||||
color: 'text-green-600',
|
||||
bgColor: 'bg-green-100',
|
||||
},
|
||||
{
|
||||
name: 'Open Rate',
|
||||
value: stats?.openRate ? `${stats.openRate.toFixed(1)}%` : '0%',
|
||||
icon: Eye,
|
||||
description: `${stats?.totalEmailsOpened?.toLocaleString() || '0'} opens`,
|
||||
color: 'text-purple-600',
|
||||
bgColor: 'bg-purple-100',
|
||||
},
|
||||
{
|
||||
name: 'Click Rate',
|
||||
value: stats?.clickRate ? `${stats.clickRate.toFixed(1)}%` : '0%',
|
||||
icon: MousePointerClick,
|
||||
description: `${stats?.totalEmailsClicked?.toLocaleString() || '0'} clicks`,
|
||||
color: 'text-orange-600',
|
||||
bgColor: 'bg-orange-100',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<NextSeo title="Activity" />
|
||||
<DashboardLayout>
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-neutral-900">Activity</h1>
|
||||
<p className="text-neutral-500 mt-2">
|
||||
Real-time overview of events, emails, and workflow executions across your project.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{statsCards.map(stat => {
|
||||
const Icon = stat.icon;
|
||||
return (
|
||||
<Card key={stat.name}>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardDescription>{stat.name}</CardDescription>
|
||||
<div className={`h-10 w-10 rounded-lg ${stat.bgColor} flex items-center justify-center`}>
|
||||
<Icon className={`h-5 w-5 ${stat.color}`} />
|
||||
</div>
|
||||
</div>
|
||||
<CardTitle className="text-2xl">{stat.value}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-xs text-neutral-500">{stat.description}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex flex-col md:flex-row gap-4">
|
||||
<div className="flex-1">
|
||||
<Select value={typeFilter} onValueChange={setTypeFilter}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="All Activity Types" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="ALL">All Activity Types</SelectItem>
|
||||
<SelectItem value="event.triggered">Events</SelectItem>
|
||||
<SelectItem value="email.sent,email.delivered,email.opened,email.clicked,email.bounced">
|
||||
Emails
|
||||
</SelectItem>
|
||||
<SelectItem value="email.sent">Emails Sent</SelectItem>
|
||||
<SelectItem value="email.opened">Emails Opened</SelectItem>
|
||||
<SelectItem value="email.clicked">Emails Clicked</SelectItem>
|
||||
<SelectItem value="workflow.started,workflow.completed">Workflows</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Select value={dateRange} onValueChange={setDateRange}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Last 30 days" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1">Last 24 hours</SelectItem>
|
||||
<SelectItem value="7">Last 7 days</SelectItem>
|
||||
<SelectItem value="30">Last 30 days</SelectItem>
|
||||
<SelectItem value="90">Last 90 days</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Activity Feed */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Recent Activity</CardTitle>
|
||||
<CardDescription>
|
||||
Live feed of all activities happening across your project. Updates automatically as new activities occur.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ActivityFeed
|
||||
typeFilter={typeFilter === 'ALL' ? undefined : typeFilter}
|
||||
dateRangeDays={parseInt(dateRange)}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user