fix: prevent billingPortal creation if no active subscription (#9701)

Billing portal is created in settings/billing page even if subscription
is canceled, causing server internal error. -> Skip back end request

Bonus : display settings/billing page with disabled button even if
subscription is canceled

---------

Co-authored-by: etiennejouan <jouan.etienne@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Etienne
2025-01-21 15:01:18 +01:00
committed by GitHub
parent 47c2c774e3
commit d8815d7ebf
12 changed files with 123 additions and 270 deletions

View File

@ -12,7 +12,6 @@ import {
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { SettingsBillingCoverImage } from '@/billing/components/SettingsBillingCoverImage';
import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
@ -21,8 +20,8 @@ import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModa
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
import {
OnboardingStatus,
SubscriptionInterval,
SubscriptionStatus,
useBillingPortalSessionQuery,
useUpdateBillingSubscriptionMutation,
} from '~/generated/graphql';
@ -59,9 +58,16 @@ export const SettingsBilling = () => {
};
const { enqueueSnackBar } = useSnackBar();
const onboardingStatus = useOnboardingStatus();
const subscriptionStatus = useSubscriptionStatus();
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const subscriptions = currentWorkspace?.billingSubscriptions;
const hasSubscriptions = (subscriptions?.length ?? 0) > 0;
const subscriptionStatus = useSubscriptionStatus();
const hasNotCanceledCurrentSubscription =
isDefined(subscriptionStatus) &&
subscriptionStatus !== SubscriptionStatus.Canceled;
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
const switchingInfo =
currentWorkspace?.currentBillingSubscription?.interval ===
@ -75,18 +81,12 @@ export const SettingsBilling = () => {
variables: {
returnUrlPath: '/settings/billing',
},
skip: !hasSubscriptions,
});
const billingPortalButtonDisabled =
loading || !isDefined(data) || !isDefined(data.billingPortalSession.url);
const switchIntervalButtonDisabled =
onboardingStatus !== OnboardingStatus.COMPLETED;
const cancelPlanButtonDisabled =
billingPortalButtonDisabled ||
onboardingStatus !== OnboardingStatus.COMPLETED;
const openBillingPortal = () => {
if (isDefined(data) && isDefined(data.billingPortalSession.url)) {
window.location.replace(data.billingPortalSession.url);
@ -137,50 +137,46 @@ export const SettingsBilling = () => {
>
<SettingsPageContainer>
<SettingsBillingCoverImage />
{isDefined(subscriptionStatus) && (
<>
<Section>
<H2Title
title={t`Manage your subscription`}
description={t`Edit payment method, see your invoices and more`}
/>
<Button
Icon={IconCreditCard}
title={t`View billing details`}
variant="secondary"
onClick={openBillingPortal}
disabled={billingPortalButtonDisabled}
/>
</Section>
<Section>
<H2Title
title={t`Edit billing interval`}
description={t`Switch ${from}`}
/>
<Button
Icon={IconCalendarEvent}
title={t`Switch ${to}`}
variant="secondary"
onClick={openSwitchingIntervalModal}
disabled={switchIntervalButtonDisabled}
/>
</Section>
<Section>
<H2Title
title={t`Cancel your subscription`}
description={t`Your workspace will be disabled`}
/>
<Button
Icon={IconCircleX}
title={t`Cancel Plan`}
variant="secondary"
accent="danger"
onClick={openBillingPortal}
disabled={cancelPlanButtonDisabled}
/>
</Section>
</>
)}
<Section>
<H2Title
title={t`Manage your subscription`}
description={t`Edit payment method, see your invoices and more`}
/>
<Button
Icon={IconCreditCard}
title={t`View billing details`}
variant="secondary"
onClick={openBillingPortal}
disabled={billingPortalButtonDisabled}
/>
</Section>
<Section>
<H2Title
title={t`Edit billing interval`}
description={t`Switch ${from}`}
/>
<Button
Icon={IconCalendarEvent}
title={t`Switch ${to}`}
variant="secondary"
onClick={openSwitchingIntervalModal}
disabled={!hasNotCanceledCurrentSubscription}
/>
</Section>
<Section>
<H2Title
title={t`Cancel your subscription`}
description={t`Your workspace will be disabled`}
/>
<Button
Icon={IconCircleX}
title={t`Cancel Plan`}
variant="secondary"
accent="danger"
onClick={openBillingPortal}
disabled={!hasNotCanceledCurrentSubscription}
/>
</Section>
</SettingsPageContainer>
<ConfirmationModal
isOpen={isSwitchingIntervalModalOpen}