Skip to content

Commit

Permalink
Update billing and limits
Browse files Browse the repository at this point in the history
  • Loading branch information
1pxone committed Feb 25, 2025
1 parent 1876661 commit c7936fa
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 79 deletions.
9 changes: 9 additions & 0 deletions .changeset/sharp-toys-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'rushdb-dashboard': minor
'rushdb-core': minor
'rushdb-website': minor
'rushdb-docs': minor
'@rushdb/javascript-sdk': minor
---

Update billing and limits
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const WORKSPACE_LIMITS_PRO: TWorkspaceLimits = {
}

export const WORKSPACE_LIMITS_START: TWorkspaceLimits = {
records: 400_000,
records: 100_000,
projects: null, // UNLIMITED
importSize: 32 * 1024 * 1024 // 32 MB
}
Expand Down
10 changes: 6 additions & 4 deletions platform/dashboard/src/pages/workspace/billing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ const benefitsMap: Record<PlanId, Array<{ description?: string; title: string }>
free: [{ title: '2 Projects' }, { title: '10,000 Records' }, { title: 'Community support' }],
start: [
{ title: 'Unlimited Projects' },
{ title: '400,000 Records', description: 'Next 400,000 Records for $9 per month' },
{ title: 'Priority Support' }
{ title: '100,000 Records' },
{ title: '7-Day Backups', description: 'Coming soon' },
{ title: '3 Team Members per Project', description: 'Coming soon' }
],
pro: [
{ title: 'Unlimited Projects' },
{ title: '1,000,000 Records', description: 'Next 1,000,000 Records for $19 per month' },
{ title: 'Backups', description: 'Coming soon' },
{ title: '1,000,000 Records' },
{ title: '30-Day Backups', description: 'Coming soon' },
{ title: '10 Team Members per Project', description: 'Coming soon' },
{ title: 'Priority Support' }
]
}
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"dependencies": {
"@radix-ui/react-portal": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.2",
"@radix-ui/react-switch": "^1.1.2",
"classnames": "2.3.2",
"framer-motion": "^11.15.0",
"lucide-react": "^0.473.0",
Expand Down
30 changes: 30 additions & 0 deletions website/src/components/Switch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use client'

import * as React from 'react'
import * as SwitchPrimitives from '@radix-ui/react-switch'

import cn from 'classnames'

const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
'focus-visible:ring-ring focus-visible:ring-offset-background peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
'data-[state=checked]:bg-accent data-[state=unchecked]:bg-stroke data-[state=checked]:border-accent border-2',
className
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
'pointer-events-none block h-4 w-4 rounded-full bg-white ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0'
)}
/>
</SwitchPrimitives.Root>
))
Switch.displayName = SwitchPrimitives.Root.displayName

export { Switch }
196 changes: 122 additions & 74 deletions website/src/sections/Pricing.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react'
import { useMemo, useState } from 'react'
import { Section, SectionHeader, SectionSubtitle, SectionTitle } from '~/components/Section'

import { ComponentPropsWithoutRef, ReactNode } from 'react'
Expand All @@ -9,19 +9,20 @@ import { ArrowUpRight, Check, Loader } from 'lucide-react'
import Link from 'next/link'
import { links } from '~/config/urls'
import { useBillingData } from '~/hooks/useBillingData'
import { Switch } from '~/components/Switch'

