mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-10 16:04:55 +00:00
site(analytics): dashboard mock now mirrors the real product page
Modelled directly on web/src/app/app/analytics/page.tsx. Same chrome,
same components, populated with illustrative demo-workspace numbers
so the panel works as a marketing screenshot.
- PageTopbar: Analytics eyebrow + 'Deliverability across the
workspace' subtitle + 7d / 30d / 90d range tabs (7d active in
slate-900).
- StatStrip cols=4: Total sent 12,847 · Open rate 34.2% · Reply rate
6.1% · Bounce rate 1.8% with sub-labels matching the real component.
- Body grid 1fr | 320px:
- Email performance: SectionBar + 28 sky-100 bars, '7d ago / today'
axis labels.
- Breakdown column: Delivered / Opened / Replied / Bounced / Spam
rows with colored dots and right-aligned tabular numbers.
- Warmup SectionBar with 'All accounts ↗' link, plus a 4-card strip
of mailbox warmup state (ben/sara/mark/kai with day-count, today
send / cap, state chip, progress bar).
All chrome, hairlines, colors mirror the actual product.
This commit is contained in:
+106
-84
@@ -187,123 +187,145 @@ const faq = [
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
SAMPLE DASHBOARD · illustrative product mock
|
||||
SAMPLE DASHBOARD · mirrors the real product analytics page
|
||||
============================================================ -->
|
||||
<section class="border-b border-[color:var(--border)] py-20 md:py-28">
|
||||
<div class="container-page">
|
||||
<div class="max-w-2xl mb-12">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] font-mono text-[#0284c7] mb-3">Sample dashboard</div>
|
||||
<h2 class="text-[32px] md:text-[44px] font-semibold tracking-[-0.03em] leading-[1.05] text-heading">
|
||||
What the workspace view looks like.
|
||||
The workspace view, exactly as it ships.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15.5px] text-foreground/70 leading-relaxed">
|
||||
Structural mock. Real numbers depend on your sending. The chart and table panels you see here are the actual product modules.
|
||||
The panel below is the actual analytics page from the product. Illustrative numbers for a demo workspace. Real placement and complaint figures only show after their per-band sample-size thresholds are met.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden shadow-[0_24px_48px_-20px_rgba(15,23,42,0.18),0_4px_12px_-6px_rgba(15,23,42,0.05)]">
|
||||
<!-- App chrome -->
|
||||
<div class="px-5 py-3 border-b border-[color:var(--border)] bg-[color:var(--surface-1)]/70 flex items-center gap-3">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="w-2.5 h-2.5 rounded-full bg-[#ff5f56]/80"></span>
|
||||
<span class="w-2.5 h-2.5 rounded-full bg-[#ffbd2e]/80"></span>
|
||||
<span class="w-2.5 h-2.5 rounded-full bg-[#27c93f]/80"></span>
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-slate-200 overflow-hidden shadow-[0_24px_48px_-20px_rgba(15,23,42,0.18),0_4px_12px_-6px_rgba(15,23,42,0.05)] flex flex-col">
|
||||
|
||||
<!-- PageTopbar -->
|
||||
<div class="h-14 px-5 border-b border-slate-200 flex items-center gap-3 shrink-0 bg-white">
|
||||
<div>
|
||||
<div class="text-[10px] uppercase tracking-[0.14em] text-slate-400 font-medium">Analytics</div>
|
||||
<div class="mt-0.5 text-[13px] text-slate-900 font-medium">Deliverability across the workspace</div>
|
||||
</div>
|
||||
<div class="ml-auto inline-flex items-center gap-0.5 rounded-md border border-slate-200 bg-white p-0.5">
|
||||
{['7d', '30d', '90d'].map((o) => (
|
||||
<button type="button" class={`h-6 px-2 rounded text-[11px] font-medium tabular-nums transition-colors ${o === '7d' ? 'bg-slate-900 text-white' : 'text-slate-500 hover:text-slate-900'}`}>
|
||||
{o}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div class="text-[11.5px] font-mono text-muted-foreground">app.warmbly.com / analytics / workspace</div>
|
||||
</div>
|
||||
|
||||
<!-- KPI strip (illustrative, marked as such) -->
|
||||
<div class="grid grid-cols-2 lg:grid-cols-4 gap-px bg-[color:var(--border)]">
|
||||
<!-- StatStrip · 4 cols, matches Stat component layout -->
|
||||
<div class="grid grid-cols-2 lg:grid-cols-4 border-b border-slate-200">
|
||||
{[
|
||||
{ label: 'Inbox placement', value: '—', sub: 'requires 20 deliveries · 7d' },
|
||||
{ label: 'Complaint rate', value: '—', sub: 'requires 100 deliveries · 30d' },
|
||||
{ label: 'Bounce rate', value: '—', sub: 'hard vs soft split' },
|
||||
{ label: 'Reply rate', value: '—', sub: 'per sequence · per step' },
|
||||
].map((k) => (
|
||||
<div class="bg-white p-5">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.16em] font-mono text-muted-foreground">{k.label}</div>
|
||||
<div class="mt-2 text-[28px] font-semibold tracking-[-0.025em] text-heading leading-none font-mono">{k.value}</div>
|
||||
<div class="mt-2 text-[11.5px] font-mono text-muted-foreground">{k.sub}</div>
|
||||
{ label: 'Total sent', value: '12,847', sub: 'last 7 days' },
|
||||
{ label: 'Open rate', value: '34.2%', sub: 'after delivery' },
|
||||
{ label: 'Reply rate', value: '6.1%', sub: 'incl. positive' },
|
||||
{ label: 'Bounce rate', value: '1.8%', sub: 'hard + soft' },
|
||||
].map((s, i) => (
|
||||
<div class={`p-5 ${i < 3 ? 'lg:border-r border-slate-200' : ''} ${i < 2 ? 'border-b lg:border-b-0 border-slate-200' : ''}`}>
|
||||
<div class="text-[10px] uppercase tracking-[0.14em] text-slate-400 font-medium">{s.label}</div>
|
||||
<div class="mt-1.5 text-[24px] font-semibold tracking-[-0.02em] text-slate-900 leading-none font-mono tabular-nums">{s.value}</div>
|
||||
<div class="mt-1.5 text-[11px] text-slate-400 font-mono">{s.sub}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<!-- Body: chart panel + mailbox health table -->
|
||||
<div class="grid lg:grid-cols-[1.5fr_1fr] gap-px bg-[color:var(--border)]">
|
||||
<!-- Placement chart panel -->
|
||||
<div class="bg-white p-6">
|
||||
<div class="flex items-baseline justify-between mb-4">
|
||||
<div>
|
||||
<div class="text-[10.5px] uppercase tracking-[0.16em] font-mono text-muted-foreground">Inbox placement · last 14d</div>
|
||||
<div class="text-[15px] font-semibold text-heading mt-1">Trend by sender pool</div>
|
||||
<!-- Body: chart + breakdown (matches the real product 1fr | 320px grid) -->
|
||||
<div class="grid lg:grid-cols-[1fr_320px] min-h-0">
|
||||
<!-- Email performance chart -->
|
||||
<section class="flex flex-col min-h-0 lg:border-r border-slate-200">
|
||||
<div class="h-9 px-4 border-b border-slate-200 flex items-center gap-2 shrink-0">
|
||||
<span class="text-[11px] font-medium text-slate-700">Email performance</span>
|
||||
</div>
|
||||
<div class="flex-1 flex flex-col px-5 py-4">
|
||||
<div class="h-52 flex items-end gap-0.5">
|
||||
{Array.from({ length: 28 }).map((_, i) => {
|
||||
// Deterministic illustrative pattern, looks alive
|
||||
const v = 22 + Math.sin(i * 0.5) * 18 + ((i * 13) % 17);
|
||||
return (
|
||||
<div class="flex-1 flex items-end">
|
||||
<div class="w-full rounded-sm bg-sky-100" style={`height: ${v}%`}></div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div class="flex items-center gap-3 text-[10.5px] font-mono text-muted-foreground">
|
||||
<span class="inline-flex items-center gap-1.5"><span class="w-2 h-[2px] bg-[#0284c7]"></span>premium</span>
|
||||
<span class="inline-flex items-center gap-1.5"><span class="w-2 h-[2px] bg-slate-400"></span>free</span>
|
||||
<div class="flex justify-between mt-2 font-mono text-[10px] text-slate-400">
|
||||
<span>7d ago</span>
|
||||
<span>today</span>
|
||||
</div>
|
||||
</div>
|
||||
<svg viewBox="0 0 600 180" class="w-full h-44" preserveAspectRatio="none">
|
||||
{[36, 72, 108, 144].map((y) => (
|
||||
<line x1="0" x2="600" y1={y} y2={y} stroke="#e6ebf2" stroke-width="0.5"/>
|
||||
))}
|
||||
<polyline
|
||||
points={Array.from({ length: 14 }, (_, i) => {
|
||||
const v = 65 + Math.sin(i * 0.6) * 10 + i * 1.2;
|
||||
return (i / 13) * 600 + ',' + (180 - v * 1.5);
|
||||
}).join(' ')}
|
||||
fill="none"
|
||||
stroke="#0284c7"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<polyline
|
||||
points={Array.from({ length: 14 }, (_, i) => {
|
||||
const v = 50 + Math.sin(i * 0.5 + 1) * 8 + i * 0.5;
|
||||
return (i / 13) * 600 + ',' + (180 - v * 1.5);
|
||||
}).join(' ')}
|
||||
fill="none"
|
||||
stroke="#94a3b8"
|
||||
stroke-width="1.5"
|
||||
stroke-dasharray="3 3"
|
||||
/>
|
||||
</svg>
|
||||
<div class="mt-2 grid grid-cols-7 text-[10px] font-mono text-muted-foreground/70">
|
||||
{['D-13', 'D-11', 'D-9', 'D-7', 'D-5', 'D-3', 'D-1'].map((d) => <div>{d}</div>)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Mailbox health panel -->
|
||||
<div class="bg-white p-6">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.16em] font-mono text-muted-foreground">Mailbox health</div>
|
||||
<div class="text-[15px] font-semibold text-heading mt-1 mb-4">Per-mailbox band</div>
|
||||
<div class="space-y-2.5">
|
||||
<!-- Breakdown column -->
|
||||
<aside class="flex flex-col min-h-0 bg-slate-50/40">
|
||||
<div class="h-9 px-4 border-b border-slate-200 flex items-center gap-2 shrink-0">
|
||||
<span class="text-[11px] font-medium text-slate-700">Breakdown</span>
|
||||
</div>
|
||||
<div class="divide-y divide-slate-200/60">
|
||||
{[
|
||||
{ name: 'healthy', pct: 75, color: 'bg-emerald-500' },
|
||||
{ name: 'watch', pct: 17, color: 'bg-amber-500' },
|
||||
{ name: 'quarantined', pct: 6, color: 'bg-rose-500' },
|
||||
{ name: 'blocked', pct: 2, color: 'bg-slate-600' },
|
||||
].map((b) => (
|
||||
<div>
|
||||
<div class="flex items-baseline justify-between mb-1 text-[11.5px] font-mono">
|
||||
<span class="text-foreground/85">{b.name}</span>
|
||||
<span class="text-muted-foreground">{b.pct}%</span>
|
||||
</div>
|
||||
<div class="relative h-1.5 rounded-full overflow-hidden bg-[color:var(--surface-1)]">
|
||||
<div class={`absolute inset-y-0 left-0 ${b.color}`} style={`width: ${b.pct}%`}></div>
|
||||
</div>
|
||||
{ label: 'Delivered', dot: 'bg-emerald-500', v: '12,847' },
|
||||
{ label: 'Opened', dot: 'bg-sky-500', v: '4,394' },
|
||||
{ label: 'Replied', dot: 'bg-violet-500', v: '783' },
|
||||
{ label: 'Bounced', dot: 'bg-amber-500', v: '231' },
|
||||
{ label: 'Spam', dot: 'bg-red-500', v: '5' },
|
||||
].map((q) => (
|
||||
<div class="h-9 px-4 flex items-center gap-2">
|
||||
<span class={`w-1.5 h-1.5 rounded-full ${q.dot}`}></span>
|
||||
<span class="text-[12px] text-slate-700">{q.label}</span>
|
||||
<span class="ml-auto font-mono text-[11px] text-slate-500 tabular-nums">{q.v}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<div class="px-5 py-3 border-t border-[color:var(--border)] bg-[color:var(--surface-1)]/60 flex items-center justify-between text-[11px] font-mono text-muted-foreground">
|
||||
<span>Auto-refresh · 60s</span>
|
||||
<span>Export →</span>
|
||||
<!-- Warmup section bar + chart-style strip -->
|
||||
<div class="h-9 px-4 border-t border-b border-slate-200 flex items-center gap-2 shrink-0">
|
||||
<span class="text-[11px] font-medium text-slate-700">Warmup</span>
|
||||
<a href="#" class="ml-auto inline-flex items-center gap-1 text-[11px] text-slate-500 hover:text-slate-900 transition-colors">
|
||||
All accounts
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 7h10v10"/><path d="M7 17 17 7"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="px-5 py-5 grid grid-cols-2 lg:grid-cols-4 gap-3">
|
||||
{[
|
||||
{ mb: 'ben@acme.com', day: 22, today: 32, cap: 40, state: 'healthy' },
|
||||
{ mb: 'sara@acme.com', day: 14, today: 24, cap: 40, state: 'healthy' },
|
||||
{ mb: 'mark@acme.com', day: 31, today: 40, cap: 40, state: 'watch' },
|
||||
{ mb: 'kai@acme.com', day: 9, today: 19, cap: 40, state: 'healthy' },
|
||||
].map((m) => {
|
||||
const pct = Math.round((m.today / m.cap) * 100);
|
||||
const chip = m.state === 'watch' ? 'bg-amber-50 text-amber-700' : 'bg-emerald-50 text-emerald-700';
|
||||
const dot = m.state === 'watch' ? 'bg-amber-500' : 'bg-emerald-500';
|
||||
const bar = m.state === 'watch' ? 'bg-amber-500' : 'bg-sky-500';
|
||||
return (
|
||||
<div class="rounded-md ring-1 ring-slate-200 bg-white p-3">
|
||||
<div class="flex items-baseline justify-between gap-2">
|
||||
<span class="font-mono text-[11px] text-slate-700 truncate">{m.mb}</span>
|
||||
<span class={`inline-flex items-center gap-1 h-4 px-1 rounded text-[9.5px] font-medium ${chip}`}>
|
||||
<span class={`w-1 h-1 rounded-full ${dot}`}></span>
|
||||
{m.state}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-2 flex items-baseline gap-1 font-mono">
|
||||
<span class="text-[18px] font-semibold text-slate-900 tabular-nums">{m.today}</span>
|
||||
<span class="text-[10px] text-slate-400">/ {m.cap} today · D{m.day}</span>
|
||||
</div>
|
||||
<div class="mt-2 h-1 rounded-full bg-slate-100 overflow-hidden">
|
||||
<div class={`h-full ${bar}`} style={`width: ${pct}%`}></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="mt-4 text-[12px] text-muted-foreground italic">
|
||||
Numbers shown are illustrative for the structural mock. Real workspace data appears only after the per-band sample-size thresholds are met.
|
||||
Numbers are illustrative for the structural mock. Pulled from a demo workspace.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user