Add loaders for team and personal account billing pages

A new loader for team and personal account billing pages has been created to fetch subscription and customer data. This change offloads the task of loading account data from the account billing pages to the newly created loaders, thus simplifying the code in the billing pages.

Furthermore, caching allows the same requested to be deduped across server components
This commit is contained in:
giancarlo
2024-04-06 14:09:17 +08:00
parent fa205ace32
commit 9303e8f5dd
5 changed files with 69 additions and 52 deletions

View File

@@ -0,0 +1,25 @@
import { cache } from 'react';
import 'server-only';
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
export const loadPersonalAccountBillingPageData = cache((userId: string) => {
const client = getSupabaseServerComponentClient();
const subscription = client
.from('subscriptions')
.select('*, items: subscription_items !inner (*)')
.eq('account_id', userId)
.maybeSingle()
.then(({ data }) => data);
const customer = client
.from('billing_customers')
.select('customer_id')
.eq('account_id', userId)
.maybeSingle()
.then(({ data }) => data?.customer_id);
return Promise.all([subscription, customer]);
});

View File

@@ -1,22 +1,24 @@
import { SupabaseClient } from '@supabase/supabase-js';
import { redirect } from 'next/navigation';
import {
BillingPortalCard,
CurrentSubscriptionCard,
} from '@kit/billing-gateway/components';
import { Database } from '@kit/supabase/database';
import { requireUser } from '@kit/supabase/require-user';
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
import { If } from '@kit/ui/if';
import { PageBody } from '@kit/ui/page';
import { Trans } from '@kit/ui/trans';
import { UserAccountHeader } from '~/(dashboard)/home/(user)/_components/user-account-header';
import { createPersonalAccountBillingPortalSession } from '~/(dashboard)/home/(user)/billing/server-actions';
import billingConfig from '~/config/billing.config';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
import { createPersonalAccountBillingPortalSession } from '../billing/server-actions';
import { PersonalAccountCheckoutForm } from './_components/personal-account-checkout-form';
// user billing imports
import { loadPersonalAccountBillingPageData } from './_lib/server/personal-account-billing-page.loader';
export const generateMetadata = async () => {
const i18n = await createI18nServerInstance();
@@ -29,7 +31,15 @@ export const generateMetadata = async () => {
async function PersonalAccountBillingPage() {
const client = getSupabaseServerComponentClient();
const [subscription, customerId] = await loadData(client);
const auth = await requireUser(client);
if (auth.error) {
redirect(auth.redirectTo);
}
const [subscription, customerId] = await loadPersonalAccountBillingPageData(
auth.data.id,
);
return (
<>
@@ -79,29 +89,3 @@ function CustomerBillingPortalForm() {
</form>
);
}
async function loadData(client: SupabaseClient<Database>) {
const { data, error } = await client.auth.getUser();
if (error ?? !data?.user) {
throw new Error('Authentication required');
}
const user = data.user;
const subscription = client
.from('subscriptions')
.select('*, items: subscription_items !inner (*)')
.eq('account_id', user.id)
.maybeSingle()
.then(({ data }) => data);
const customer = client
.from('billing_customers')
.select('customer_id')
.eq('account_id', user.id)
.maybeSingle()
.then(({ data }) => data?.customer_id);
return Promise.all([subscription, customer]);
}

View File

@@ -0,0 +1,26 @@
import { cache } from 'react';
import 'server-only';
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
export const loadTeamAccountBillingPage = cache((accountId: string) => {
const client = getSupabaseServerComponentClient();
// TODO: improve these queries to only load the necessary data
const subscription = client
.from('subscriptions')
.select('*, items: subscription_items !inner (*)')
.eq('account_id', accountId)
.maybeSingle()
.then(({ data }) => data);
const customerId = client
.from('billing_customers')
.select('customer_id')
.eq('account_id', accountId)
.maybeSingle()
.then(({ data }) => data?.customer_id);
return Promise.all([subscription, customerId]);
});

View File

@@ -1,5 +1,6 @@
import { SupabaseClient } from '@supabase/supabase-js';
import 'server-only';
import { z } from 'zod';
import { LineItemSchema } from '@kit/billing';

View File

@@ -4,7 +4,6 @@ import {
BillingPortalCard,
CurrentSubscriptionCard,
} from '@kit/billing-gateway/components';
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import { If } from '@kit/ui/if';
import { PageBody, PageHeader } from '@kit/ui/page';
@@ -15,6 +14,7 @@ import billingConfig from '~/config/billing.config';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
import { loadTeamAccountBillingPage } from '../_lib/server/team-account-billing-page.loader';
import { loadTeamWorkspace } from '../_lib/server/team-account-workspace.loader';
import { TeamAccountCheckoutForm } from './_components/team-account-checkout-form';
@@ -36,7 +36,8 @@ export const generateMetadata = async () => {
async function TeamAccountBillingPage({ params }: Params) {
const workspace = await loadTeamWorkspace(params.account);
const accountId = workspace.account.id;
const [subscription, customerId] = await loadAccountData(accountId);
const [subscription, customerId] =
await loadTeamAccountBillingPage(accountId);
const canManageBilling =
workspace.account.permissions.includes('billing.manage');
@@ -119,23 +120,3 @@ function CannotManageBillingAlert() {
</Alert>
);
}
async function loadAccountData(accountId: string) {
const client = getSupabaseServerComponentClient();
const subscription = client
.from('subscriptions')
.select('*, items: subscription_items !inner (*)')
.eq('account_id', accountId)
.maybeSingle()
.then(({ data }) => data);
const customerId = client
.from('billing_customers')
.select('customer_id')
.eq('account_id', accountId)
.maybeSingle()
.then(({ data }) => data?.customer_id);
return Promise.all([subscription, customerId]);
}