function Feat({ title, subtitle }: { title: ReactNode; subtitle?: ReactNode }) {
return (
<li className="py-3 text-start">
<div>
<span className="text-start text-base font-medium">
<Check className="mr-3 h-4 w-4 shrink-0" />
<span className="text-content2 text-start text-base font-medium">
<Check className="text-accent mr-3 h-4 w-4 shrink-0" />

{title}
</span>
</div>

{subtitle && <div className="text-content2 pl-7 text-start text-sm">{subtitle}</div>}
{subtitle && <div className="text-content3 pl-7 text-start text-sm">{subtitle}</div>}
</li>
)
}
Expand All @@ -33,42 +34,51 @@ function PricingCard({
className,
price,
children,
headline,
pricePlaceholder,
featured,
...props
}: ComponentPropsWithoutRef<'div'> & {
title?: ReactNode
description: ReactNode
action: ReactNode
headline?: string
price?: number | 'free'
pricePlaceholder?: string
featured?: boolean
}) {
return (
<article
className={cx(
'bg-secondary flex flex-col items-center rounded-xl p-1 text-center shadow-lg first:rounded-l-3xl last:rounded-r-3xl sm:!rounded-xl [&:first-child>div]:rounded-l-[20px] [&:last-child>div]:rounded-r-[20px]',
featured ? 'from-accent-hover to-accent-orange bg-gradient-to-br' : 'bg-secondary',
'bg-secondary flex flex-col items-center rounded-xl p-1 text-center first:rounded-l-3xl last:rounded-r-3xl sm:!rounded-xl [&:first-child>div]:rounded-l-[20px] [&:last-child>div]:rounded-r-[20px]',
featured ? 'border-accent border-2' : 'bg-secondary border-2',
className
)}
{...props}
>
<div
className={cx(
'flex h-full w-full flex-col items-center rounded-lg p-5 sm:!rounded-lg',
'flex h-full w-full flex-col items-center justify-between gap-4 rounded-lg p-4 sm:!rounded-lg',
featured ? 'bg-fill' : 'bg-secondary'
)}
>
<h3 className="typography-sm h-5 font-bold">{title}</h3>
<div className="w-full p-4">
<h3 className="typography-sm text-accent h-5 font-bold">{title}</h3>

<span className="typography-2xl font-bold uppercase">
{price !== 'free' && price !== undefined && '$'}
{price ?? 'Custom'}
</span>
<span className="typography-2xl font-bold capitalize">
{price !== 'free' && price !== undefined && '$'}
{price ?? pricePlaceholder ?? 'Custom'}
</span>

<p className="text-content2 mb-3">{description}</p>
<p className="text-content3">{description}</p>
</div>

<div className="grid w-full">{action}</div>
<div className="w-full flex-auto text-left">
<p className="text-content3 mb-2">&nbsp;{headline}</p>

<ul className="mt-5 flex w-full flex-col divide-y">{children}</ul>
<ul className="flex w-full flex-col divide-y">{children}</ul>
</div>
<div className="grid w-full">{action}</div>
</div>
</article>
)
Expand All @@ -83,8 +93,24 @@ export function Pricing() {
const [variant] = useState<Variants>(Variants.Cloud)
const { loading, data } = useBillingData()

const monthlyPricePro = data?.pro?.month?.amount
const monthlyPriceStart = data?.start?.month?.amount
const [annual, setAnnual] = useState(false)

const prices = useMemo(
() => ({
start: {
month: data?.start?.month?.amount,
annual: data?.start?.annual?.amount
},
pro: {
month: data?.pro?.month?.amount,
annual: data?.pro?.annual?.amount
}
}),
[data]
)

const startPrice = useMemo(() => prices.start[annual ? 'annual' : 'month'], [annual, prices])
const proPrice = useMemo(() => prices.pro[annual ? 'annual' : 'month'], [annual, prices])

if (loading) {
return (
Expand All @@ -93,71 +119,93 @@ export function Pricing() {
</div>
)
}

return (
<Section className="container">
<SectionHeader className="text-center">
<SectionTitle className="m-auto max-w-3xl">Pricing</SectionTitle>

<SectionSubtitle>
{variant === Variants.Cloud && 'Start building for free with the power to scale.'}
<SectionSubtitle className="text-content3">
Start building for free with the power to scale
</SectionSubtitle>
</SectionHeader>

<div className="grid grid-cols-3 gap-3 sm:grid-cols-1 md:grid-cols-2">
{variant === Variants.Cloud && (
<>
<PricingCard
price="free"
description="Forever"
action={
<Button size="small" variant="outline" as={Link} href={links.app} target="_blank">
Start Building
<ArrowUpRight />
</Button>
}
>
<Feat title="100 000 Records" />
<Feat title="2 Projects" />
<Feat title="Community Support" />
</PricingCard>
{monthlyPriceStart && (
<PricingCard
price={monthlyPriceStart}
featured
title="Start"
description="Monthly"
action={
<Button size="small" variant="accent" as={Link} href={links.app}>
Start Building
<ArrowUpRight />
</Button>
}
>
<Feat title="400 000 Records" />
<Feat title="Unlimited Projects" />
<Feat title="Priority Support" />
</PricingCard>
)}
{monthlyPricePro && (
<PricingCard
price={monthlyPricePro}
title="Pro"
description="Monthly"
action={
<Button size="small" variant="accent" as={Link} href={links.app}>
Start Building
<ArrowUpRight />
</Button>
}
>
<Feat title="1 000 000 Records" />
<Feat title="Unlimited Projects" />
<Feat title="Backups" subtitle="Coming soon" />
<Feat title="Priority Support" />
</PricingCard>
)}
</>
<div className="mx-auto flex items-center gap-4">
<p className="text-content3">Monthly</p>
<Switch checked={annual} onCheckedChange={setAnnual} aria-readonly />
<p className="text-content3">Annually</p>
</div>
<div className="grid grid-cols-4 gap-4 sm:grid-cols-1 md:grid-cols-2">
<PricingCard
price="free"
title="Free"
description="Forever"
headline={' '}
action={
<Button size="small" variant="outline" as={Link} href={links.app} target="_blank">
Continue Free
<ArrowUpRight />
</Button>
}
>
<Feat title="Unlimited API Requests" />
<Feat title="10 000 Records" />
<Feat title="2 Projects" subtitle="Projects are never paused, available for commercial use" />
<Feat title="1 Team Member per Project" />
<Feat title="Community Support" />
</PricingCard>
{startPrice && (
<PricingCard
price={startPrice}
title="Start"
description={annual ? 'Annually' : 'Monthly'}
headline="Everything in Free, plus:"
action={
<Button size="small" variant="outline" as={Link} href={links.app}>
Start Building
<ArrowUpRight />
</Button>
}
>
<Feat title="100 000 Records" />
<Feat title="Unlimited Projects" />
<Feat title="7-Day Backup" subtitle="Coming soon" />
<Feat title="3 Team Members per Project" subtitle="Coming soon" />
</PricingCard>
)}
{proPrice && (
<PricingCard
price={proPrice}
title="Pro"
featured
description={annual ? 'Annually' : 'Monthly'}
headline="Everything in Start, plus:"
action={
<Button size="small" variant="accent" as={Link} href={links.app}>
Start Building
<ArrowUpRight />
</Button>
}
>
<Feat title="1 000 000 Records" />
<Feat title="Unlimited Projects" />
<Feat title="30-Day Backup" subtitle="Coming soon" />
<Feat title="10 Team Members per Project" subtitle="Coming soon" />
<Feat title="Priority Support" />
</PricingCard>
)}
<PricingCard
className="opacity-50"
title="Enterprise"
pricePlaceholder={'Custom'}
description="Coming soon"
headline="Everything in Pro, plus:"
action={null}
>
<Feat title="Unlimited Everything" />
<Feat title="Premium Support" />
<Feat title="Uptime SLAs" />
<Feat title="Customizations" />
</PricingCard>
</div>
</Section>
)
Expand Down

0 comments on commit c7936fa

Please sign in to comment.