✨ (lp) Add new pricing page
This commit is contained in:
committed by
Baptiste Arnaud
parent
d8b1d8ad59
commit
c94a6581be
@ -13,21 +13,23 @@ export const BillingContent = () => {
|
||||
if (!workspace) return null
|
||||
return (
|
||||
<Stack spacing="10" w="full">
|
||||
<CurrentSubscriptionContent
|
||||
plan={workspace.plan}
|
||||
stripeId={workspace.stripeId}
|
||||
onCancelSuccess={() =>
|
||||
refreshWorkspace({
|
||||
plan: Plan.FREE,
|
||||
additionalChatsIndex: 0,
|
||||
additionalStorageIndex: 0,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<UsageContent workspace={workspace} />
|
||||
{workspace.plan !== Plan.LIFETIME && workspace.plan !== Plan.OFFERED && (
|
||||
<ChangePlanForm />
|
||||
)}
|
||||
<Stack gap="2">
|
||||
<CurrentSubscriptionContent
|
||||
plan={workspace.plan}
|
||||
stripeId={workspace.stripeId}
|
||||
onCancelSuccess={() =>
|
||||
refreshWorkspace({
|
||||
plan: Plan.FREE,
|
||||
additionalChatsIndex: 0,
|
||||
additionalStorageIndex: 0,
|
||||
})
|
||||
}
|
||||
/>
|
||||
{workspace.plan !== Plan.LIFETIME &&
|
||||
workspace.plan !== Plan.OFFERED && <ChangePlanForm />}
|
||||
</Stack>
|
||||
|
||||
{workspace.stripeId && <InvoicesList workspace={workspace} />}
|
||||
</Stack>
|
||||
)
|
||||
|
@ -4,8 +4,8 @@ import {
|
||||
Link,
|
||||
Spinner,
|
||||
Stack,
|
||||
Flex,
|
||||
Button,
|
||||
Heading,
|
||||
} from '@chakra-ui/react'
|
||||
import { PlanTag } from 'components/shared/PlanTag'
|
||||
import { Plan } from 'db'
|
||||
@ -35,15 +35,29 @@ export const CurrentSubscriptionContent = ({
|
||||
setIsCancelling(false)
|
||||
}
|
||||
|
||||
const isSubscribed = (plan === Plan.STARTER || plan === Plan.PRO) && stripeId
|
||||
|
||||
if (isCancelling) return <Spinner colorScheme="gray" />
|
||||
return (
|
||||
<Stack gap="2">
|
||||
<Heading fontSize="3xl">Subscription</Heading>
|
||||
<HStack>
|
||||
<Text>Current workspace subscription: </Text>
|
||||
<PlanTag plan={plan} />
|
||||
{isSubscribed && (
|
||||
<Link
|
||||
as="button"
|
||||
color="gray.500"
|
||||
textDecor="underline"
|
||||
fontSize="sm"
|
||||
onClick={cancelSubscription}
|
||||
>
|
||||
Cancel my subscription
|
||||
</Link>
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
{(plan === Plan.STARTER || plan === Plan.PRO) && stripeId && (
|
||||
{isSubscribed && (
|
||||
<>
|
||||
<Stack gap="1">
|
||||
<Text fontSize="sm">
|
||||
@ -59,17 +73,6 @@ export const CurrentSubscriptionContent = ({
|
||||
Billing Portal
|
||||
</Button>
|
||||
</Stack>
|
||||
<Flex>
|
||||
<Link
|
||||
as="button"
|
||||
color="gray.500"
|
||||
textDecor="underline"
|
||||
fontSize="sm"
|
||||
onClick={cancelSubscription}
|
||||
>
|
||||
Cancel my subscription
|
||||
</Link>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
|
@ -60,16 +60,7 @@ export const ChangePlanForm = () => {
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<HStack
|
||||
alignItems="stretch"
|
||||
spacing="4"
|
||||
w="full"
|
||||
pt={
|
||||
workspace?.plan === Plan.STARTER || workspace?.plan === Plan.PRO
|
||||
? '10'
|
||||
: '0'
|
||||
}
|
||||
>
|
||||
<HStack alignItems="stretch" spacing="4" w="full">
|
||||
<StarterPlanContent
|
||||
initialChatsLimitIndex={
|
||||
workspace?.plan === Plan.STARTER ? data?.additionalChatsIndex : 0
|
||||
|
@ -23,10 +23,11 @@ import {
|
||||
getStorageLimit,
|
||||
storageLimit,
|
||||
parseNumberWithCommas,
|
||||
formatPrice,
|
||||
computePrice,
|
||||
} from 'utils'
|
||||
import { MoreInfoTooltip } from '../MoreInfoTooltip'
|
||||
import { FeaturesList } from './components/FeaturesList'
|
||||
import { computePrice, formatPrice } from './helpers'
|
||||
|
||||
type ProPlanContentProps = {
|
||||
initialChatsLimitIndex?: number
|
||||
@ -72,8 +73,6 @@ export const ProPlanContent = ({
|
||||
? getStorageLimit(workspace)
|
||||
: undefined
|
||||
|
||||
console.log('workspaceChatsLimit', workspaceChatsLimit)
|
||||
console.log('workspaceStorageLimit', workspace)
|
||||
const isCurrentPlan =
|
||||
chatsLimit[Plan.PRO].totalIncluded +
|
||||
chatsLimit[Plan.PRO].increaseStep.amount *
|
||||
|
@ -20,10 +20,11 @@ import {
|
||||
getStorageLimit,
|
||||
storageLimit,
|
||||
parseNumberWithCommas,
|
||||
computePrice,
|
||||
formatPrice,
|
||||
} from 'utils'
|
||||
import { MoreInfoTooltip } from '../MoreInfoTooltip'
|
||||
import { FeaturesList } from './components/FeaturesList'
|
||||
import { computePrice, formatPrice } from './helpers'
|
||||
|
||||
type StarterPlanContentProps = {
|
||||
initialChatsLimitIndex?: number
|
||||
|
@ -1,86 +0,0 @@
|
||||
import { Plan } from 'db'
|
||||
import { chatsLimit, prices, storageLimit } from 'utils'
|
||||
|
||||
export const computePrice = (
|
||||
plan: Plan,
|
||||
selectedTotalChatsIndex: number,
|
||||
selectedTotalStorageIndex: number
|
||||
) => {
|
||||
if (plan !== Plan.STARTER && plan !== Plan.PRO) return
|
||||
const {
|
||||
increaseStep: { price: chatsPrice },
|
||||
} = chatsLimit[plan]
|
||||
const {
|
||||
increaseStep: { price: storagePrice },
|
||||
} = storageLimit[plan]
|
||||
return (
|
||||
prices[plan] +
|
||||
selectedTotalChatsIndex * chatsPrice +
|
||||
selectedTotalStorageIndex * storagePrice
|
||||
)
|
||||
}
|
||||
|
||||
const europeanUnionCountryCodes = [
|
||||
'AT',
|
||||
'BE',
|
||||
'BG',
|
||||
'CY',
|
||||
'CZ',
|
||||
'DE',
|
||||
'DK',
|
||||
'EE',
|
||||
'ES',
|
||||
'FI',
|
||||
'FR',
|
||||
'GR',
|
||||
'HR',
|
||||
'HU',
|
||||
'IE',
|
||||
'IT',
|
||||
'LT',
|
||||
'LU',
|
||||
'LV',
|
||||
'MT',
|
||||
'NL',
|
||||
'PL',
|
||||
'PT',
|
||||
'RO',
|
||||
'SE',
|
||||
'SI',
|
||||
'SK',
|
||||
]
|
||||
|
||||
const europeanUnionExclusiveLanguageCodes = [
|
||||
'fr',
|
||||
'de',
|
||||
'it',
|
||||
'el',
|
||||
'pl',
|
||||
'fi',
|
||||
'nl',
|
||||
'hr',
|
||||
'cs',
|
||||
'hu',
|
||||
'ro',
|
||||
'sl',
|
||||
'sv',
|
||||
'bg',
|
||||
]
|
||||
|
||||
export const guessIfUserIsEuropean = () =>
|
||||
navigator.languages.some((language) => {
|
||||
const [languageCode, countryCode] = language.split('-')
|
||||
return countryCode
|
||||
? europeanUnionCountryCodes.includes(countryCode)
|
||||
: europeanUnionExclusiveLanguageCodes.includes(languageCode)
|
||||
})
|
||||
|
||||
export const formatPrice = (price: number) => {
|
||||
const isEuropean = guessIfUserIsEuropean()
|
||||
const formatter = new Intl.NumberFormat(isEuropean ? 'fr-FR' : 'en-US', {
|
||||
style: 'currency',
|
||||
currency: isEuropean ? 'EUR' : 'USD',
|
||||
maximumFractionDigits: 0, // (causes 2500.99 to be printed as $2,501)
|
||||
})
|
||||
return formatter.format(price)
|
||||
}
|
@ -1,7 +1,12 @@
|
||||
import { loadStripe } from '@stripe/stripe-js/pure'
|
||||
import { Plan, User } from 'db'
|
||||
import { env, isDefined, isEmpty, sendRequest } from 'utils'
|
||||
import { guessIfUserIsEuropean } from '../helpers'
|
||||
import {
|
||||
env,
|
||||
guessIfUserIsEuropean,
|
||||
isDefined,
|
||||
isEmpty,
|
||||
sendRequest,
|
||||
} from 'utils'
|
||||
|
||||
type UpgradeProps = {
|
||||
user: User
|
||||
|
@ -155,7 +155,6 @@ const updateSubscription = async (req: NextApiRequest) => {
|
||||
}
|
||||
: undefined,
|
||||
].filter(isDefined)
|
||||
console.log(items)
|
||||
await stripe.subscriptions.update(subscription.id, {
|
||||
items,
|
||||
})
|
||||
@ -171,7 +170,6 @@ const updateSubscription = async (req: NextApiRequest) => {
|
||||
|
||||
const cancelSubscription =
|
||||
(req: NextApiRequest, res: NextApiResponse) => async (userId: string) => {
|
||||
console.log(req.query.stripeId, userId)
|
||||
const stripeId = req.query.stripeId as string | undefined
|
||||
if (!stripeId) return badRequest(res)
|
||||
if (!process.env.STRIPE_SECRET_KEY)
|
||||
@ -189,9 +187,7 @@ const cancelSubscription =
|
||||
const existingSubscription = await stripe.subscriptions.list({
|
||||
customer: workspace.stripeId,
|
||||
})
|
||||
console.log('yes')
|
||||
await stripe.subscriptions.del(existingSubscription.data[0].id)
|
||||
console.log('deleted')
|
||||
await prisma.workspace.update({
|
||||
where: { id: workspace.id },
|
||||
data: {
|
||||
|
@ -18,12 +18,12 @@ const DashboardPage = () => {
|
||||
const { workspace } = useWorkspace()
|
||||
|
||||
useEffect(() => {
|
||||
const subscribePlan = query.subscribePlan as 'pro' | 'starter' | undefined
|
||||
const subscribePlan = query.subscribePlan as Plan | undefined
|
||||
if (workspace && subscribePlan && user && workspace.plan === 'FREE') {
|
||||
setIsLoading(true)
|
||||
pay({
|
||||
user,
|
||||
plan: subscribePlan === 'pro' ? Plan.PRO : Plan.STARTER,
|
||||
plan: subscribePlan,
|
||||
workspaceId: workspace.id,
|
||||
additionalChats: 0,
|
||||
additionalStorage: 0,
|
||||
|
@ -34,8 +34,6 @@ const ResultsPage = () => {
|
||||
})
|
||||
const { data: usageData } = useUsage(workspace?.id)
|
||||
|
||||
console.log(workspace?.id, usageData)
|
||||
|
||||
const chatsLimitPercentage = useMemo(() => {
|
||||
if (!usageData?.totalChatsUsed || !workspace?.plan) return 0
|
||||
return Math.round(
|
||||
@ -53,7 +51,6 @@ const ResultsPage = () => {
|
||||
])
|
||||
|
||||
const storageLimitPercentage = useMemo(() => {
|
||||
console.log(usageData?.totalStorageUsed)
|
||||
if (!usageData?.totalStorageUsed || !workspace?.plan) return 0
|
||||
return Math.round(
|
||||
(usageData.totalStorageUsed /
|
||||
|
Reference in New Issue
Block a user