pull/715/head
Travis Fischer 2025-06-20 14:00:56 -05:00
rodzic a014881c89
commit b71ce684c0
7 zmienionych plików z 111 dodań i 25 usunięć

Wyświetl plik

@ -173,12 +173,13 @@ export const consumerSelectSchema = createSelectSchema(consumers, {
.any() .any()
.refine( .refine(
(deployment): boolean => (deployment): boolean =>
deploymentSelectSchema.safeParse(deployment).success, !deployment || deploymentSelectSchema.safeParse(deployment).success,
{ {
message: 'Invalid lastDeployment' message: 'Invalid lastDeployment'
} }
) )
.transform((deployment): any => { .transform((deployment): any => {
if (!deployment) return undefined
return deploymentSelectSchema.parse(deployment) return deploymentSelectSchema.parse(deployment)
}) })
.optional() .optional()

Wyświetl plik

@ -195,12 +195,14 @@ export const deploymentSelectSchema = createSelectSchema(deployments, {
project: z project: z
.any() .any()
.refine( .refine(
(project): boolean => projectSelectSchema.safeParse(project).success, (project): boolean =>
!project || projectSelectSchema.safeParse(project).success,
{ {
message: 'Invalid lastDeployment' message: 'Invalid lastDeployment'
} }
) )
.transform((project): any => { .transform((project): any => {
if (!project) return undefined
return projectSelectSchema.parse(project) return projectSelectSchema.parse(project)
}) })
.optional() .optional()

Wyświetl plik

@ -237,12 +237,14 @@ export const projectSelectSchema = createSelectSchema(projects, {
.any() .any()
.refine( .refine(
(deployment): boolean => (deployment): boolean =>
deploymentSelectSchema.safeParse(deployment).success, !deployment || deploymentSelectSchema.safeParse(deployment).success,
{ {
message: 'Invalid lastPublishedDeployment' message: 'Invalid lastPublishedDeployment'
} }
) )
.transform((deployment): any => { .transform((deployment): any => {
if (!deployment) return undefined
return deploymentSelectSchema.parse(deployment) return deploymentSelectSchema.parse(deployment)
}) })
.optional(), .optional(),
@ -252,12 +254,13 @@ export const projectSelectSchema = createSelectSchema(projects, {
.any() .any()
.refine( .refine(
(deployment): boolean => (deployment): boolean =>
deploymentSelectSchema.safeParse(deployment).success, !deployment || deploymentSelectSchema.safeParse(deployment).success,
{ {
message: 'Invalid lastDeployment' message: 'Invalid lastDeployment'
} }
) )
.transform((deployment): any => { .transform((deployment): any => {
if (!deployment) return undefined
return deploymentSelectSchema.parse(deployment) return deploymentSelectSchema.parse(deployment)
}) })
.optional() .optional()

Wyświetl plik

@ -163,27 +163,31 @@ export function MarketplaceProjectIndex({
Pricing Plans Pricing Plans
</h2> </h2>
{project.lastPublishedDeployment!.pricingPlans.map((plan) => ( <div className='flex gap-8'>
<div key={plan.slug} className='grid gap-4'> {project.lastPublishedDeployment!.pricingPlans.map((plan) => (
<h3 className='text-center text-balance leading-snug md:leading-none text-xl font-bold'> <div key={plan.slug} className='flex flex-col gap-4'>
{plan.name} <h3 className='text-center text-balance leading-snug md:leading-none text-xl font-bold'>
</h3> {plan.name}
</h3>
<pre className='max-w-lg'>{JSON.stringify(plan, null, 2)}</pre> <pre className='max-w-lg'>
{JSON.stringify(plan, null, 2)}
</pre>
<Button <Button
onClick={() => onSubscribe(plan.slug)} onClick={() => onSubscribe(plan.slug)}
// TODO: handle free plans correctly // TODO: handle free plans correctly
disabled={consumer?.plan === plan.slug} disabled={consumer?.plan === plan.slug}
> >
{consumer?.plan === plan.slug ? ( {consumer?.plan === plan.slug ? (
<span>Currently subscribed to "{plan.name}"</span> <span>Currently subscribed to "{plan.name}"</span>
) : ( ) : (
<span>Subscribe to "{plan.name}"</span> <span>Subscribe to "{plan.name}"</span>
)} )}
</Button> </Button>
</div> </div>
))} ))}
</div>
</div> </div>
</> </>
)} )}

Wyświetl plik

@ -80,5 +80,82 @@ export default defineConfig({
inputSchemaAdditionalProperties: false, inputSchemaAdditionalProperties: false,
outputSchemaAdditionalProperties: false outputSchemaAdditionalProperties: false
} }
],
pricingPlans: [
{
name: 'Free',
slug: 'free',
lineItems: [
{
slug: 'base',
usageType: 'licensed',
amount: 0
},
{
slug: 'requests',
usageType: 'metered',
billingScheme: 'per_unit',
unitAmount: 0
}
]
},
{
name: 'Starter',
slug: 'starter',
lineItems: [
{
slug: 'base',
usageType: 'licensed',
amount: 999 // $9.99 USD
},
{
slug: 'requests',
usageType: 'metered',
billingScheme: 'tiered',
tiersMode: 'volume',
// free for first 1000 requests per month
// then $0.00053 USD for unlimited further requests that month
tiers: [
{
upTo: 1000,
unitAmount: 0
},
{
upTo: 'inf',
unitAmount: 0.053
}
]
}
]
},
{
name: 'Pro',
slug: 'pro',
lineItems: [
{
slug: 'base',
usageType: 'licensed',
amount: 2999 // $29.99 USD
},
{
slug: 'requests',
usageType: 'metered',
billingScheme: 'tiered',
tiersMode: 'volume',
// free for first 10000 requests per month
// then $0.00049 USD for unlimited further requests that month
tiers: [
{
upTo: 10_000,
unitAmount: 0
},
{
upTo: 'inf',
unitAmount: 0.049
}
]
}
]
}
] ]
}) })

Wyświetl plik

@ -75,7 +75,7 @@ export function errorHandler(
console.error('Error Sentry.captureException failed', err, err_) console.error('Error Sentry.captureException failed', err, err_)
} }
} }
} else { } else if (status !== 404) {
logger.warn(status, err) logger.warn(status, err)
} }
} }

Wyświetl plik

@ -18,7 +18,6 @@
- consider a PrettyJson component which displays json but links to resources - consider a PrettyJson component which displays json but links to resources
- stripe - stripe
- stripe checkout for changing plans? (need to at least be able to upgrade) - stripe checkout for changing plans? (need to at least be able to upgrade)
- stripe billing portal
- should we bypass stripe for `free` plans to increase conversions? - should we bypass stripe for `free` plans to increase conversions?
- handle browser back/forward with `?next=` - handle browser back/forward with `?next=`
- add some social proof to signup page - add some social proof to signup page