Update billing page data handling and version bump to 2.12.0 (#300)

* Update billing page data handling and version bump to 2.12.0

- Refactored billing page components to streamline data loading for subscriptions and orders.
- Introduced `getProductPlan` function to encapsulate product plan resolution logic.
- Updated `package.json` version from 2.11.0 to 2.12.0.
This commit is contained in:
Giancarlo Buomprisco
2025-07-16 16:11:55 +07:00
committed by GitHub
parent abcf1ae3d7
commit da8a3a903d
6 changed files with 116 additions and 125 deletions

View File

@@ -2,26 +2,9 @@ import 'server-only';
import { cache } from 'react';
import { z } from 'zod';
import { createAccountsApi } from '@kit/accounts/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
/**
* The variable BILLING_MODE represents the billing mode for a service. It can
* have either the value 'subscription' or 'one-time'. If not provided, the default
* value is 'subscription'. The value can be overridden by the environment variable
* BILLING_MODE.
*
* If the value is 'subscription', we fetch the subscription data for the user.
* If the value is 'one-time', we fetch the orders data for the user.
* if none of these suits your needs, please override the below function.
*/
const BILLING_MODE = z
.enum(['subscription', 'one-time'])
.default('subscription')
.parse(process.env.BILLING_MODE);
/**
* Load the personal account billing page data for the given user.
* @param userId
@@ -36,12 +19,9 @@ function personalAccountBillingPageDataLoader(userId: string) {
const client = getSupabaseServerClient();
const api = createAccountsApi(client);
const data =
BILLING_MODE === 'subscription'
? api.getSubscription(userId)
: api.getOrder(userId);
const subscription = api.getSubscription(userId);
const order = api.getOrder(userId);
const customerId = api.getCustomerId(userId);
return Promise.all([data, customerId]);
return Promise.all([subscription, order, customerId]);
}

View File

@@ -35,24 +35,21 @@ export const generateMetadata = async () => {
async function PersonalAccountBillingPage() {
const user = await requireUserInServerComponent();
const [data, customerId] = await loadPersonalAccountBillingPageData(user.id);
const [subscription, order, customerId] =
await loadPersonalAccountBillingPageData(user.id);
let productPlan: {
product: ProductSchema;
plan: z.infer<typeof PlanSchema>;
} | null = null;
const subscriptionProductPlan = subscription
? await getProductPlan(
subscription.items[0]?.variant_id,
subscription.currency,
)
: undefined;
if (data) {
const firstLineItem = data.items[0];
const orderProductPlan = order
? await getProductPlan(order.items[0]?.variant_id, order.currency)
: undefined;
if (firstLineItem) {
productPlan = await resolveProductPlan(
billingConfig,
firstLineItem.variant_id,
data.currency,
);
}
}
const hasBillingData = subscription || order;
return (
<>
@@ -63,7 +60,7 @@ async function PersonalAccountBillingPage() {
<PageBody>
<div className={'flex flex-col space-y-4'}>
<If condition={!data}>
<If condition={!hasBillingData}>
<PersonalAccountCheckoutForm customerId={customerId} />
<If condition={customerId}>
@@ -71,32 +68,36 @@ async function PersonalAccountBillingPage() {
</If>
</If>
<If condition={data}>
{(data) => (
<If condition={hasBillingData}>
<div className={'flex w-full max-w-2xl flex-col space-y-6'}>
{'active' in data ? (
<If condition={subscription}>
{(subscription) => {
return (
<CurrentSubscriptionCard
subscription={data}
product={productPlan!.product}
plan={productPlan!.plan}
subscription={subscription}
product={subscriptionProductPlan!.product}
plan={subscriptionProductPlan!.plan}
/>
) : (
<CurrentLifetimeOrderCard
order={data}
product={productPlan!.product}
plan={productPlan!.plan}
/>
)}
<If condition={!data}>
<PersonalAccountCheckoutForm customerId={customerId} />
);
}}
</If>
<If condition={customerId}>
<CustomerBillingPortalForm />
<If condition={order}>
{(order) => {
return (
<CurrentLifetimeOrderCard
order={order}
product={orderProductPlan!.product}
plan={orderProductPlan!.plan}
/>
);
}}
</If>
</div>
)}
</If>
<If condition={customerId}>
<CustomerBillingPortalForm />
</If>
</div>
</PageBody>
@@ -113,3 +114,20 @@ function CustomerBillingPortalForm() {
</form>
);
}
async function getProductPlan(
variantId: string | undefined,
currency: string,
): Promise<
| {
product: ProductSchema;
plan: z.infer<typeof PlanSchema>;
}
| undefined
> {
if (!variantId) {
return undefined;
}
return resolveProductPlan(billingConfig, variantId, currency);
}

View File

@@ -815,7 +815,7 @@ export function PageViewsChart() {
desktop: chartData.reduce((acc, curr) => acc + curr.desktop, 0),
mobile: chartData.reduce((acc, curr) => acc + curr.mobile, 0),
}),
[],
[chartData],
);
return (

View File

@@ -2,26 +2,9 @@ import 'server-only';
import { cache } from 'react';
import { z } from 'zod';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { createTeamAccountsApi } from '@kit/team-accounts/api';
/**
* The variable BILLING_MODE represents the billing mode for a service. It can
* have either the value 'subscription' or 'one-time'. If not provided, the default
* value is 'subscription'. The value can be overridden by the environment variable
* BILLING_MODE.
*
* If the value is 'subscription', we fetch the subscription data for the user.
* If the value is 'one-time', we fetch the orders data for the user.
* if none of these suits your needs, please override the below function.
*/
const BILLING_MODE = z
.enum(['subscription', 'one-time'])
.default('subscription')
.parse(process.env.BILLING_MODE);
/**
* @name loadTeamAccountBillingPage
* @description Load the team account billing page data for the given account.
@@ -32,12 +15,9 @@ function teamAccountBillingPageLoader(accountId: string) {
const client = getSupabaseServerClient();
const api = createTeamAccountsApi(client);
const data =
BILLING_MODE === 'subscription'
? api.getSubscription(accountId)
: api.getOrder(accountId);
const subscription = api.getSubscription(accountId);
const order = api.getOrder(accountId);
const customerId = api.getCustomerId(accountId);
return Promise.all([data, customerId]);
return Promise.all([subscription, order, customerId]);
}

View File

@@ -44,24 +44,21 @@ async function TeamAccountBillingPage({ params }: TeamAccountBillingPageProps) {
const workspace = await loadTeamWorkspace(account);
const accountId = workspace.account.id;
const [data, customerId] = await loadTeamAccountBillingPage(accountId);
const [subscription, order, customerId] =
await loadTeamAccountBillingPage(accountId);
let productPlan: {
product: ProductSchema;
plan: z.infer<typeof PlanSchema>;
} | null = null;
const subscriptionProductPlan = subscription
? await getProductPlan(
subscription.items[0]?.variant_id,
subscription.currency,
)
: undefined;
if (data) {
const firstLineItem = data.items[0];
const orderProductPlan = order
? await getProductPlan(order.items[0]?.variant_id, order.currency)
: undefined;
if (firstLineItem) {
productPlan = await resolveProductPlan(
billingConfig,
firstLineItem.variant_id,
data.currency,
);
}
}
const hasBillingData = subscription || order;
const canManageBilling =
workspace.account.permissions.includes('billing.manage');
@@ -102,33 +99,32 @@ async function TeamAccountBillingPage({ params }: TeamAccountBillingPageProps) {
<PageBody>
<div
className={cn(`flex w-full flex-col space-y-4`, {
'max-w-2xl': data,
'max-w-2xl': hasBillingData,
})}
>
<If
condition={data}
fallback={
<div>
<If condition={!hasBillingData}>
<Checkout />
</div>
}
>
{(data) => {
if ('active' in data) {
</If>
<If condition={subscription}>
{(subscription) => {
return (
<CurrentSubscriptionCard
subscription={data}
product={productPlan!.product}
plan={productPlan!.plan}
subscription={subscription}
product={subscriptionProductPlan!.product}
plan={subscriptionProductPlan!.plan}
/>
);
}
}}
</If>
<If condition={order}>
{(order) => {
return (
<CurrentLifetimeOrderCard
order={data}
product={productPlan!.product}
plan={productPlan!.plan}
order={order}
product={orderProductPlan!.product}
plan={orderProductPlan!.plan}
/>
);
}}
@@ -158,3 +154,20 @@ function CannotManageBillingAlert() {
</Alert>
);
}
async function getProductPlan(
variantId: string | undefined,
currency: string,
): Promise<
| {
product: ProductSchema;
plan: z.infer<typeof PlanSchema>;
}
| undefined
> {
if (!variantId) {
return undefined;
}
return resolveProductPlan(billingConfig, variantId, currency);
}

View File

@@ -1,6 +1,6 @@
{
"name": "next-supabase-saas-kit-turbo",
"version": "2.11.0",
"version": "2.12.0",
"private": true,
"sideEffects": false,
"engines": {