Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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?: { fetchOnMount: boolean }) => {
const subscriberType = useSubscriberTypeContext();
return __experimental_useStatements({
for: subscriberType === 'org' ? 'organization' : 'user',
initialPage: 1,
pageSize: 10,
keepPreviousData: true,
fetchOnMount: params?.fetchOnMount,
});
};

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({ fetchOnMount: false });

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