Skip to content
File

Blob: src/client/pages/about-page.tsx

typescript207 lines
1import { Cloud, Database, Globe, HardDrive, Key, Mail, Radio, Shield, Terminal, Timer } from "lucide-react";
2 
3const STACK = [
4 { icon: Cloud, label: "Cloudflare Workers", desc: "Serverless runtime for API, email handling, and cron cleanup" },
5 { icon: Database, label: "D1 (SQLite)", desc: "Inbox and email metadata with Drizzle ORM" },
6 { icon: HardDrive, label: "R2", desc: "Object storage for email bodies and attachments" },
7 { icon: Key, label: "KV", desc: "Session tokens with automatic TTL expiry" },
8 { icon: Radio, label: "Durable Objects", desc: "Hibernation WebSocket API for real-time push notifications" },
9 { icon: Mail, label: "Email Routing", desc: "Catch-all rules forward inbound mail to the Worker" },
10];
11 
12const FEATURES = [
13 {
14 icon: Timer,
15 title: "Test inboxes",
16 desc: "Spin up an address with a 24, 48, or 72-hour TTL. Auto-cleanup when it expires.",
17 },
18 {
19 icon: Radio,
20 title: "Real-time delivery",
21 desc: "Emails land via WebSocket the moment your app sends them. No polling, no refresh.",
22 },
23 {
24 icon: Globe,
25 title: "Bring your own domain",
26 desc: "Wire up a domain — every address on it catches mail. Match the domain your app uses in production.",
27 },
28 {
29 icon: Shield,
30 title: "Admin dashboard",
31 desc: "Manage domains, inspect active test inboxes, and monitor permanent mailboxes.",
32 },
33];
34 
35export function AboutPage() {
36 return (
37 <div className="mx-auto max-w-4xl space-y-16 pt-8 pb-16">
38 {/* Hero */}
39 <section className="animate-slide-up flex flex-col items-center text-center">
40 <div className="mb-6 inline-flex items-center gap-2 rounded-full border border-accent-500/30 bg-accent-500/10 px-3 py-1 text-xs font-medium tracking-wide text-accent-400">
41 <Terminal className="h-3.5 w-3.5" />
42 Powered by Cloudflare Workers
43 </div>
44 <h1 className="font-display text-4xl font-extrabold tracking-tight text-zinc-100 sm:text-5xl">
45 How flamemail works
46 </h1>
47 <p className="mt-4 max-w-3xl text-sm leading-relaxed text-zinc-400 sm:text-lg">
48 Catch-all email testing for developers, running entirely on Cloudflare's edge.
49 </p>
50
51 {/* Code snippet */}
52 <div className="mt-12 w-full max-w-3xl overflow-hidden rounded-xl border border-zinc-800/60 bg-zinc-900 shadow-sm">
53 <div className="flex items-center gap-2 border-b border-zinc-800/60 bg-zinc-900 px-4 py-3">
54 <div className="flex gap-1.5">
55 <div className="h-3 w-3 rounded-full bg-zinc-700/50"></div>
56 <div className="h-3 w-3 rounded-full bg-zinc-700/50"></div>
57 <div className="h-3 w-3 rounded-full bg-zinc-700/50"></div>
58 </div>
59 <span className="ml-2 font-mono text-xs text-zinc-500">worker.ts — email pipeline</span>
60 </div>
61 <div className="p-4 text-left font-mono text-xs leading-relaxed text-zinc-300 sm:p-6 sm:text-sm">
62 <div className="flex gap-4">
63 <span className="text-zinc-500">01</span>
64 <span>
65 <span className="text-accent-400">export default</span> {"{"}
66 </span>
67 </div>
68 <div className="flex gap-4">
69 <span className="text-zinc-500">02</span>
70 <span className="pl-4">
71 <span className="text-emerald-400">async</span> email(message, env, ctx) {"{"}
72 </span>
73 </div>
74 <div className="flex gap-4 opacity-50">
75 <span className="text-zinc-500">03</span>
76 <span className="pl-8 text-zinc-500">// 1. Parse inbound stream</span>
77 </div>
78 <div className="flex gap-4">
79 <span className="text-zinc-500">04</span>
80 <span className="pl-8">
81 <span className="text-zinc-400">const</span> parsed = <span className="text-emerald-400">await</span>{" "}
82 parseEmail(message.raw);
83 </span>
84 </div>
85 <div className="flex gap-4 opacity-50">
86 <span className="text-zinc-500">05</span>
87 <span className="pl-8 text-zinc-500">// 2. Store body in R2</span>
88 </div>
89 <div className="flex gap-4">
90 <span className="text-zinc-500">06</span>
91 <span className="pl-8">
92 <span className="text-emerald-400">await</span> env.STORAGE.put(id, parsed.html);
93 </span>
94 </div>
95 <div className="flex gap-4 opacity-50">
96 <span className="text-zinc-500">07</span>
97 <span className="pl-8 text-zinc-500">// 3. Notify connected clients via WebSocket</span>
98 </div>
99 <div className="flex gap-4">
100 <span className="text-zinc-500">08</span>
101 <span className="pl-8">
102 <span className="text-emerald-400">await</span> env.INBOX_WS.get(id).fetch(event);
103 </span>
104 </div>
105 <div className="flex gap-4">
106 <span className="text-zinc-500">09</span>
107 <span className="pl-4">{"}"}</span>
108 </div>
109 <div className="flex gap-4">
110 <span className="text-zinc-500">10</span>
111 <span>{"}"}</span>
112 </div>
113 </div>
114 </div>
115 </section>
116
117 {/* Features & Tech Stack */}
118 <div className="grid gap-12 md:grid-cols-2">
119 <section>
120 <h2 className="text-xs font-semibold uppercase tracking-widest text-zinc-500">Features</h2>
121 <dl className="mt-6 space-y-6">
122 {FEATURES.map((f, i) => (
123 <div
124 key={f.title}
125 className="animate-slide-up opacity-0"
126 style={{ animationDelay: `${Math.min(i, 8) * 60 + 200}ms` }}
127 >
128 <dt className="flex items-center gap-2 text-sm font-semibold text-zinc-200">
129 <f.icon className="h-4 w-4 text-accent-400" aria-hidden="true" />
130 {f.title}
131 </dt>
132 <dd className="mt-1 pl-6 text-sm leading-relaxed text-zinc-400">{f.desc}</dd>
133 </div>
134 ))}
135 </dl>
136 </section>
137
138 <section>
139 <h2 className="text-xs font-semibold uppercase tracking-widest text-zinc-500">Technology stack</h2>
140 <div className="mt-6 space-y-3">
141 {STACK.map((s, i) => (
142 <div
143 key={s.label}
144 className="animate-scale-fade flex items-start gap-3 rounded-lg border border-zinc-800/50 bg-zinc-800/30 px-4 py-3 opacity-0"
145 style={{ animationDelay: `${Math.min(i, 8) * 60 + 300}ms` }}
146 >
147 <span className="inline-grid h-7 w-7 shrink-0 place-items-center rounded-full bg-accent-500/10 text-accent-400">
148 <s.icon className="h-3.5 w-3.5" aria-hidden="true" />
149 </span>
150 <div className="min-w-0">
151 <strong className="block text-sm font-medium text-zinc-200">{s.label}</strong>
152 <span className="text-xs text-zinc-400">{s.desc}</span>
153 </div>
154 </div>
155 ))}
156 </div>
157 </section>
158 </div>
159
160 {/* How it works */}
161 <section className="animate-slide-up opacity-0" style={{ animationDelay: "500ms" }}>
162 <h2 className="text-xs font-semibold uppercase tracking-widest text-zinc-500">How it works</h2>
163 <div className="mt-6 rounded-xl border border-zinc-800/60 bg-zinc-900 p-6 sm:p-8">
164 <ol className="space-y-6 text-sm leading-relaxed text-zinc-400">
165 <li className="flex gap-4">
166 <span className="inline-grid h-8 w-8 shrink-0 place-items-center rounded-full bg-zinc-800 text-xs font-semibold text-zinc-300">
167 1
168 </span>
169 <span className="pt-1.5">
170 <strong className="text-zinc-200">Create an inbox</strong> — pick a domain and lifetime. You get a
171 random address and an access token stored in your browser.
172 </span>
173 </li>
174 <li className="flex gap-4">
175 <span className="inline-grid h-8 w-8 shrink-0 place-items-center rounded-full bg-zinc-800 text-xs font-semibold text-zinc-300">
176 2
177 </span>
178 <span className="pt-1.5">
179 <strong className="text-zinc-200">Receive email</strong> — Cloudflare Email Routing forwards inbound
180 mail to the Worker, which parses it, stores the body in R2, and writes metadata to D1.
181 </span>
182 </li>
183 <li className="flex gap-4">
184 <span className="inline-grid h-8 w-8 shrink-0 place-items-center rounded-full bg-zinc-800 text-xs font-semibold text-zinc-300">
185 3
186 </span>
187 <span className="pt-1.5">
188 <strong className="text-zinc-200">Instant notification</strong> — a Durable Object wakes up just long
189 enough to push the new-email event over WebSocket, then hibernates again.
190 </span>
191 </li>
192 <li className="flex gap-4">
193 <span className="inline-grid h-8 w-8 shrink-0 place-items-center rounded-full bg-zinc-800 text-xs font-semibold text-zinc-300">
194 4
195 </span>
196 <span className="pt-1.5">
197 <strong className="text-zinc-200">Automatic cleanup</strong> — an hourly cron purges expired inboxes,
198 their emails, and all R2 objects.
199 </span>
200 </li>
201 </ol>
202 </div>
203 </section>
204 </div>
205 );
206}