From 7b370db0803c77d679d0cb210d311626397ea9f3 Mon Sep 17 00:00:00 2001 From: Alan Daniel Date: Wed, 11 Mar 2026 15:55:21 -0400 Subject: [PATCH] product page concept. --- .../_components/BuiltWithSupabaseSection.tsx | 6 +- .../database/_components/ApiSection.tsx | 127 ++++++++++++++++ .../database/_components/ApiSectionClient.tsx | 136 ++++++++++++++++++ .../database/_components/DatabaseContent.tsx | 44 ++++++ .../database/_components/FeaturesSection.tsx | 131 +++++++++++++++++ .../(products)/database/_components/Hero.tsx | 29 ++++ .../_components/HighlightsSection.tsx | 100 +++++++++++++ .../database/_components/QuoteSection.tsx | 35 +++++ .../database/_components/SqlEditorSection.tsx | 133 +++++++++++++++++ .../_components/TableEditorSection.tsx | 133 +++++++++++++++++ .../database/_components/VisualDemo.tsx | 28 ++++ apps/www/app/(products)/database/page.tsx | 14 ++ apps/www/app/(products)/layout.tsx | 19 +++ .../pages/{database.tsx => database.tsx.bak} | 0 14 files changed, 932 insertions(+), 3 deletions(-) create mode 100644 apps/www/app/(products)/database/_components/ApiSection.tsx create mode 100644 apps/www/app/(products)/database/_components/ApiSectionClient.tsx create mode 100644 apps/www/app/(products)/database/_components/DatabaseContent.tsx create mode 100644 apps/www/app/(products)/database/_components/FeaturesSection.tsx create mode 100644 apps/www/app/(products)/database/_components/Hero.tsx create mode 100644 apps/www/app/(products)/database/_components/HighlightsSection.tsx create mode 100644 apps/www/app/(products)/database/_components/QuoteSection.tsx create mode 100644 apps/www/app/(products)/database/_components/SqlEditorSection.tsx create mode 100644 apps/www/app/(products)/database/_components/TableEditorSection.tsx create mode 100644 apps/www/app/(products)/database/_components/VisualDemo.tsx create mode 100644 apps/www/app/(products)/database/page.tsx create mode 100644 apps/www/app/(products)/layout.tsx rename apps/www/pages/{database.tsx => database.tsx.bak} (100%) diff --git a/apps/www/app/(home)/_components/BuiltWithSupabaseSection.tsx b/apps/www/app/(home)/_components/BuiltWithSupabaseSection.tsx index f761bbacc15..1bd45a7de64 100644 --- a/apps/www/app/(home)/_components/BuiltWithSupabaseSection.tsx +++ b/apps/www/app/(home)/_components/BuiltWithSupabaseSection.tsx @@ -103,7 +103,7 @@ export function BuiltWithSupabaseSection() { {/* Header row */}
-
+

Kickstart your next project
with production ready templates @@ -132,7 +132,7 @@ export function BuiltWithSupabaseSection() { target="_blank" className="group flex flex-col border-b md:border-b-0 md:border-r border-border last:border-r-0 last:border-b-0 hover:bg-surface-75/50 transition-colors" > -
+
{/* Browser frame */}
{/* Title bar */} @@ -191,7 +191,7 @@ export function BuiltWithSupabaseSection() { )}

{example.title}

-

+

{example.description}

diff --git a/apps/www/app/(products)/database/_components/ApiSection.tsx b/apps/www/app/(products)/database/_components/ApiSection.tsx new file mode 100644 index 00000000000..72a6f59ab7e --- /dev/null +++ b/apps/www/app/(products)/database/_components/ApiSection.tsx @@ -0,0 +1,127 @@ +import { createHighlighter, type ThemeRegistration } from 'shiki' +import { ApiSectionClient } from './ApiSectionClient' + +// ── Shiki themes ──────────────────────────────────────────────────────────── + +const supabaseDark: ThemeRegistration = { + name: 'supabase-dark', + type: 'dark', + colors: { + 'editor.background': '#00000000', + 'editor.foreground': '#ffffff', + }, + tokenColors: [ + { scope: ['keyword', 'storage', 'storage.type', 'storage.modifier'], settings: { foreground: '#bda4ff' } }, + { scope: ['entity.name.function', 'support.function', 'entity.name.tag', 'support.class.component'], settings: { foreground: '#3ecf8e' } }, + { scope: ['constant', 'variable.other.constant', 'support.constant'], settings: { foreground: '#3ecf8e' } }, + { scope: ['variable.other.property', 'support.type.property-name', 'meta.object-literal.key', 'entity.other.attribute-name'], settings: { foreground: '#3ecf8e' } }, + { scope: ['string', 'string.quoted'], settings: { foreground: '#ffcda1' } }, + { scope: ['comment', 'punctuation.definition.comment'], settings: { foreground: '#7e7e7e' } }, + { scope: ['variable.parameter'], settings: { foreground: '#ffffff' } }, + { scope: ['punctuation'], settings: { foreground: '#ffffff' } }, + { scope: ['constant.numeric'], settings: { foreground: '#ededed' } }, + { scope: ['markup.underline.link'], settings: { foreground: '#ffffff' } }, + { scope: ['markup.inserted'], settings: { foreground: '#3ecf8e' } }, + { scope: ['markup.deleted'], settings: { foreground: '#F06A50' } }, + ], +} + +const supabaseLight: ThemeRegistration = { + name: 'supabase-light', + type: 'light', + colors: { + 'editor.background': '#00000000', + 'editor.foreground': '#525252', + }, + tokenColors: [ + { scope: ['keyword', 'storage', 'storage.type', 'storage.modifier'], settings: { foreground: '#6b35dc' } }, + { scope: ['entity.name.function', 'support.function', 'entity.name.tag', 'support.class.component'], settings: { foreground: '#15593b' } }, + { scope: ['constant', 'variable.other.constant', 'support.constant'], settings: { foreground: '#15593b' } }, + { scope: ['variable.other.property', 'support.type.property-name', 'meta.object-literal.key', 'entity.other.attribute-name'], settings: { foreground: '#15593b' } }, + { scope: ['string', 'string.quoted'], settings: { foreground: '#f1a10d' } }, + { scope: ['comment', 'punctuation.definition.comment'], settings: { foreground: '#7e7e7e' } }, + { scope: ['variable.parameter'], settings: { foreground: '#525252' } }, + { scope: ['punctuation'], settings: { foreground: '#a0a0a0' } }, + { scope: ['constant.numeric'], settings: { foreground: '#525252' } }, + { scope: ['markup.underline.link'], settings: { foreground: '#525252' } }, + ], +} + +// ── API examples ──────────────────────────────────────────────────────────── + +const API_EXAMPLES = [ + { + title: 'Fetch records', + code: `// Fetch all public rooms with their messages +const { data: rooms } = await supabase + .from('rooms') + .select(\` + id, name, created_at, + messages ( id, text, user_id ) + \`) + .eq('public', true) + .order('created_at', { ascending: false }) + .limit(20)`, + }, + { + title: 'Insert with relations', + code: `// Create a room and its first message +const { data: room } = await supabase + .from('rooms') + .insert({ name: 'Design Team', public: false }) + .select() + .single() + +await supabase + .from('messages') + .insert({ + room_id: room.id, + text: 'Welcome to the team channel!', + user_id: session.user.id, + })`, + }, + { + title: 'Update records', + code: `// Update a room and return the updated row +const { data: updated } = await supabase + .from('rooms') + .update({ + name: 'Engineering Team', + public: false, + updated_at: new Date().toISOString(), + }) + .eq('id', roomId) + .select() + .single()`, + }, + { + title: 'Filter & paginate', + code: `// Full-text search with pagination +const { data, count } = await supabase + .from('rooms') + .select('id, name, member_count', { count: 'exact' }) + .textSearch('name', 'design') + .gte('member_count', 5) + .range(0, 9) + .order('member_count', { ascending: false })`, + }, +] + +// ── Server component: pre-highlights all code ─────────────────────────────── + +export async function ApiSection() { + const hl = await createHighlighter({ + themes: [supabaseDark, supabaseLight], + langs: ['javascript'], + }) + + const examples = API_EXAMPLES.map((example) => ({ + title: example.title, + darkHtml: hl.codeToHtml(example.code, { lang: 'javascript', theme: 'supabase-dark' }), + lightHtml: hl.codeToHtml(example.code, { lang: 'javascript', theme: 'supabase-light' }), + })) + + hl.dispose() + + return +} diff --git a/apps/www/app/(products)/database/_components/ApiSectionClient.tsx b/apps/www/app/(products)/database/_components/ApiSectionClient.tsx new file mode 100644 index 00000000000..4ab06fa33ec --- /dev/null +++ b/apps/www/app/(products)/database/_components/ApiSectionClient.tsx @@ -0,0 +1,136 @@ +'use client' + +import { AnimatePresence, motion } from 'framer-motion' +import Link from 'next/link' +import { useState } from 'react' +import { Badge, cn } from 'ui' + +type ApiExample = { + title: string + darkHtml: string + lightHtml: string +} + +const LIBRARIES = [ + { label: 'Javascript', href: '/docs/reference/javascript/introduction' }, + { label: 'Flutter', href: '/docs/reference/dart/introduction' }, + { label: 'Python', href: '/docs/reference/python/introduction' }, + { label: 'C#', href: '/docs/reference/csharp/introduction' }, + { label: 'Kotlin', href: '/docs/reference/kotlin/introduction' }, + { label: 'Swift', href: '/docs/reference/swift/introduction' }, +] + +export function ApiSectionClient({ examples }: { examples: ApiExample[] }) { + const [activeIdx, setActiveIdx] = useState(0) + const active = examples[activeIdx] + + return ( +
+
+ {/* Left: title + libraries */} +
+
+
+ Never write an API + + + + again + + + +
+

+ We introspect your database and provide instant APIs. Focus on building your product, + while Supabase handles the CRUD. +

+
+ + {/* Available libraries */} +
+ Available libraries +
+ {LIBRARIES.map((lib) => ( + + {lib.label} + + ))} +
+
+
+ + {/* Right: tabs + code */} +
+ {/* Tab row */} +
+ {examples.map((example, index) => ( + + ))} +
+ + {/* Code area */} +
+ + +
+
+ + + + Documentation + + + + +
+
+
+
+ ) +} diff --git a/apps/www/app/(products)/database/_components/DatabaseContent.tsx b/apps/www/app/(products)/database/_components/DatabaseContent.tsx new file mode 100644 index 00000000000..b5373571b9f --- /dev/null +++ b/apps/www/app/(products)/database/_components/DatabaseContent.tsx @@ -0,0 +1,44 @@ +import { BuiltWithSupabaseSection } from '~/app/(home)/_components/BuiltWithSupabaseSection' +import { CTASection } from '~/app/(home)/_components/CTASection' +import { FeaturesSection } from './FeaturesSection' +import { Hero } from './Hero' +import { HighlightsSection } from './HighlightsSection' +import { QuoteSection } from './QuoteSection' +import { SqlEditorSection } from './SqlEditorSection' +import { TableEditorSection } from './TableEditorSection' +import { VisualDemo } from './VisualDemo' + +export function DatabaseContent({ apiSlot }: { apiSlot: React.ReactNode }) { + return ( +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ {apiSlot} +
+
+ +
+
+ +
+
+ ) +} diff --git a/apps/www/app/(products)/database/_components/FeaturesSection.tsx b/apps/www/app/(products)/database/_components/FeaturesSection.tsx new file mode 100644 index 00000000000..d287813b314 --- /dev/null +++ b/apps/www/app/(products)/database/_components/FeaturesSection.tsx @@ -0,0 +1,131 @@ +import { cn } from 'ui' + +function SkeletonBar({ className }: { className?: string }) { + return
+} + +const FEATURES = [ + { + title: 'Just Postgres', + description: 'Every Supabase project is a dedicated Postgres database.', + detail: '100% portable. Bring your existing Postgres database, or migrate away at any time.', + visual: PostgresSkeleton, + }, + { + title: 'Secure by default', + description: "Leveraging Postgres's proven Row Level Security.", + detail: 'Integrated with JWT authentication which controls exactly what your users can access.', + visual: RLSSkeleton, + }, + { + title: 'Realtime enabled', + description: 'Data-change listeners over websockets.', + detail: 'Subscribe and react to database changes, milliseconds after they happen.', + visual: RealtimeSkeleton, + }, +] + +function PostgresSkeleton() { + return ( +
+ +
+ + + +
+
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+
+ ) +} + +function RLSSkeleton() { + return ( +
+
+ + +
+
+ + +
+
+ + +
+
+ ) +} + +function RealtimeSkeleton() { + return ( +
+
+ + +
+
+ {Array.from({ length: 4 }).map((_, i) => ( +
+ + +
+ ))} +
+
+ ) +} + +export function FeaturesSection() { + return ( +
+ {/* Header */} +
+
+
+

+ Everything you need +
+ from your database +

+
+
+
+ + {/* 3-col grid */} +
+
+ {FEATURES.map((feature) => { + const Visual = feature.visual + return ( +
+ {/* Visual with overlaid title */} +
+ +
+ + {feature.title} + +
+
+ {/* Content */} +
+

{feature.description}

+

{feature.detail}

+
+
+ ) + })} +
+
+
+ ) +} diff --git a/apps/www/app/(products)/database/_components/Hero.tsx b/apps/www/app/(products)/database/_components/Hero.tsx new file mode 100644 index 00000000000..0dd73d9da06 --- /dev/null +++ b/apps/www/app/(products)/database/_components/Hero.tsx @@ -0,0 +1,29 @@ +import Link from 'next/link' +import { Button } from 'ui' + +export function Hero() { + return ( +
+
+
+

+ Postgres + without the hassle +

+
+ + +
+
+

+ Every Supabase project is a dedicated PostgreSQL database. 100% portable with no vendor + lock-in. +

+
+
+ ) +} diff --git a/apps/www/app/(products)/database/_components/HighlightsSection.tsx b/apps/www/app/(products)/database/_components/HighlightsSection.tsx new file mode 100644 index 00000000000..122171a89f5 --- /dev/null +++ b/apps/www/app/(products)/database/_components/HighlightsSection.tsx @@ -0,0 +1,100 @@ +import { Check } from 'lucide-react' +import Image from 'next/image' +import Link from 'next/link' +import { Badge, Button } from 'ui' + +const HIGHLIGHTS = [ + { + title: 'Branching', + badge: 'New', + features: [ + 'Branch your Supabase project', + 'Sync with your git branches', + 'Manage every Preview from the Dashboard', + 'Support for Vercel Previews', + ], + cta: { label: 'Learn more', href: '/docs/guides/platform/branching' }, + image: { + dark: '/images/product/database/branching.svg', + light: '/images/product/database/branching-light.svg', + }, + }, + { + title: 'Read Replicas', + badge: 'New', + features: [ + 'Serve data closer to your users', + 'Provide data redundancy', + 'Run complex queries without affecting your primary database', + 'Distribute load across various databases', + ], + cta: { label: 'Learn more', href: '/docs/guides/platform/read-replicas' }, + image: { + dark: '/images/product/database/read-replicas.svg', + light: '/images/product/database/read-replicas-light.svg', + }, + }, +] + +export function HighlightsSection() { + return ( +
+ {/* Header */} +
+

+ Built for scale +
+ with powerful new features +

+
+ +
+ {HIGHLIGHTS.map((highlight) => ( +
+
+
+
+

{highlight.title}

+ {highlight.badge && {highlight.badge}} +
+
    + {highlight.features.map((feature) => ( +
  • + + {feature} +
  • + ))} +
+
+
+ +
+
+ {/* Decorative image */} +
+ {highlight.title} + {highlight.title} +
+
+ ))} +
+
+ ) +} diff --git a/apps/www/app/(products)/database/_components/QuoteSection.tsx b/apps/www/app/(products)/database/_components/QuoteSection.tsx new file mode 100644 index 00000000000..2df499bb643 --- /dev/null +++ b/apps/www/app/(products)/database/_components/QuoteSection.tsx @@ -0,0 +1,35 @@ +import Image from 'next/image' +import Link from 'next/link' + +export function QuoteSection() { + return ( +
+
+
+ + You can have a really great product, but you need to want to work with the people behind + it. With Supabase, we always felt very aligned. + + + +
+ Howard Haynes, CPO at Next Door Lending + +
+ Howard Haynes + CPO at Next Door Lending +
+
+ +
+
+
+ ) +} diff --git a/apps/www/app/(products)/database/_components/SqlEditorSection.tsx b/apps/www/app/(products)/database/_components/SqlEditorSection.tsx new file mode 100644 index 00000000000..ea24867899f --- /dev/null +++ b/apps/www/app/(products)/database/_components/SqlEditorSection.tsx @@ -0,0 +1,133 @@ +'use client' + +import { AnimatePresence, motion } from 'framer-motion' +import Image from 'next/image' +import { useState } from 'react' +import { cn } from 'ui' + +const TABS = [ + { + label: 'SQL Editor built in', + title: 'Full SQL', + description: + 'Create tables, functions, join tables — a full SQL editor built into the dashboard.', + image: '/images/product/database/sql-view/sql-editor.png', + }, + { + label: 'Monaco editor', + title: 'Monaco editor', + description: 'Built-in Monaco editor, with rich validation and autocomplete.', + image: '/images/product/database/sql-view/manaco-editor.png', + }, + { + label: 'Favorite queries', + title: 'Favorite queries', + description: 'Save your favorite queries to use again and again.', + image: '/images/product/database/sql-view/favorites.png', + }, + { + label: 'Select and Export', + title: 'Export to CSV', + description: 'Any output can be exported into a CSV.', + image: '/images/product/database/sql-view/export.png', + }, +] + +export function SqlEditorSection() { + const [activeIdx, setActiveIdx] = useState(0) + const active = TABS[activeIdx] + + return ( +
+ {/* Header */} +
+

+ The power of +
+ SQL Editor +

+
+ +
+ {/* Left: tabs + content */} +
+ {/* Tab row */} +
+ {TABS.map((tab, index) => ( + + ))} +
+ + {/* Tab content + tweet */} +
+ {/* Active tab content */} +
+ + +

{active.title}

+

{active.description}

+
+
+
+ + {/* Tweet card */} +
+
+ @jim_bisenius +
+ @jim_bisenius +

+ @MongoDB or @MySQL?!?! Please, let me introduce you to @supabase and the + wonderful world of @Postgres before it's too late!! +

+
+
+
+
+
+ + {/* Right: visual */} +
+ + + {active.title} + + +
+
+
+ ) +} diff --git a/apps/www/app/(products)/database/_components/TableEditorSection.tsx b/apps/www/app/(products)/database/_components/TableEditorSection.tsx new file mode 100644 index 00000000000..5e15c37f67f --- /dev/null +++ b/apps/www/app/(products)/database/_components/TableEditorSection.tsx @@ -0,0 +1,133 @@ +'use client' + +import { AnimatePresence, motion } from 'framer-motion' +import Image from 'next/image' +import { useState } from 'react' +import { cn } from 'ui' + +const TABS = [ + { + label: 'Spreadsheet like editing', + title: 'The simplicity of a spreadsheet', + description: 'Add, edit, and update your data with the simplicity of a no-code tool.', + image: '/images/product/database/table-view/spreadsheet-interface.png', + }, + { + label: 'Create tables', + title: 'Create tables', + description: + 'Add tables, columns and rows right in the dashboard. Without a single line of SQL.', + image: '/images/product/database/table-view/create-table.png', + }, + { + label: 'Set up foreign keys', + title: 'Set up foreign keys', + description: + 'Build connections throughout your data with the full power of a Relational Database.', + image: '/images/product/database/table-view/foreign-keys.png', + }, + { + label: 'Select and Export', + title: 'Select and Export', + description: 'Pick the rows you want and export them into a CSV.', + image: '/images/product/database/table-view/export.png', + }, +] + +export function TableEditorSection() { + const [activeIdx, setActiveIdx] = useState(0) + const active = TABS[activeIdx] + + return ( +
+ {/* Header */} +
+

+ Easy to use +
+ Table Editor +

+
+ +
+ {/* Left: tabs + content */} +
+ {/* Tab row */} +
+ {TABS.map((tab, index) => ( + + ))} +
+ + {/* Tab content + tweet */} +
+ {/* Active tab content */} +
+ + +

{active.title}

+

{active.description}

+
+
+
+ + {/* Tweet card */} +
+
+ @Elsolo244 +
+ @Elsolo244 +

+ Where has @supabase been all my life? +

+
+
+
+
+
+ + {/* Right: visual */} +
+ + + {active.title} + + +
+
+
+ ) +} diff --git a/apps/www/app/(products)/database/_components/VisualDemo.tsx b/apps/www/app/(products)/database/_components/VisualDemo.tsx new file mode 100644 index 00000000000..fa15a6076d6 --- /dev/null +++ b/apps/www/app/(products)/database/_components/VisualDemo.tsx @@ -0,0 +1,28 @@ +export function VisualDemo() { + return ( +
+
+
+ {/* Gradient blob behind card */} +
+ {/* Card */} +
+ Visual demo +
+
+
+
+ ) +} diff --git a/apps/www/app/(products)/database/page.tsx b/apps/www/app/(products)/database/page.tsx new file mode 100644 index 00000000000..cc3ced04f75 --- /dev/null +++ b/apps/www/app/(products)/database/page.tsx @@ -0,0 +1,14 @@ +import type { Metadata } from 'next' + +import { ApiSection } from './_components/ApiSection' +import { DatabaseContent } from './_components/DatabaseContent' + +export const metadata: Metadata = { + title: 'Database | Supabase', + description: + 'Every Supabase project is a dedicated PostgreSQL database. 100% portable with no vendor lock-in.', +} + +export default function DatabasePage() { + return } /> +} diff --git a/apps/www/app/(products)/layout.tsx b/apps/www/app/(products)/layout.tsx new file mode 100644 index 00000000000..0b0daedacca --- /dev/null +++ b/apps/www/app/(products)/layout.tsx @@ -0,0 +1,19 @@ +'use client' + +import Footer from '~/components/Footer' +import Nav from '~/components/Nav' +import { useForceDeepDark } from 'lib/theme.utils' + +export default function ProductsLayout({ children }: { children: React.ReactNode }) { + useForceDeepDark() + + return ( + <> +