kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
pull/715/head
rodzic
a014881c89
commit
b71ce684c0
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Ładowanie…
Reference in New Issue