Skip to content

Commit 99f0518

Browse files
Added a Hosted Postgres page (supabase#46674)
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Created a new solution page for Hosted Postgres. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added Hosted Postgres solution with a dedicated landing page (hero, features, pricing comparison, security, results, CTAs) * Introduced a reusable pricing comparison table component * Enhanced feature grids to support optional headers and richer icon options * **Updates** * Updated company stats (databases managed → 44M, daily launches → 200K, registered developers → 9M) * Added Hosted Postgres link to Solutions navigation * **Refactor** * Consolidated and exported component prop interfaces for cleaner prop contracts <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Terry Sutton <saltcod@gmail.com>
1 parent 8d49701 commit 99f0518

8 files changed

Lines changed: 933 additions & 31 deletions

File tree

apps/www/components/Solutions/CtaSection.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Button, cn } from 'ui'
44

55
import SectionContainer from '@/components/Layouts/SectionContainer'
66

7-
interface Props {
7+
export interface CtaSectionProps {
88
id: string
99
title: string | React.ReactNode
1010
subtitle?: string
@@ -25,7 +25,14 @@ interface Props {
2525
className?: string
2626
}
2727

28-
const CtaSection = ({ id, title, subtitle, primaryCta, secondaryCta, className }: Props) => {
28+
const CtaSection = ({
29+
id,
30+
title,
31+
subtitle,
32+
primaryCta,
33+
secondaryCta,
34+
className,
35+
}: CtaSectionProps) => {
2936
return (
3037
<SectionContainer
3138
id={id}

apps/www/components/Solutions/FeatureGrid.tsx

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,34 @@
1+
import SectionContainer from '~/components/Layouts/SectionContainer'
2+
import type { IconType } from '~/data/solutions/solutions.utils'
13
import React from 'react'
24
import { cn } from 'ui'
3-
import SectionContainer from '~/components/Layouts/SectionContainer'
45

56
interface Feature {
67
id: string
78
title: string
89
description: string | React.ReactNode
9-
icon: string
10+
icon: string | IconType
1011
iconNoStroke?: boolean
1112
className?: string
1213
}
1314

1415
export interface FeatureGridProps {
1516
id: string
1617
features: Feature[]
18+
heading?: React.ReactNode
19+
subheading?: React.ReactNode
1720
className?: string
1821
}
1922

20-
const FeatureGrid = ({ id, features, className }: FeatureGridProps) => {
23+
const FeatureGrid = ({ id, features, heading, subheading, className }: FeatureGridProps) => {
2124
return (
2225
<SectionContainer id={id} className={cn('flex flex-col gap-12 py-16 md:py-24', className)}>
26+
{(heading || subheading) && (
27+
<div className="flex flex-col gap-2 max-w-xl">
28+
{heading && <h2 className="h2 text-foreground-lighter m-0!">{heading}</h2>}
29+
{subheading && <p className="text-foreground-lighter">{subheading}</p>}
30+
</div>
31+
)}
2332
<div
2433
className="
2534
grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3
@@ -41,26 +50,29 @@ const FeatureGrid = ({ id, features, className }: FeatureGridProps) => {
4150
)}
4251
>
4352
<div className="flex items-center gap-2">
44-
{feature.icon && (
45-
<svg
46-
width="18"
47-
height="18"
48-
viewBox="0 0 25 25"
49-
fill={feature.iconNoStroke ? 'currentColor' : 'none'}
50-
xmlns="http://www.w3.org/2000/svg"
51-
>
52-
<path
53-
fillRule="evenodd"
54-
clipRule="evenodd"
55-
d={feature.icon}
56-
stroke={feature.iconNoStroke ? 'none' : 'currentColor'}
57-
strokeMiterlimit="10"
58-
strokeLinejoin="round"
59-
strokeLinecap="round"
60-
strokeWidth="1.5"
61-
/>
62-
</svg>
63-
)}
53+
{feature.icon &&
54+
(typeof feature.icon === 'string' ? (
55+
<svg
56+
width="18"
57+
height="18"
58+
viewBox="0 0 25 25"
59+
fill={feature.iconNoStroke ? 'currentColor' : 'none'}
60+
xmlns="http://www.w3.org/2000/svg"
61+
>
62+
<path
63+
fillRule="evenodd"
64+
clipRule="evenodd"
65+
d={feature.icon}
66+
stroke={feature.iconNoStroke ? 'none' : 'currentColor'}
67+
strokeMiterlimit="10"
68+
strokeLinejoin="round"
69+
strokeLinecap="round"
70+
strokeWidth="1.5"
71+
/>
72+
</svg>
73+
) : (
74+
<feature.icon className="w-[18px] h-[18px]" strokeWidth={1.5} />
75+
))}
6476
<h3 className="">{feature.title}</h3>
6577
</div>
6678
<p className="text-base">{feature.description}</p>
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { Check, Minus } from 'lucide-react'
2+
import React, { type FC, type ReactNode } from 'react'
3+
import { cn } from 'ui'
4+
import { TextLink } from 'ui-patterns/TextLink'
5+
6+
import SectionContainer from '@/components/Layouts/SectionContainer'
7+
8+
export interface PricingComparisonPlan {
9+
name: string
10+
/** Highlights the column visually (eg. the recommended plan). */
11+
highlight?: boolean
12+
}
13+
14+
export interface PricingComparisonRow {
15+
feature: ReactNode
16+
/** One value per plan, in the same order as `plans`. Booleans render as a check or dash. */
17+
values: (ReactNode | boolean)[]
18+
}
19+
20+
export interface PricingComparisonSectionProps {
21+
id?: string
22+
heading: ReactNode
23+
subheading?: ReactNode
24+
plans: PricingComparisonPlan[]
25+
rows: PricingComparisonRow[]
26+
cta?: {
27+
label: string
28+
url: string
29+
}
30+
className?: string
31+
}
32+
33+
const PlanValue = ({ value }: { value: ReactNode | boolean }) => {
34+
if (value === true) {
35+
return <Check className="w-4 h-4 text-brand" strokeWidth={2} aria-label="Included" />
36+
}
37+
if (value === false) {
38+
return (
39+
<Minus className="w-4 h-4 text-foreground-muted" strokeWidth={2} aria-label="Not included" />
40+
)
41+
}
42+
return <span className="text-sm text-foreground">{value}</span>
43+
}
44+
45+
const PricingComparisonSection: FC<PricingComparisonSectionProps> = ({
46+
id,
47+
heading,
48+
subheading,
49+
plans,
50+
rows,
51+
cta,
52+
className,
53+
}) => {
54+
return (
55+
<SectionContainer id={id} className={cn('flex flex-col gap-8 py-16 md:py-24', className)}>
56+
<div className="flex flex-col gap-2 max-w-xl">
57+
<h2 className="h2 text-foreground-lighter m-0!">{heading}</h2>
58+
{subheading && <p className="text-foreground-lighter">{subheading}</p>}
59+
</div>
60+
61+
<div className="w-full overflow-x-auto border border-default rounded-lg bg-surface-75">
62+
<table className="w-full min-w-[480px] border-collapse text-left">
63+
<thead>
64+
<tr className="border-b border-default">
65+
<th
66+
scope="col"
67+
className="py-4 px-4 md:px-6 text-sm font-normal text-foreground-light"
68+
>
69+
What you get
70+
</th>
71+
{plans.map((plan) => (
72+
<th
73+
key={plan.name}
74+
scope="col"
75+
className={cn(
76+
'py-4 px-4 md:px-6 text-sm font-medium text-center',
77+
plan.highlight ? 'text-foreground bg-surface-100' : 'text-foreground'
78+
)}
79+
>
80+
{plan.name}
81+
</th>
82+
))}
83+
</tr>
84+
</thead>
85+
<tbody>
86+
{rows.map((row, rowIndex) => (
87+
<tr
88+
key={rowIndex}
89+
className="border-b border-default last:border-b-0 hover:bg-surface-100/50 transition-colors"
90+
>
91+
<th
92+
scope="row"
93+
className="py-3 px-4 md:px-6 text-sm font-normal text-foreground-light"
94+
>
95+
{row.feature}
96+
</th>
97+
{row.values.map((value, valueIndex) => (
98+
<td
99+
key={valueIndex}
100+
className={cn(
101+
'py-3 px-4 md:px-6 text-center',
102+
plans[valueIndex]?.highlight && 'bg-surface-100'
103+
)}
104+
>
105+
<span className="flex items-center justify-center">
106+
<PlanValue value={value} />
107+
</span>
108+
</td>
109+
))}
110+
</tr>
111+
))}
112+
</tbody>
113+
</table>
114+
</div>
115+
116+
{cta && <TextLink hasChevron label={cta.label} url={cta.url} className="mt-2" />}
117+
</SectionContainer>
118+
)
119+
}
120+
121+
export default PricingComparisonSection

apps/www/data/Footer.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ const footerData = [
5050
{
5151
title: 'Solutions',
5252
links: [
53+
{
54+
text: 'Hosted Postgres',
55+
url: '/solutions/hosted-postgres',
56+
},
5357
...skillBasedSolutions.solutions.map((solution) => ({
5458
text: solution.text,
5559
url: solution.url,

apps/www/data/Solutions.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
BotIcon,
33
Building2Icon,
44
Code2Icon,
5+
Database,
56
HammerIcon,
67
Heart,
78
LightbulbIcon,
@@ -33,6 +34,7 @@ export enum Solutions {
3334
finserv = 'finserv',
3435
healthcare = 'healthcare',
3536
agents = 'agents',
37+
hostedPostgres = 'hosted-postgres',
3638
}
3739

3840
export const skillBasedSolutions = {
@@ -154,6 +156,13 @@ export const useCaseSolutions = {
154156
export const appTypeSolutions = {
155157
label: 'Solutions',
156158
solutions: [
159+
{
160+
id: Solutions.hostedPostgres,
161+
text: 'Hosted Postgres',
162+
description: '',
163+
url: '/solutions/hosted-postgres',
164+
icon: Database,
165+
},
157166
{
158167
id: Solutions.b2bSaaS,
159168
text: 'B2B SaaS',

apps/www/data/company-stats.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@
33
// Number is used for animated components (eg. AnimatedCounter)
44
export const companyStats = {
55
databasesManaged: {
6-
number: 16_000_000,
7-
text: '16,000,000+',
6+
number: 44_000_000,
7+
text: '44,000,000+',
88
label: 'Databases Created',
99
},
1010
databasesLaunchedDaily: {
11-
number: 90_000,
12-
text: '90,000+',
11+
number: 200_000,
12+
text: '200,000+',
1313
label: 'Databases launched daily',
1414
},
1515
developersRegistered: {
16-
number: 7_000_000,
17-
text: '7,000,000+',
16+
number: 9_000_000,
17+
text: '9,000,000+',
1818
label: 'Users',
1919
},
2020
developersRegisteredChange: {

0 commit comments

Comments
 (0)