5095 move onboardingstatus computation from frontend to backend (#5954)

- move front `onboardingStatus` computing to server side
- add logic to `useSetNextOnboardingStatus`
- update some missing redirections in
`usePageChangeEffectNavigateLocation`
- separate subscriptionStatus from onboardingStatus
This commit is contained in:
martmull
2024-06-28 17:32:02 +02:00
committed by GitHub
parent 1a66db5bff
commit b8f33f6f59
78 changed files with 1767 additions and 1763 deletions

View File

@ -4,14 +4,21 @@ import { within } from '@storybook/test';
import { graphql, HttpResponse } from 'msw';
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
import { ValidatePasswordResetTokenDocument } from '~/generated/graphql';
import {
OnboardingStatus,
ValidatePasswordResetTokenDocument,
} from '~/generated/graphql';
import { PasswordReset } from '~/pages/auth/PasswordReset';
import {
PageDecorator,
PageDecoratorArgs,
} from '~/testing/decorators/PageDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedOnboardingUsersData } from '~/testing/mock-data/users';
import { mockedOnboardingUserData } from '~/testing/mock-data/users';
const mockedOnboardingUsersData = mockedOnboardingUserData(
OnboardingStatus.Completed,
);
const meta: Meta<PageDecoratorArgs> = {
title: 'Pages/Auth/PasswordReset',
@ -30,8 +37,8 @@ const meta: Meta<PageDecoratorArgs> = {
return HttpResponse.json({
data: {
validatePasswordResetToken: {
id: mockedOnboardingUsersData[0].id,
email: mockedOnboardingUsersData[0].email,
id: mockedOnboardingUsersData.id,
email: mockedOnboardingUsersData.email,
},
},
});
@ -40,7 +47,7 @@ const meta: Meta<PageDecoratorArgs> = {
graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
return HttpResponse.json({
data: {
currentUser: mockedOnboardingUsersData[0],
currentUser: mockedOnboardingUsersData,
},
});
}),

View File

@ -19,6 +19,7 @@ import { ActionLink } from '@/ui/navigation/link/components/ActionLink';
import { CAL_LINK } from '@/ui/navigation/link/constants/Cal';
import {
ProductPriceEntity,
SubscriptionInterval,
useCheckoutSessionMutation,
useGetProductPricesQuery,
} from '~/generated/graphql';
@ -75,7 +76,7 @@ const benefits = [
export const ChooseYourPlan = () => {
const billing = useRecoilValue(billingState);
const [planSelected, setPlanSelected] = useState('month');
const [planSelected, setPlanSelected] = useState(SubscriptionInterval.Month);
const [isSubmitting, setIsSubmitting] = useState(false);
@ -87,7 +88,7 @@ export const ChooseYourPlan = () => {
const [checkoutSession] = useCheckoutSessionMutation();
const handlePlanChange = (type?: string) => {
const handlePlanChange = (type?: SubscriptionInterval) => {
return () => {
if (isNonEmptyString(type) && planSelected !== type) {
setPlanSelected(type);
@ -101,11 +102,11 @@ export const ChooseYourPlan = () => {
price: ProductPriceEntity,
prices: ProductPriceEntity[],
): string => {
if (price.recurringInterval !== 'year') {
if (price.recurringInterval !== SubscriptionInterval.Year) {
return 'Cancel anytime';
}
const monthPrice = prices.filter(
(price) => price.recurringInterval === 'month',
(price) => price.recurringInterval === SubscriptionInterval.Month,
)?.[0];
if (
isDefined(monthPrice) &&

View File

@ -9,11 +9,11 @@ import { z } from 'zod';
import { SubTitle } from '@/auth/components/SubTitle';
import { Title } from '@/auth/components/Title';
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus';
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
import { ProfilePictureUploader } from '@/settings/profile/components/ProfilePictureUploader';
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
@ -22,6 +22,8 @@ import { MainButton } from '@/ui/input/button/components/MainButton';
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
import { OnboardingStatus } from '~/generated/graphql';
import { isDefined } from '~/utils/isDefined';
const StyledContentContainer = styled.div`
width: 100%;
@ -55,11 +57,11 @@ type Form = z.infer<typeof validationSchema>;
export const CreateProfile = () => {
const onboardingStatus = useOnboardingStatus();
const setNextOnboardingStatus = useSetNextOnboardingStatus();
const { enqueueSnackBar } = useSnackBar();
const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilState(
currentWorkspaceMemberState,
);
const { updateOneRecord } = useUpdateOneRecord<WorkspaceMember>({
objectNameSingular: CoreObjectNameSingular.WorkspaceMember,
});
@ -100,17 +102,20 @@ export const CreateProfile = () => {
},
});
setCurrentWorkspaceMember(
(current) =>
({
setCurrentWorkspaceMember((current) => {
if (isDefined(current)) {
return {
...current,
name: {
firstName: data.firstName,
lastName: data.lastName,
},
colorScheme: 'System',
}) as any,
);
};
}
return current;
});
setNextOnboardingStatus();
} catch (error: any) {
enqueueSnackBar(error?.message, {
variant: SnackBarVariant.Error,
@ -119,6 +124,7 @@ export const CreateProfile = () => {
},
[
currentWorkspaceMember?.id,
setNextOnboardingStatus,
enqueueSnackBar,
setCurrentWorkspaceMember,
updateOneRecord,
@ -137,7 +143,7 @@ export const CreateProfile = () => {
PageHotkeyScope.CreateProfile,
);
if (onboardingStatus !== OnboardingStatus.OngoingProfileCreation) {
if (onboardingStatus !== OnboardingStatus.ProfileCreation) {
return null;
}

View File

@ -9,18 +9,20 @@ import { z } from 'zod';
import { SubTitle } from '@/auth/components/SubTitle';
import { Title } from '@/auth/components/Title';
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadingState';
import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
import { useApolloMetadataClient } from '@/object-metadata/hooks/useApolloMetadataClient';
import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus';
import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader';
import { Loader } from '@/ui/feedback/loader/components/Loader';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { MainButton } from '@/ui/input/button/components/MainButton';
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
import { useActivateWorkspaceMutation } from '~/generated/graphql';
import {
OnboardingStatus,
useActivateWorkspaceMutation,
} from '~/generated/graphql';
import { isDefined } from '~/utils/isDefined';
const StyledContentContainer = styled.div`
@ -105,7 +107,7 @@ export const CreateWorkspace = () => {
}
};
if (onboardingStatus !== OnboardingStatus.OngoingWorkspaceActivation) {
if (onboardingStatus !== OnboardingStatus.WorkspaceActivation) {
return null;
}

View File

@ -17,7 +17,7 @@ import { SubTitle } from '@/auth/components/SubTitle';
import { Title } from '@/auth/components/Title';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useSetNextOnboardingStep } from '@/onboarding/hooks/useSetNextOnboardingStep';
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
import { SeparatorLineText } from '@/ui/display/text/components/SeparatorLineText';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
@ -27,7 +27,10 @@ import { MainButton } from '@/ui/input/button/components/MainButton';
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
import { AnimatedTranslation } from '@/ui/utilities/animation/components/AnimatedTranslation';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { OnboardingStep, useSendInviteLinkMutation } from '~/generated/graphql';
import {
OnboardingStatus,
useSendInviteLinkMutation,
} from '~/generated/graphql';
import { isDefined } from '~/utils/isDefined';
const StyledAnimatedContainer = styled.div`
@ -63,7 +66,7 @@ export const InviteTeam = () => {
const theme = useTheme();
const { enqueueSnackBar } = useSnackBar();
const [sendInviteLink] = useSendInviteLinkMutation();
const setNextOnboardingStep = useSetNextOnboardingStep();
const setNextOnboardingStatus = useSetNextOnboardingStatus();
const currentUser = useRecoilValue(currentUserState);
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const {
@ -133,7 +136,7 @@ export const InviteTeam = () => {
);
const result = await sendInviteLink({ variables: { emails } });
setNextOnboardingStep(OnboardingStep.InviteTeam);
setNextOnboardingStatus();
if (isDefined(result.errors)) {
throw result.errors;
@ -145,7 +148,7 @@ export const InviteTeam = () => {
});
}
},
[enqueueSnackBar, sendInviteLink, setNextOnboardingStep],
[enqueueSnackBar, sendInviteLink, setNextOnboardingStatus],
);
useScopedHotkeys(
@ -157,7 +160,7 @@ export const InviteTeam = () => {
[handleSubmit],
);
if (currentUser?.onboardingStep !== OnboardingStep.InviteTeam) {
if (currentUser?.onboardingStatus !== OnboardingStatus.InviteTeam) {
return <></>;
}

View File

@ -1,14 +1,17 @@
import React from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { IconCheck, RGBA } from 'twenty-ui';
import { SubTitle } from '@/auth/components/SubTitle';
import { Title } from '@/auth/components/Title';
import { currentUserState } from '@/auth/states/currentUserState';
import { AppPath } from '@/types/AppPath';
import { MainButton } from '@/ui/input/button/components/MainButton';
import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink';
import { AnimatedEaseIn } from '@/ui/utilities/animation/components/AnimatedEaseIn';
import { OnboardingStatus } from '~/generated/graphql';
const StyledCheckContainer = styled.div`
align-items: center;
@ -29,8 +32,14 @@ const StyledButtonContainer = styled.div`
export const PaymentSuccess = () => {
const theme = useTheme();
const currentUser = useRecoilValue(currentUserState);
const color =
theme.name === 'light' ? theme.grayScale.gray90 : theme.grayScale.gray10;
if (currentUser?.onboardingStatus === OnboardingStatus.Completed) {
return <></>;
}
return (
<>
<AnimatedEaseIn>

View File

@ -9,7 +9,7 @@ import { SubTitle } from '@/auth/components/SubTitle';
import { Title } from '@/auth/components/Title';
import { currentUserState } from '@/auth/states/currentUserState';
import { OnboardingSyncEmailsSettingsCard } from '@/onboarding/components/OnboardingSyncEmailsSettingsCard';
import { useSetNextOnboardingStep } from '@/onboarding/hooks/useSetNextOnboardingStep';
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
import { useTriggerGoogleApisOAuth } from '@/settings/accounts/hooks/useTriggerGoogleApisOAuth';
import { AppPath } from '@/types/AppPath';
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
@ -19,7 +19,7 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import {
CalendarChannelVisibility,
MessageChannelVisibility,
OnboardingStep,
OnboardingStatus,
useSkipSyncEmailOnboardingStepMutation,
} from '~/generated/graphql';
@ -40,12 +40,12 @@ const StyledActionLinkContainer = styled.div`
export const SyncEmails = () => {
const theme = useTheme();
const { triggerGoogleApisOAuth } = useTriggerGoogleApisOAuth();
const setNextOnboardingStep = useSetNextOnboardingStep();
const setNextOnboardingStatus = useSetNextOnboardingStatus();
const currentUser = useRecoilValue(currentUserState);
const [visibility, setVisibility] = useState<MessageChannelVisibility>(
MessageChannelVisibility.ShareEverything,
);
const [skipSyncEmailOnboardingStepMutation] =
const [skipSyncEmailOnboardingStatusMutation] =
useSkipSyncEmailOnboardingStepMutation();
const handleButtonClick = async () => {
@ -62,8 +62,8 @@ export const SyncEmails = () => {
};
const continueWithoutSync = async () => {
await skipSyncEmailOnboardingStepMutation();
setNextOnboardingStep(OnboardingStep.SyncEmail);
await skipSyncEmailOnboardingStatusMutation();
setNextOnboardingStatus();
};
useScopedHotkeys(
@ -75,7 +75,7 @@ export const SyncEmails = () => {
[continueWithoutSync],
);
if (currentUser?.onboardingStep !== OnboardingStep.SyncEmail) {
if (currentUser?.onboardingStatus !== OnboardingStatus.SyncEmail) {
return <></>;
}

View File

@ -5,13 +5,14 @@ import { graphql, HttpResponse } from 'msw';
import { AppPath } from '@/types/AppPath';
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
import { OnboardingStatus } from '~/generated/graphql';
import { ChooseYourPlan } from '~/pages/onboarding/ChooseYourPlan';
import {
PageDecorator,
PageDecoratorArgs,
} from '~/testing/decorators/PageDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedOnboardingUsersData } from '~/testing/mock-data/users';
import { mockedOnboardingUserData } from '~/testing/mock-data/users';
import { sleep } from '~/utils/sleep';
const meta: Meta<PageDecoratorArgs> = {
@ -25,7 +26,9 @@ const meta: Meta<PageDecoratorArgs> = {
graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
return HttpResponse.json({
data: {
currentUser: mockedOnboardingUsersData[0],
currentUser: mockedOnboardingUserData(
OnboardingStatus.PlanRequired,
),
},
});
}),

View File

@ -5,13 +5,14 @@ import { graphql, HttpResponse } from 'msw';
import { AppPath } from '@/types/AppPath';
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
import { OnboardingStatus } from '~/generated/graphql';
import { CreateProfile } from '~/pages/onboarding/CreateProfile';
import {
PageDecorator,
PageDecoratorArgs,
} from '~/testing/decorators/PageDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedOnboardingUsersData } from '~/testing/mock-data/users';
import { mockedOnboardingUserData } from '~/testing/mock-data/users';
const meta: Meta<PageDecoratorArgs> = {
title: 'Pages/Onboarding/CreateProfile',
@ -24,7 +25,9 @@ const meta: Meta<PageDecoratorArgs> = {
graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
return HttpResponse.json({
data: {
currentUser: mockedOnboardingUsersData[0],
currentUser: mockedOnboardingUserData(
OnboardingStatus.ProfileCreation,
),
},
});
}),

View File

@ -2,30 +2,22 @@ import { getOperationName } from '@apollo/client/utilities';
import { Meta, StoryObj } from '@storybook/react';
import { within } from '@storybook/test';
import { graphql, HttpResponse } from 'msw';
import { useSetRecoilState } from 'recoil';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { AppPath } from '@/types/AppPath';
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
import { OnboardingStatus } from '~/generated/graphql';
import { CreateWorkspace } from '~/pages/onboarding/CreateWorkspace';
import {
PageDecorator,
PageDecoratorArgs,
} from '~/testing/decorators/PageDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedOnboardingUsersData } from '~/testing/mock-data/users';
import { mockedOnboardingUserData } from '~/testing/mock-data/users';
const meta: Meta<PageDecoratorArgs> = {
title: 'Pages/Onboarding/CreateWorkspace',
component: CreateWorkspace,
decorators: [
(Story) => {
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
setCurrentWorkspace(mockedOnboardingUsersData[1].defaultWorkspace);
return <Story />;
},
PageDecorator,
],
decorators: [PageDecorator],
args: { routePath: AppPath.CreateWorkspace },
parameters: {
msw: {
@ -33,7 +25,9 @@ const meta: Meta<PageDecoratorArgs> = {
graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
return HttpResponse.json({
data: {
currentUser: mockedOnboardingUsersData[1],
currentUser: mockedOnboardingUserData(
OnboardingStatus.WorkspaceActivation,
),
},
});
}),

View File

@ -3,7 +3,7 @@ import { Meta, StoryObj } from '@storybook/react';
import { within } from '@storybook/test';
import { graphql, HttpResponse } from 'msw';
import { OnboardingStep } from '~/generated/graphql';
import { OnboardingStatus } from '~/generated/graphql';
import { AppPath } from '~/modules/types/AppPath';
import { GET_CURRENT_USER } from '~/modules/users/graphql/queries/getCurrentUser';
import { InviteTeam } from '~/pages/onboarding/InviteTeam';
@ -12,7 +12,7 @@ import {
PageDecoratorArgs,
} from '~/testing/decorators/PageDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedOnboardingUsersData } from '~/testing/mock-data/users';
import { mockedOnboardingUserData } from '~/testing/mock-data/users';
const meta: Meta<PageDecoratorArgs> = {
title: 'Pages/Onboarding/InviteTeam',
@ -25,10 +25,9 @@ const meta: Meta<PageDecoratorArgs> = {
graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
return HttpResponse.json({
data: {
currentUser: {
...mockedOnboardingUsersData[0],
onboardingStep: OnboardingStep.InviteTeam,
},
currentUser: mockedOnboardingUserData(
OnboardingStatus.InviteTeam,
),
},
});
}),

View File

@ -5,13 +5,14 @@ import { graphql, HttpResponse } from 'msw';
import { AppPath } from '@/types/AppPath';
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
import { OnboardingStatus } from '~/generated/graphql';
import { PaymentSuccess } from '~/pages/onboarding/PaymentSuccess';
import {
PageDecorator,
PageDecoratorArgs,
} from '~/testing/decorators/PageDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedOnboardingUsersData } from '~/testing/mock-data/users';
import { mockedOnboardingUserData } from '~/testing/mock-data/users';
const meta: Meta<PageDecoratorArgs> = {
title: 'Pages/Onboarding/PaymentSuccess',
@ -24,7 +25,9 @@ const meta: Meta<PageDecoratorArgs> = {
graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
return HttpResponse.json({
data: {
currentUser: mockedOnboardingUsersData[0],
currentUser: mockedOnboardingUserData(
OnboardingStatus.WorkspaceActivation,
),
},
});
}),

View File

@ -3,7 +3,7 @@ import { Meta, StoryObj } from '@storybook/react';
import { within } from '@storybook/test';
import { graphql, HttpResponse } from 'msw';
import { OnboardingStep } from '~/generated/graphql';
import { OnboardingStatus } from '~/generated/graphql';
import { AppPath } from '~/modules/types/AppPath';
import { GET_CURRENT_USER } from '~/modules/users/graphql/queries/getCurrentUser';
import { SyncEmails } from '~/pages/onboarding/SyncEmails';
@ -12,7 +12,7 @@ import {
PageDecoratorArgs,
} from '~/testing/decorators/PageDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedOnboardingUsersData } from '~/testing/mock-data/users';
import { mockedOnboardingUserData } from '~/testing/mock-data/users';
const meta: Meta<PageDecoratorArgs> = {
title: 'Pages/Onboarding/SyncEmails',
@ -25,10 +25,7 @@ const meta: Meta<PageDecoratorArgs> = {
graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
return HttpResponse.json({
data: {
currentUser: {
...mockedOnboardingUsersData[0],
onboardingStep: OnboardingStep.SyncEmail,
},
currentUser: mockedOnboardingUserData(OnboardingStatus.SyncEmail),
},
});
}),

View File

@ -10,10 +10,9 @@ import {
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 { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SupportChat } from '@/support/components/SupportChat';
import { AppPath } from '@/types/AppPath';
@ -24,8 +23,11 @@ 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 { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
import {
OnboardingStatus,
SubscriptionInterval,
SubscriptionStatus,
useBillingPortalSessionQuery,
useUpdateBillingSubscriptionMutation,
} from '~/generated/graphql';
@ -40,21 +42,21 @@ const StyledInvisibleChat = styled.div`
`;
type SwitchInfo = {
newInterval: string;
newInterval: SubscriptionInterval;
to: string;
from: string;
impact: string;
};
const MONTHLY_SWITCH_INFO: SwitchInfo = {
newInterval: 'year',
newInterval: SubscriptionInterval.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',
newInterval: SubscriptionInterval.Month,
to: 'to monthly',
from: 'from yearly to monthly',
impact: 'Your credit balance will be used to pay the monthly bills.',
@ -68,10 +70,12 @@ const SWITCH_INFOS = {
export const SettingsBilling = () => {
const { enqueueSnackBar } = useSnackBar();
const onboardingStatus = useOnboardingStatus();
const subscriptionStatus = useSubscriptionStatus();
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
const switchingInfo =
currentWorkspace?.currentBillingSubscription?.interval === 'year'
currentWorkspace?.currentBillingSubscription?.interval ===
SubscriptionInterval.Year
? SWITCH_INFOS.year
: SWITCH_INFOS.month;
const [isSwitchingIntervalModalOpen, setIsSwitchingIntervalModalOpen] =
@ -94,14 +98,15 @@ export const SettingsBilling = () => {
onboardingStatus !== OnboardingStatus.Completed;
const displayPaymentFailInfo =
onboardingStatus === OnboardingStatus.PastDue ||
onboardingStatus === OnboardingStatus.Unpaid;
subscriptionStatus === SubscriptionStatus.PastDue ||
subscriptionStatus === SubscriptionStatus.Unpaid;
const displaySubscriptionCanceledInfo =
onboardingStatus === OnboardingStatus.Canceled;
subscriptionStatus === SubscriptionStatus.Canceled;
const displaySubscribeInfo =
onboardingStatus === OnboardingStatus.CompletedWithoutSubscription;
onboardingStatus === OnboardingStatus.Completed &&
!isDefined(subscriptionStatus);
const openBillingPortal = () => {
if (isDefined(data) && isDefined(data.billingPortalSession.url)) {
@ -153,22 +158,20 @@ export const SettingsBilling = () => {
/>
)}
{displaySubscriptionCanceledInfo && (
<UndecoratedLink to={AppPath.PlanRequired}>
<Info
text={'Subscription canceled. Please start a new one'}
buttonTitle={'Subscribe'}
accent={'danger'}
/>
</UndecoratedLink>
<Info
text={'Subscription canceled. Please start a new one'}
buttonTitle={'Subscribe'}
accent={'danger'}
to={AppPath.PlanRequired}
/>
)}
{displaySubscribeInfo ? (
<UndecoratedLink to={AppPath.PlanRequired}>
<Info
text={'Your workspace does not have an active subscription'}
buttonTitle={'Subscribe'}
accent={'danger'}
/>
</UndecoratedLink>
<Info
text={'Your workspace does not have an active subscription'}
buttonTitle={'Subscribe'}
accent={'danger'}
to={AppPath.PlanRequired}
/>
) : (
<>
<Section>