page.tsx196 lines · main
| 1 | import Link from 'next/link'; |
| 2 | |
| 3 | import { BackgroundGrid } from '../components/marketing/background-grid'; |
| 4 | import { MaintenanceBanner } from '../components/marketing/maintenance-banner'; |
| 5 | import { SiteFooter } from '../components/marketing/site-footer'; |
| 6 | import { SiteHeader } from '../components/marketing/site-header'; |
| 7 | import { getSessionUser } from '../lib/session'; |
| 8 | import { PricingSection } from './pricing-section'; |
| 9 | |
| 10 | interface 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 | */ |
| 21 | async 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 | |
| 51 | export 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't built by a giant with a churn dashboard and a pivot-to-ads roadmap. |
| 157 | it'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 | |
| 173 | function 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 | |
| 185 | function 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 | } |