page.tsx196 lines · main
1import Link from 'next/link';
2
3import { BackgroundGrid } from '../components/marketing/background-grid';
4import { MaintenanceBanner } from '../components/marketing/maintenance-banner';
5import { SiteFooter } from '../components/marketing/site-footer';
6import { SiteHeader } from '../components/marketing/site-header';
7import { getSessionUser } from '../lib/session';
8import { PricingSection } from './pricing-section';
9
10interface MaintenanceStatus {
11 upcoming: boolean;
12 startsAt: string | null;
13 endsAt: string | null;
14}
15
16/**
17 * Pre-formatted "starts–ends" window when maintenance is scheduled but not yet
18 * live, else null. Fetched from the public api and failed soft to null so a
19 * hiccup never blocks the homepage from rendering.
20 */
21async function getUpcomingWindow(): Promise<string | null> {
22 const origin = process.env.NEXT_PUBLIC_BRIVEN_API_ORIGIN ?? '';
23 if (!origin) return null;
24 try {
25 const res = await fetch(`${origin}/v1/status/maintenance`, {
26 cache: 'no-store',
27 headers: { accept: 'application/json' },
28 });
29 if (!res.ok) return null;
30 const status = (await res.json()) as MaintenanceStatus;
31 if (!status.upcoming || !status.startsAt || !status.endsAt) return null;
32 const fmt = (iso: string) => {
33 const d = new Date(iso);
34 if (Number.isNaN(d.getTime())) return '';
35 return new Intl.DateTimeFormat('en-GB', {
36 day: 'numeric',
37 month: 'short',
38 hour: '2-digit',
39 minute: '2-digit',
40 }).format(d);
41 };
42 const start = fmt(status.startsAt);
43 const end = fmt(status.endsAt);
44 if (!start || !end) return null;
45 return `${start}–${end} local`;
46 } catch {
47 return null;
48 }
49}
50
51export default async function HomePage() {
52 const [user, upcomingWindow] = await Promise.all([
53 getSessionUser().catch(() => null),
54 getUpcomingWindow(),
55 ]);
56 return (
57 <main className="relative min-h-dvh overflow-hidden bg-[var(--color-bg)] text-[var(--color-text)]">
58 {upcomingWindow ? <MaintenanceBanner window={upcomingWindow} /> : null}
59 <BackgroundGrid />
60 <SiteHeader user={user} />
61
62 <section className="relative z-10 mx-auto w-full max-w-6xl px-6 pb-16 pt-20 sm:pt-28">
63 <div className="flex max-w-3xl flex-col gap-6">
64 <LiveBadge />
65 <h1 className="font-sans font-medium leading-[1.05] tracking-[-0.03em] text-[var(--color-text)] text-[var(--text-display-3)] sm:text-[var(--text-display-2)]">
66 the database
67 <br />
68 anyone can use.
69 </h1>
70 <p className="max-w-2xl leading-[1.6] text-[var(--color-text-muted)] text-[var(--text-body)]">
71 create and run a real database — no coding required. start from a template, edit your
72 data like a spreadsheet, and undo any mistake with one click. honest pricing, no
73 surprise bills. made in Flanders, independent.
74 </p>
75
76 <div className="mt-4 flex flex-wrap items-center gap-3">
77 <Link
78 href="/signin"
79 className="inline-flex h-12 items-center justify-center rounded-[var(--radius-md)] bg-[var(--color-primary)] px-6 font-sans font-medium text-[var(--color-text-inverse)] shadow-[var(--shadow-sm)] transition-colors duration-[var(--duration-fast)] ease-[var(--ease-briven)] hover:bg-[var(--color-primary-hover)] active:bg-[var(--color-primary-pressed)]"
80 >
81 get started
82 </Link>
83 <Link
84 href="https://docs.briven.tech"
85 className="inline-flex h-12 items-center justify-center rounded-[var(--radius-md)] border border-[var(--color-border)] bg-transparent px-6 font-sans font-medium text-[var(--color-text)] transition-colors duration-[var(--duration-fast)] ease-[var(--ease-briven)] hover:border-[var(--color-border-strong)] hover:bg-[var(--color-surface-raised)]"
86 >
87 read the docs
88 </Link>
89 </div>
90 </div>
91 </section>
92
93 <section className="relative z-10 mx-auto grid w-full max-w-6xl grid-cols-1 gap-px border-t border-[var(--color-border-subtle)] bg-[var(--color-border-subtle)] sm:grid-cols-2 lg:grid-cols-4">
94 <Pillar
95 title="no code needed"
96 body="build your database by clicking, not coding. add tables and edit rows like a spreadsheet — your first working database in under a minute."
97 />
98 <Pillar
99 title="start from a template"
100 body="pick what you want to track — contacts, inventory, bookings, tasks — and get ready-made tables with example data, instantly."
101 />
102 <Pillar
103 title="an undo button for your data"
104 body="save a snapshot before any big change, restore it in one click if something goes wrong. experiment without fear."
105 />
106 <Pillar
107 title="honest & independent"
108 body="generous limits, no surprise bills. a real Postgres database underneath. made in Flanders, self-funded — not big-tech."
109 />
110 </section>
111
112 <section className="relative z-10 mx-auto w-full max-w-6xl px-6 py-16">
113 <h2 className="font-sans font-medium tracking-[-0.02em] text-[var(--color-text)] text-[var(--text-h2)]">
114 click it, or code it
115 </h2>
116 <div className="mt-6 grid grid-cols-1 gap-px overflow-hidden rounded-[var(--radius-lg)] border border-[var(--color-border-subtle)] bg-[var(--color-border-subtle)] md:grid-cols-2">
117 <div className="flex flex-col gap-3 bg-[var(--color-bg)] p-6">
118 <h3 className="font-mono text-sm text-[var(--color-primary)]">click — no code</h3>
119 <p className="font-sans text-[var(--text-body)] text-[var(--color-text-muted)]">
120 start from a template or a blank database, then build it by clicking: add tables,
121 edit rows like a spreadsheet, link things together. save a snapshot before any big
122 change and undo it in one click. you never have to see code.
123 </p>
124 <Link
125 href="https://briven.tech/signin"
126 className="self-start font-mono text-xs text-[var(--color-text-link)] underline-offset-2 hover:underline"
127 >
128 open the dashboard →
129 </Link>
130 </div>
131 <div className="flex flex-col gap-3 bg-[var(--color-bg)] p-6">
132 <h3 className="font-mono text-sm text-[var(--color-primary)]">code — for developers</h3>
133 <p className="font-sans text-[var(--text-body)] text-[var(--color-text-muted)]">
134 prefer code? keep your schema in version control: write{' '}
135 <code>briven/schema.ts</code>, run <code>briven deploy</code>, and the CLI applies the
136 migration. real SQL, real tables — every standard client works.
137 </p>
138 <Link
139 href="https://docs.briven.tech/quickstart"
140 className="self-start font-mono text-xs text-[var(--color-text-link)] underline-offset-2 hover:underline"
141 >
142 developer quickstart →
143 </Link>
144 </div>
145 </div>
146 <p className="mt-4 font-mono text-xs text-[var(--color-text-subtle)]">
147 both write to the same database. mix freely.
148 </p>
149 </section>
150
151 <section className="relative z-10 mx-auto w-full max-w-3xl px-6 py-16 text-center">
152 <p className="font-mono text-xs uppercase tracking-[0.12em] text-[var(--color-primary)]">
153 not a big-tech company
154 </p>
155 <p className="mt-4 font-sans text-[var(--text-body)] leading-[1.6] text-[var(--color-text-muted)]">
156 briven isn&apos;t built by a giant with a churn dashboard and a pivot-to-ads roadmap.
157 it&apos;s made with <span className="text-[#e8344a]">♥</span> in Flanders by flndrn —{' '}
158 <strong className="text-[var(--color-text)]">
159 100% self-funded, sustainable, and independent
160 </strong>
161 . no investors to appease, no rug-pulls, no acquisition fire-sale. just a tool we run
162 ourselves, built to outlast the hype.
163 </p>
164 </section>
165
166 <PricingSection />
167
168 <SiteFooter />
169 </main>
170 );
171}
172
173function LiveBadge() {
174 return (
175 <div className="inline-flex w-fit items-center gap-2 rounded-[var(--radius-full)] border border-[var(--color-border-primary)] bg-[var(--color-primary-subtle)] px-3 py-1 font-mono text-[var(--color-text)] text-[var(--text-xs)]">
176 <span className="relative flex h-1.5 w-1.5">
177 <span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-[var(--color-primary)] opacity-60" />
178 <span className="relative inline-flex h-1.5 w-1.5 rounded-full bg-[var(--color-primary)]" />
179 </span>
180 public beta · live now · made in Flanders
181 </div>
182 );
183}
184
185function Pillar({ title, body }: { title: string; body: string }) {
186 return (
187 <article className="flex flex-col gap-2 bg-[var(--color-bg)] p-6">
188 <h3 className="font-mono uppercase tracking-[0.12em] text-[var(--color-primary)] text-[var(--text-xs)]">
189 {title}
190 </h3>
191 <p className="leading-[1.6] text-[var(--color-text-muted)] text-[var(--text-small)]">
192 {body}
193 </p>
194 </article>
195 );
196}