Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions packages/clerk-js/src/ui/components/APIKeys/APIKeys.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export const APIKeysPage = ({ subject, perPage, revokeModalRoot }: APIKeysPagePr
};
const card = useCardState();
const clerk = useClerk();
// TODO: Replace useSWRMutation with the react-query equivalent.
const {
data: createdAPIKey,
trigger: createAPIKey,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useClerk, useOrganizationContext } from '@clerk/shared/react';
import { __internal_usePaymentAttemptQuery } from '@clerk/shared/react/index';
import type { BillingSubscriptionItemResource } from '@clerk/shared/types';
import useSWR from 'swr';

import { Alert } from '@/ui/elements/Alert';
import { Header } from '@/ui/elements/Header';
Expand Down Expand Up @@ -31,28 +30,16 @@ export const PaymentAttemptPage = () => {
const subscriberType = useSubscriberTypeContext();
const localizationRoot = useSubscriberTypeLocalizationRoot();
const { t, translateError } = useLocalizations();
const clerk = useClerk();
// Do not use `useOrganization` to avoid triggering the in-app enable organizations prompt in development instance
const organizationCtx = useOrganizationContext();
const requesterType = subscriberType === 'organization' ? 'organization' : 'user';

const {
data: paymentAttempt,
isLoading,
error,
} = useSWR(
params.paymentAttemptId
? {
type: 'payment-attempt',
id: params.paymentAttemptId,
orgId: subscriberType === 'organization' ? organizationCtx?.organization?.id : undefined,
}
: null,
() =>
clerk.billing.getPaymentAttempt({
id: params.paymentAttemptId,
orgId: subscriberType === 'organization' ? organizationCtx?.organization?.id : undefined,
}),
);
} = __internal_usePaymentAttemptQuery({
paymentAttemptId: params.paymentAttemptId ?? null,
for: requesterType,
});

const subscriptionItem = paymentAttempt?.subscriptionItem;

