martmull
2025-06-05 20:56:55 +02:00
committed by GitHub
parent c75f10bc33
commit b2c57c5dcc
29 changed files with 650 additions and 237 deletions

View File

@ -3,7 +3,7 @@ import { Meta, StoryObj } from '@storybook/react';
import { within } from '@storybook/testing-library';
import { HttpResponse, graphql } from 'msw';
import { BILLING_BASE_PRODUCT_PRICES } from '@/billing/graphql/billingBaseProductPrices';
import { BILLING_BASE_PRODUCT_PRICES } from '@/billing/graphql/queries/billingBaseProductPrices';
import { AppPath } from '@/types/AppPath';
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
import {

View File

@ -1,56 +1,41 @@
import { Trans, useLingui } from '@lingui/react/macro';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { useRecoilValue } from 'recoil';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { SettingsBillingMonthlyCreditsSection } from '@/billing/components/SettingsBillingMonthlyCreditsSection';
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
import { isDefined } from 'twenty-shared/utils';
import {
H2Title,
IconCalendarEvent,
IconCircleX,
IconCreditCard,
} from 'twenty-ui/display';
import { H2Title, IconCircleX, IconCreditCard } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import {
SubscriptionInterval,
SubscriptionStatus,
useBillingPortalSessionQuery,
useSwitchSubscriptionToYearlyIntervalMutation,
} from '~/generated/graphql';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
const SWITCH_BILLING_INTERVAL_MODAL_ID = 'switch-billing-interval-modal';
import { SettingsBillingSubscriptionInfo } from '@/billing/components/SettingsBillingSubscriptionInfo';
export const SettingsBilling = () => {
const { t } = useLingui();
const { redirect } = useRedirect();
const { enqueueSnackBar } = useSnackBar();
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 [switchToYearlyInterval] =
useSwitchSubscriptionToYearlyIntervalMutation();
const { data, loading } = useBillingPortalSessionQuery({
variables: {
returnUrlPath: '/settings/billing',
@ -67,31 +52,6 @@ export const SettingsBilling = () => {
}
};
const { openModal } = useModal();
const switchInterval = async () => {
try {
await switchToYearlyInterval();
if (isDefined(currentWorkspace?.currentBillingSubscription)) {
const newCurrentWorkspace = {
...currentWorkspace,
currentBillingSubscription: {
...currentWorkspace?.currentBillingSubscription,
interval: SubscriptionInterval.Year,
},
};
setCurrentWorkspace(newCurrentWorkspace);
}
enqueueSnackBar(t`Subscription has been switched to yearly.`, {
variant: SnackBarVariant.Success,
});
} catch (error: any) {
enqueueSnackBar(t`Error while switching subscription to yearly.`, {
variant: SnackBarVariant.Error,
});
}
};
return (
<SubMenuTopBarContainer
title={t`Billing`}
@ -104,12 +64,15 @@ export const SettingsBilling = () => {
]}
>
<SettingsPageContainer>
{hasNotCanceledCurrentSubscription && (
<SettingsBillingSubscriptionInfo />
)}
{hasNotCanceledCurrentSubscription && (
<SettingsBillingMonthlyCreditsSection />
)}
<Section>
<H2Title
title={t`Manage your subscription`}
title={t`Manage billing information`}
description={t`Edit payment method, see your invoices and more`}
/>
<Button
@ -120,45 +83,23 @@ export const SettingsBilling = () => {
disabled={billingPortalButtonDisabled}
/>
</Section>
{currentWorkspace?.currentBillingSubscription?.interval ===
SubscriptionInterval.Month && (
{hasNotCanceledCurrentSubscription && (
<Section>
<H2Title
title={t`Edit billing interval`}
description={t`Switch from monthly to yearly`}
title={t`Cancel your subscription`}
description={t`Your workspace will be disabled`}
/>
<Button
Icon={IconCalendarEvent}
title={t`Switch to yearly`}
Icon={IconCircleX}
title={t`Cancel Plan`}
variant="secondary"
onClick={() => openModal(SWITCH_BILLING_INTERVAL_MODAL_ID)}
disabled={!hasNotCanceledCurrentSubscription}
accent="danger"
onClick={openBillingPortal}
disabled={billingPortalButtonDisabled}
/>
</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
modalId={SWITCH_BILLING_INTERVAL_MODAL_ID}
title={t`Switch billing to yearly`}
subtitle={t`Are you sure that you want to change your billing interval? You will be charged immediately for the full year.`}
onConfirmClick={switchInterval}
confirmButtonText={t`Change to yearly`}
confirmButtonAccent={'blue'}
/>
</SubMenuTopBarContainer>
);
};