Files
twenty/packages/twenty-front/src/pages/settings/SettingsBilling.tsx
gitstart-twenty bb7d94a455 Create ESLint rule to discourage usage of navigate() and prefer Link (#5642)
### Description
Create ESLint rule to discourage usage of navigate() and prefer Link


### Refs
#5468 

### Demo

![Capture-2024-05-29-112852](https://github.com/twentyhq/twenty/assets/140154534/28378c09-86bb-49d3-9e9a-49aa1c07ad11)

![Capture-2024-05-29-112843](https://github.com/twentyhq/twenty/assets/140154534/2c05ea92-e19b-49ae-acb9-07f6ec9182ab)

Fixes #5468

---------

Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com>
Co-authored-by: v1b3m <vibenjamin6@gmail.com>
Co-authored-by: Matheus <matheus_benini@hotmail.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2024-06-04 17:04:57 +02:00

237 lines
7.7 KiB
TypeScript

import React, { useState } from 'react';
import styled from '@emotion/styled';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import {
H1Title,
H2Title,
IconCalendarEvent,
IconCircleX,
IconCreditCard,
IconCurrencyDollar,
} from 'twenty-ui';
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
import { SettingsBillingCoverImage } from '@/billing/components/SettingsBillingCoverImage';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SupportChat } from '@/support/components/SupportChat';
import { AppPath } from '@/types/AppPath';
import { Info } from '@/ui/display/info/components/Info';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { Button } from '@/ui/input/button/components/Button';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section';
import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink';
import {
useBillingPortalSessionQuery,
useUpdateBillingSubscriptionMutation,
} from '~/generated/graphql';
import { isDefined } from '~/utils/isDefined';
const StyledH1Title = styled(H1Title)`
margin-bottom: 0;
`;
const StyledInvisibleChat = styled.div`
display: none;
`;
type SwitchInfo = {
newInterval: string;
to: string;
from: string;
impact: string;
};
const MONTHLY_SWITCH_INFO: SwitchInfo = {
newInterval: 'year',
to: 'to yearly',
from: 'from monthly to yearly',
impact: 'You will be charged immediately for the full year.',
};
const YEARLY_SWITCH_INFO: SwitchInfo = {
newInterval: 'month',
to: 'to monthly',
from: 'from yearly to monthly',
impact: 'Your credit balance will be used to pay the monthly bills.',
};
const SWITCH_INFOS = {
year: YEARLY_SWITCH_INFO,
month: MONTHLY_SWITCH_INFO,
};
export const SettingsBilling = () => {
const { enqueueSnackBar } = useSnackBar();
const onboardingStatus = useOnboardingStatus();
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
const switchingInfo =
currentWorkspace?.currentBillingSubscription?.interval === 'year'
? SWITCH_INFOS.year
: SWITCH_INFOS.month;
const [isSwitchingIntervalModalOpen, setIsSwitchingIntervalModalOpen] =
useState(false);
const [updateBillingSubscription] = useUpdateBillingSubscriptionMutation();
const { data, loading } = useBillingPortalSessionQuery({
variables: {
returnUrlPath: '/settings/billing',
},
});
const billingPortalButtonDisabled =
loading || !isDefined(data) || !isDefined(data.billingPortalSession.url);
const switchIntervalButtonDisabled =
onboardingStatus !== OnboardingStatus.Completed;
const cancelPlanButtonDisabled =
billingPortalButtonDisabled ||
onboardingStatus !== OnboardingStatus.Completed;
const displayPaymentFailInfo =
onboardingStatus === OnboardingStatus.PastDue ||
onboardingStatus === OnboardingStatus.Unpaid;
const displaySubscriptionCanceledInfo =
onboardingStatus === OnboardingStatus.Canceled;
const displaySubscribeInfo =
onboardingStatus === OnboardingStatus.CompletedWithoutSubscription;
const openBillingPortal = () => {
if (isDefined(data) && isDefined(data.billingPortalSession.url)) {
window.location.replace(data.billingPortalSession.url);
}
};
const openSwitchingIntervalModal = () => {
setIsSwitchingIntervalModalOpen(true);
};
const switchInterval = async () => {
try {
await updateBillingSubscription();
if (isDefined(currentWorkspace?.currentBillingSubscription)) {
const newCurrentWorkspace = {
...currentWorkspace,
currentBillingSubscription: {
...currentWorkspace?.currentBillingSubscription,
interval: switchingInfo.newInterval,
},
};
setCurrentWorkspace(newCurrentWorkspace);
}
enqueueSnackBar(`Subscription has been switched ${switchingInfo.to}`, {
variant: SnackBarVariant.Success,
});
} catch (error: any) {
enqueueSnackBar(
`Error while switching subscription ${switchingInfo.to}.`,
{
variant: SnackBarVariant.Error,
},
);
}
};
return (
<SubMenuTopBarContainer Icon={IconCurrencyDollar} title="Billing">
<SettingsPageContainer>
<StyledH1Title title="Billing" />
<SettingsBillingCoverImage />
{displayPaymentFailInfo && (
<Info
text={'Last payment failed. Please update your billing details.'}
buttonTitle={'Update'}
accent={'danger'}
onClick={openBillingPortal}
/>
)}
{displaySubscriptionCanceledInfo && (
<UndecoratedLink to={AppPath.PlanRequired}>
<Info
text={'Subscription canceled. Please start a new one'}
buttonTitle={'Subscribe'}
accent={'danger'}
/>
</UndecoratedLink>
)}
{displaySubscribeInfo ? (
<UndecoratedLink to={AppPath.PlanRequired}>
<Info
text={'Your workspace does not have an active subscription'}
buttonTitle={'Subscribe'}
accent={'danger'}
/>
</UndecoratedLink>
) : (
<>
<Section>
<H2Title
title="Manage your subscription"
description="Edit payment method, see your invoices and more"
/>
<Button
Icon={IconCreditCard}
title="View billing details"
variant="secondary"
onClick={openBillingPortal}
disabled={billingPortalButtonDisabled}
/>
</Section>
<Section>
<H2Title
title="Edit billing interval"
description={`Switch ${switchingInfo.from}`}
/>
<Button
Icon={IconCalendarEvent}
title={`Switch ${switchingInfo.to}`}
variant="secondary"
onClick={openSwitchingIntervalModal}
disabled={switchIntervalButtonDisabled}
/>
</Section>
<Section>
<H2Title
title="Cancel your subscription"
description="Your workspace will be disabled"
/>
<Button
Icon={IconCircleX}
title="Cancel Plan"
variant="secondary"
accent="danger"
onClick={openBillingPortal}
disabled={cancelPlanButtonDisabled}
/>
</Section>
</>
)}
</SettingsPageContainer>
<StyledInvisibleChat>
<SupportChat />
</StyledInvisibleChat>
<ConfirmationModal
isOpen={isSwitchingIntervalModalOpen}
setIsOpen={setIsSwitchingIntervalModalOpen}
title={`Switch billing ${switchingInfo.to}`}
subtitle={
<>
{`Are you sure that you want to change your billing interval?
${switchingInfo.impact}`}
</>
}
onConfirmClick={switchInterval}
deleteButtonText={`Change ${switchingInfo.to}`}
confirmButtonAccent={'blue'}
/>
</SubMenuTopBarContainer>
);
};