Expand Down
24 changes: 9 additions & 15 deletions packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useClerk } from '@clerk/shared/react';
import { __internal_usePlanDetailsQuery } from '@clerk/shared/react/index';
import type {
__internal_PlanDetailsProps,
BillingPlanResource,
Expand All @@ -7,7 +7,6 @@ import type {
} from '@clerk/shared/types';
import * as React from 'react';
import { useMemo, useState } from 'react';
import useSWR from 'swr';

import { Alert } from '@/ui/elements/Alert';
import { Avatar } from '@/ui/elements/Avatar';
Expand All @@ -29,6 +28,8 @@ import {
useLocalizations,
} from '../../customizables';

type PlanFeature = BillingPlanResource['features'][number];

export const PlanDetails = (props: __internal_PlanDetailsProps) => {
return (
<Flow.Root flow='planDetails'>
Expand Down Expand Up @@ -79,24 +80,17 @@ const PlanDetailsInternal = ({
plan: initialPlan,
initialPlanPeriod = 'month',
}: __internal_PlanDetailsProps) => {
const clerk = useClerk();
const [planPeriod, setPlanPeriod] = useState<BillingSubscriptionPlanPeriod>(initialPlanPeriod);

const {
data: plan,
isLoading,
error,
} = useSWR<BillingPlanResource, ClerkAPIResponseError>(
planId || initialPlan ? { type: 'plan', id: planId || initialPlan?.id } : null,
// @ts-expect-error we are handling it above
() => clerk.billing.getPlan({ id: planId || initialPlan?.id }),
{
fallbackData: initialPlan,
revalidateOnFocus: false,
shouldRetryOnError: false,
keepPreviousData: true,
},
);
} = __internal_usePlanDetailsQuery({
planId,
initialPlan,
enabled: Boolean(planId || initialPlan?.id),
});

if (isLoading && !initialPlan) {
return (
Expand Down Expand Up @@ -161,7 +155,7 @@ const PlanDetailsInternal = ({
margin: 0,
})}
>
{features.map(feature => (
{features.map((feature: PlanFeature) => (
<Box
key={feature.id}
elementDescriptor={descriptors.planDetailFeaturesListItem}
Expand Down
33 changes: 12 additions & 21 deletions packages/clerk-js/src/ui/components/Statements/StatementPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useClerk, useOrganizationContext } from '@clerk/shared/react';
import useSWR from 'swr';
import { __internal_useStatementQuery } from '@clerk/shared/react/index';
import type { BillingStatementResource } from '@clerk/shared/types';

import { Alert } from '@/ui/elements/Alert';
import { Header } from '@/ui/elements/Header';
Expand All @@ -20,33 +20,24 @@ import { ArrowRightIcon, Plus, RotateLeftRight } from '../../icons';
import { useRouter } from '../../router';
import { Statement } from './Statement';

type StatementGroup = BillingStatementResource['groups'][number];
type StatementItem = StatementGroup['items'][number];

export const StatementPage = () => {
const { params, navigate } = useRouter();
const subscriberType = useSubscriberTypeContext();
const localizationRoot = useSubscriberTypeLocalizationRoot();
const { t, translateError } = useLocalizations();
const clerk = useClerk();
// Do not use `useOrganization` to avoid triggering the in-app enable organizations prompt in development instance
const organizationCtx = useOrganizationContext();
const requesterType = subscriberType === 'organization' ? 'organization' : 'user';

const {
data: statement,
isLoading,
error,
} = useSWR(
params.statementId
? {
type: 'statement',
id: params.statementId,
orgId: subscriberType === 'organization' ? organizationCtx?.organization?.id : undefined,
}
: null,
() =>
clerk.billing.getStatement({
id: params.statementId,
orgId: subscriberType === 'organization' ? organizationCtx?.organization?.id : undefined,
}),
);
} = __internal_useStatementQuery({
statementId: params.statementId ?? null,
for: requesterType,
});

if (isLoading) {
return (
Expand Down Expand Up @@ -99,11 +90,11 @@ export const StatementPage = () => {
status={statement.status}
/>
<Statement.Body>
{statement.groups.map(group => (
{statement.groups.map((group: StatementGroup) => (
<Statement.Section key={group.timestamp.toISOString()}>
<Statement.SectionHeader text={formatDate(group.timestamp, 'long')} />
<Statement.SectionContent>
{group.items.map(item => (
{group.items.map((item: StatementItem) => (
<Statement.SectionContentItem key={item.id}>
<Statement.SectionContentDetailsHeader
title={item.subscriptionItem.plan.name}
Expand Down
Empty file.
Empty file.
Empty file.
1 change: 1 addition & 0 deletions packages/shared/src/react/clerk-swr.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';

// TODO: Replace these SWR re-exports with react-query equivalents.
export * from 'swr';

export { default as useSWR } from 'swr';
Expand Down
3 changes: 3 additions & 0 deletions packages/shared/src/react/commerce.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const [StripeLibsContext, useStripeLibsContext] = createContextAndHook<{

const StripeLibsProvider = ({ children }: PropsWithChildren) => {
const clerk = useClerk();
// TODO: Replace useSWR with the react-query equivalent.
const { data: stripeClerkLibs } = useSWR(
'clerk-stripe-sdk',
async () => {
Expand Down Expand Up @@ -85,6 +86,7 @@ const usePaymentSourceUtils = (forResource: ForPayerType = 'user') => {
const resource = forResource === 'organization' ? organization : user;
const stripeClerkLibs = useStripeLibsContext();

// TODO: Replace useSWRMutation with the react-query equivalent.
const { data: initializedPaymentMethod, trigger: initializePaymentMethod } = useSWRMutation(
{
key: 'billing-payment-method-initialize',
Expand Down Expand Up @@ -113,6 +115,7 @@ const usePaymentSourceUtils = (forResource: ForPayerType = 'user') => {
const paymentMethodOrder = initializedPaymentMethod?.paymentMethodOrder;
const stripePublishableKey = environment?.commerceSettings.billing.stripePublishableKey;

// TODO: Replace useSWR with the react-query equivalent.
const { data: stripe } = useSWR(
stripeClerkLibs && externalGatewayId && stripePublishableKey
? { key: 'stripe-sdk', externalGatewayId, stripePublishableKey }
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/react/hooks/__tests__/wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import { SWRConfig } from 'swr';

export const wrapper = ({ children }: { children: React.ReactNode }) => (
// TODO: Replace SWRConfig with the react-query equivalent.
<SWRConfig
value={{
provider: () => new Map(),
Expand Down
14 changes: 12 additions & 2 deletions packages/shared/src/react/hooks/createCacheKeys.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ResourceCacheStableKey } from '../stable-keys';
import type { __internal_ResourceCacheStableKey, ResourceCacheStableKey } from '../stable-keys';
import type { QueryKeyWithArgs } from './usePageOrInfinite.types';

/**
* @internal
Expand All @@ -8,7 +9,7 @@ export function createCacheKeys<
T extends Record<string, unknown> = Record<string, unknown>,
U extends Record<string, unknown> | undefined = undefined,
>(params: {
stablePrefix: ResourceCacheStableKey;
stablePrefix: ResourceCacheStableKey | __internal_ResourceCacheStableKey;
authenticated: boolean;
tracked: T;
untracked: U extends { args: Params } ? U : never;
Expand All @@ -20,3 +21,12 @@ export function createCacheKeys<
authenticated: params.authenticated,
};
}

export function toSWRQuery<T extends { queryKey: QueryKeyWithArgs<unknown> }>(keys: T) {
const { queryKey } = keys;
return {
type: queryKey[0],
...queryKey[2],
...(queryKey[3] as { args: Record<string, unknown> }).args,
};
}
3 changes: 3 additions & 0 deletions packages/shared/src/react/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ export { usePaymentMethods as __experimental_usePaymentMethods } from './usePaym
export { usePlans as __experimental_usePlans } from './usePlans';
export { useSubscription as __experimental_useSubscription } from './useSubscription';
export { useCheckout as __experimental_useCheckout } from './useCheckout';
export { __internal_useStatementQuery } from './useStatementQuery';
export { __internal_usePlanDetailsQuery } from './usePlanDetailsQuery';
export { __internal_usePaymentAttemptQuery } from './usePaymentAttemptQuery';
4 changes: 3 additions & 1 deletion packages/shared/src/react/hooks/usePageOrInfinite.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type QueryArgs<Params> = Readonly<{
args: Params;
}>;

type QueryKeyWithArgs<Params> = readonly [
export type QueryKeyWithArgs<Params> = readonly [
string,
boolean,
Record<string, unknown>,
Expand All @@ -27,6 +27,8 @@ export type UsePagesOrInfiniteSignature = <
queryKey: QueryKeyWithArgs<Params>;
invalidationKey: InvalidationQueryKey;
stableKey: string;
authenticated: boolean;
// toSWRQuery: () => Record<string, unknown>;
},
TConfig extends Config = Config,
>(params: {
Expand Down
9 changes: 3 additions & 6 deletions packages/shared/src/react/hooks/usePagesOrInfinite.swr.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useCallback, useMemo, useRef, useState } from 'react';

import { useSWR, useSWRInfinite } from '../clerk-swr';
import type { CacheSetter, ValueOrSetter } from '../types';
import { toSWRQuery } from './createCacheKeys';
import type { UsePagesOrInfiniteSignature } from './usePageOrInfinite.types';
import { getDifferentKeys, useWithSafeValues } from './usePagesOrInfinite.shared';
import { usePreviousValue } from './usePreviousValue';
Expand Down Expand Up @@ -49,9 +50,7 @@ export const usePagesOrInfinite: UsePagesOrInfiniteSignature = params => {
const isSignedIn = config.isSignedIn;

const pagesCacheKey = {
type: keys.queryKey[0],
...keys.queryKey[2],
...keys.queryKey[3].args,
...toSWRQuery(keys),
initialPage: paginatedPage,
pageSize: pageSizeRef.current,
};
Expand Down Expand Up @@ -136,9 +135,7 @@ export const usePagesOrInfinite: UsePagesOrInfiniteSignature = params => {
}

return {
type: keys.queryKey[0],
...keys.queryKey[2],
...keys.queryKey[3].args,
...toSWRQuery(keys),
initialPage: initialPageRef.current + pageIndex,
pageSize: pageSizeRef.current,
};
Expand Down
68 changes: 68 additions & 0 deletions packages/shared/src/react/hooks/usePaymentAttemptQuery.rq.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { eventMethodCalled } from '../../telemetry/events';
import { useClerkQuery } from '../clerk-rq/useQuery';
import {
useAssertWrappedByClerkProvider,
useClerkInstanceContext,
useOrganizationContext,
useUserContext,
} from '../contexts';
import { usePaymentAttemptQueryCacheKeys } from './usePaymentAttemptQuery.shared';
import type { PaymentAttemptQueryResult, UsePaymentAttemptQueryParams } from './usePaymentAttemptQuery.types';

const HOOK_NAME = 'usePaymentAttemptQuery';

/**
* @internal
*/
function KeepPreviousDataFn<Data>(previousData: Data): Data {
return previousData;
}

/**
* This is the new implementation of usePaymentAttemptQuery using React Query.
* It is exported only if the package is built with the `CLERK_USE_RQ` environment variable set to `true`.
*
* @internal
*/
export function __internal_usePaymentAttemptQuery(params: UsePaymentAttemptQueryParams): PaymentAttemptQueryResult {
useAssertWrappedByClerkProvider(HOOK_NAME);

const { paymentAttemptId, enabled = true, keepPreviousData = false, for: forType = 'user' } = params;
const clerk = useClerkInstanceContext();
const user = useUserContext();
const { organization } = useOrganizationContext();

clerk.telemetry?.record(eventMethodCalled(HOOK_NAME));

const organizationId = forType === 'organization' ? (organization?.id ?? null) : null;
const userId = user?.id ?? null;

const { queryKey } = usePaymentAttemptQueryCacheKeys({
paymentAttemptId,
userId,
orgId: organizationId,
for: forType,
});

const queryEnabled = Boolean(paymentAttemptId) && enabled && (forType !== 'organization' || Boolean(organizationId));

const query = useClerkQuery({
queryKey,
queryFn: ({ queryKey }) => {
const args = queryKey[3].args;
return clerk.billing.getPaymentAttempt(args);
},
enabled: queryEnabled,
placeholderData: keepPreviousData ? KeepPreviousDataFn : undefined,
staleTime: 1_000 * 60,
});

return {
data: query.data,
// Our existing types for SWR return undefined when there is no error, but React Query returns null.
// So we need to convert the error to undefined, for backwards compatibility.
error: query.error ?? undefined,
isLoading: query.isLoading,
isFetching: query.isFetching,
};
}
Loading
Loading