Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/smart-pandas-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': patch
---

Use hooks exported from `@clerk/shared` to query commerce data.
10 changes: 10 additions & 0 deletions .changeset/violet-terms-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@clerk/shared': minor
'@clerk/types': minor
---

Introduce experimental paginated hooks for commerce data.
- `useStatements`
- `usePaymentAttempts`
- `usePaymentMethods`
Prefixed with `__experimental_`
5 changes: 2 additions & 3 deletions packages/clerk-js/src/ui/components/Checkout/CheckoutForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { Select, SelectButton, SelectOptionList } from '@/ui/elements/Select';
import { Tooltip } from '@/ui/elements/Tooltip';

import { DevOnly } from '../../common/DevOnly';
import { useCheckoutContext, usePaymentSources } from '../../contexts';
import { useCheckoutContext, usePaymentMethods } from '../../contexts';
import { Box, Button, Col, descriptors, Flex, Form, localizationKeys, Text } from '../../customizables';
import { ChevronUpDown, InformationCircle } from '../../icons';
import { handleError } from '../../utils';
Expand Down Expand Up @@ -191,8 +191,7 @@ const useCheckoutMutations = () => {
};

const CheckoutFormElements = ({ checkout }: { checkout: CommerceCheckoutResource }) => {
const { data } = usePaymentSources();
const { data: paymentSources } = data || { data: [] };
const { data: paymentSources } = usePaymentMethods();

const [paymentMethodSource, setPaymentMethodSource] = useState<PaymentMethodSource>(() =>
paymentSources.length > 0 ? 'existing' : 'new',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import { formatDate, truncateWithEndVisible } from '../../utils';
* -----------------------------------------------------------------------------------------------*/

export const PaymentAttemptsList = () => {
const { data: paymentAttempts, isLoading } = usePaymentAttempts();
const { data: paymentAttempts, isLoading, count } = usePaymentAttempts();
const localizationRoot = useSubscriberTypeLocalizationRoot();

return (
<DataTable
page={1}
onPageChange={_ => {}}
itemCount={paymentAttempts?.total_count || 0}
itemCount={count}
pageCount={1}
itemsPerPage={10}
isLoading={isLoading}
Expand All @@ -29,7 +29,7 @@ export const PaymentAttemptsList = () => {
localizationKeys(`${localizationRoot}.billingPage.paymentHistorySection.tableHeader__amount`),
localizationKeys(`${localizationRoot}.billingPage.paymentHistorySection.tableHeader__status`),
]}
rows={(paymentAttempts?.data || []).map(i => (
rows={paymentAttempts.map(i => (
<PaymentAttemptsListRow
key={i.id}
paymentAttempt={i}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useClerk, useOrganization } from '@clerk/shared/react';
import type { CommercePaymentSourceResource } from '@clerk/types';
import type { SetupIntent } from '@stripe/stripe-js';
import { Fragment, useCallback, useMemo, useRef } from 'react';
import { Fragment, useMemo, useRef } from 'react';

import { useCardState, withCardStateProvider } from '@/ui/elements/contexts';
import { FullHeightLoader } from '@/ui/elements/FullHeightLoader';
Expand All @@ -10,7 +10,7 @@ import { ThreeDotsMenu } from '@/ui/elements/ThreeDotsMenu';

import { RemoveResourceForm } from '../../common';
import { DevOnly } from '../../common/DevOnly';
import { usePaymentSources, useSubscriberTypeContext, useSubscriberTypeLocalizationRoot } from '../../contexts';
import { usePaymentMethods, useSubscriberTypeContext, useSubscriberTypeLocalizationRoot } from '../../contexts';
import { localizationKeys } from '../../customizables';
import { Action } from '../../elements/Action';
import { useActionContext } from '../../elements/Action/ActionRoot';
Expand Down Expand Up @@ -114,17 +114,13 @@ export const PaymentSources = withCardStateProvider(() => {
const localizationRoot = useSubscriberTypeLocalizationRoot();
const resource = subscriberType === 'org' ? clerk?.organization : clerk.user;

const { data, isLoading, mutate: mutatePaymentSources } = usePaymentSources();

const { data: paymentSources = [] } = data || {};
const { data: paymentMethods, isLoading, revalidate: revalidatePaymentMethods } = usePaymentMethods();

const sortedPaymentSources = useMemo(
() => paymentSources.sort((a, b) => (a.isDefault && !b.isDefault ? -1 : 1)),
[paymentSources],
() => paymentMethods.sort((a, b) => (a.isDefault && !b.isDefault ? -1 : 1)),
[paymentMethods],
);

const revalidatePaymentSources = useCallback(() => void mutatePaymentSources(), [mutatePaymentSources]);

if (!resource) {
return null;
}
Expand Down Expand Up @@ -156,15 +152,15 @@ export const PaymentSources = withCardStateProvider(() => {
<PaymentSourceRow paymentSource={paymentSource} />
<PaymentSourceMenu
paymentSource={paymentSource}
revalidate={revalidatePaymentSources}
revalidate={revalidatePaymentMethods}
/>
</ProfileSection.Item>

<Action.Open value={`remove-${paymentSource.id}`}>
<Action.Card variant='destructive'>
<RemoveScreen
paymentSource={paymentSource}
revalidate={revalidatePaymentSources}
revalidate={revalidatePaymentMethods}
/>
</Action.Card>
</Action.Open>
Expand All @@ -178,7 +174,7 @@ export const PaymentSources = withCardStateProvider(() => {
</Action.Trigger>
<Action.Open value='add'>
<Action.Card>
<AddScreen onSuccess={revalidatePaymentSources} />
<AddScreen onSuccess={revalidatePaymentMethods} />
</Action.Card>
</Action.Open>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useClerk } from '@clerk/shared/react';
import type { CommercePlanResource, CommerceSubscriptionPlanPeriod, PricingTableProps } from '@clerk/types';
import { useEffect, useMemo, useState } from 'react';

import { usePaymentSources, usePlans, usePlansContext, usePricingTableContext, useSubscriptions } from '../../contexts';
import { usePaymentMethods, usePlans, usePlansContext, usePricingTableContext, useSubscriptions } from '../../contexts';
import { Flow } from '../../customizables';
import { PricingTableDefault } from './PricingTableDefault';
import { PricingTableMatrix } from './PricingTableMatrix';
Expand Down Expand Up @@ -60,7 +60,7 @@ const PricingTableRoot = (props: PricingTableProps) => {
};

// Pre-fetch payment sources
usePaymentSources();
usePaymentMethods();

return (
<Flow.Root
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ import { truncateWithEndVisible } from '../../utils/truncateTextWithEndVisible';
* -----------------------------------------------------------------------------------------------*/

export const StatementsList = () => {
const { data: statements, isLoading } = useStatements();
const { data: statements, isLoading, count } = useStatements();
const localizationRoot = useSubscriberTypeLocalizationRoot();

return (
<DataTable
page={1}
onPageChange={_ => {}}
itemCount={statements?.total_count || 0}
itemCount={count}
pageCount={1}
itemsPerPage={10}
isLoading={isLoading}
Expand All @@ -29,7 +29,7 @@ export const StatementsList = () => {
localizationKeys(`${localizationRoot}.billingPage.statementsSection.tableHeader__date`),
localizationKeys(`${localizationRoot}.billingPage.statementsSection.tableHeader__amount`),
]}
rows={(statements?.data || []).map(i => (
rows={statements.map(i => (
<StatementsListRow
key={i.id}
statement={i}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ export const usePaymentAttemptsContext = () => {
const { data: payments } = usePaymentAttempts();
const getPaymentAttemptById = useCallback(
(paymentAttemptId: string) => {
return payments?.data.find(payment => payment.id === paymentAttemptId);
return payments.find(payment => payment.id === paymentAttemptId);
},
[payments?.data],
[payments],
);

return {
Expand Down
85 changes: 39 additions & 46 deletions packages/clerk-js/src/ui/contexts/components/Plans.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { useClerk, useOrganization, useSession, useUser } from '@clerk/shared/react';
import {
__experimental_usePaymentAttempts,
__experimental_usePaymentMethods,
__experimental_useStatements,
useClerk,
useOrganization,
useSession,
useUser,
} from '@clerk/shared/react';
import type {
Appearance,
CommercePlanResource,
Expand All @@ -8,7 +16,8 @@ import type {
import { useCallback, useMemo } from 'react';
import useSWR from 'swr';

import { CommerceSubscription } from '../../../core/resources/internal';
import { CommerceSubscription } from '@/core/resources/CommerceSubscription';

import type { LocalizationKey } from '../../localization';
import { localizationKeys } from '../../localization';
import { getClosestProfileScrollBox } from '../../utils';
Expand All @@ -30,51 +39,36 @@ export const usePaymentSourcesCacheKey = () => {
};
};

export const usePaymentSources = () => {
const { organization } = useOrganization();
const { user } = useUser();
const subscriberType = useSubscriberTypeContext();
const cacheKey = usePaymentSourcesCacheKey();

return useSWR(cacheKey, () => (subscriberType === 'org' ? organization : user)?.getPaymentSources({}), dedupeOptions);
};

export const usePaymentAttemptsCacheKey = () => {
const { organization } = useOrganization();
const { user } = useUser();
// TODO(@COMMERCE): Rename payment sources to payment methods at the API level
export const usePaymentMethods = () => {
const subscriberType = useSubscriberTypeContext();

return {
key: `commerce-payment-history`,
userId: user?.id,
args: { orgId: subscriberType === 'org' ? organization?.id : undefined },
};
return __experimental_usePaymentMethods({
for: subscriberType === 'org' ? 'organization' : 'user',
initialPage: 1,
pageSize: 10,
keepPreviousData: true,
});
};

export const usePaymentAttempts = () => {
const { billing } = useClerk();
const cacheKey = usePaymentAttemptsCacheKey();

return useSWR(cacheKey, ({ args, userId }) => (userId ? billing.getPaymentAttempts(args) : undefined), dedupeOptions);
};

export const useStatementsCacheKey = () => {
const { organization } = useOrganization();
const { user } = useUser();
const subscriberType = useSubscriberTypeContext();

return {
key: `commerce-statements`,
userId: user?.id,
args: { orgId: subscriberType === 'org' ? organization?.id : undefined },
};
return __experimental_usePaymentAttempts({
for: subscriberType === 'org' ? 'organization' : 'user',
initialPage: 1,
pageSize: 10,
keepPreviousData: true,
});
};

export const useStatements = () => {
const { billing } = useClerk();
const cacheKey = useStatementsCacheKey();

return useSWR(cacheKey, ({ args, userId }) => (userId ? billing.getStatements(args) : undefined), dedupeOptions);
export const useStatements = (params?: { mode: 'cache' }) => {
const subscriberType = useSubscriberTypeContext();
return __experimental_useStatements({
for: subscriberType === 'org' ? 'organization' : 'user',
initialPage: 1,
pageSize: 10,
keepPreviousData: true,
__experimental_mode: params?.mode,
});
};

export const useSubscriptions = () => {
Expand Down Expand Up @@ -181,18 +175,17 @@ export const usePlansContext = () => {
});

// Invalidates cache but does not fetch immediately
const { mutate: mutateStatements } =
useSWR<Awaited<ReturnType<typeof clerk.billing.getStatements>>>(useStatementsCacheKey());
const { revalidate: revalidateStatements } = useStatements({ mode: 'cache' });

const { mutate: mutatePaymentSources } = usePaymentSources();
const { revalidate: revalidatePaymentSources } = usePaymentMethods();

const revalidateAll = useCallback(() => {
// Revalidate the plans and subscriptions
void mutateSubscriptions();
void mutatePlans();
void mutateStatements();
void mutatePaymentSources();
}, [mutateSubscriptions, mutatePlans, mutateStatements, mutatePaymentSources]);
void revalidateStatements();
void revalidatePaymentSources();
}, [mutateSubscriptions, mutatePlans, revalidateStatements, revalidatePaymentSources]);

// should the default plan be shown as active
const isDefaultPlanImplicitlyActiveOrUpcoming = useMemo(() => {
Expand Down
4 changes: 2 additions & 2 deletions packages/clerk-js/src/ui/contexts/components/Statements.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ export const useStatementsContext = () => {
const { data: statements } = useStatements();
const getStatementById = useCallback(
(statementId: string) => {
return statements?.data.find(statement => statement.id === statementId);
return statements.find(statement => statement.id === statementId);
},
[statements?.data],
[statements],
);

return {
Expand Down
Loading
Loading