40 remove self billing feature flag (#4379)
* Define quantity at checkout * Remove billing submenu when not isBillingEnabled * Remove feature flag * Log warning when missing subscription active workspace add or remove member * Display subscribe cta for free usage of twenty * Authorize all settings when subscription canceled or unpaid * Display subscribe cta for workspace with canceled subscription * Replace OneToOne by OneToMany * Add a currentBillingSubscriptionField * Handle multiple subscriptions by workspace * Fix redirection * Fix test * Fix billingState
This commit is contained in:
@ -1,9 +1,10 @@
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { billingState } from '@/client-config/states/billingState.ts';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { DefaultLayout } from '@/ui/layout/page/DefaultLayout';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { DefaultPageTitle } from '~/DefaultPageTitle';
|
||||
import { CommandMenuEffect } from '~/effect-components/CommandMenuEffect';
|
||||
import { GotoHotkeysEffect } from '~/effect-components/GotoHotkeysEffect';
|
||||
@ -12,7 +13,6 @@ import { CreateProfile } from '~/pages/auth/CreateProfile';
|
||||
import { CreateWorkspace } from '~/pages/auth/CreateWorkspace';
|
||||
import { PasswordReset } from '~/pages/auth/PasswordReset';
|
||||
import { PaymentSuccess } from '~/pages/auth/PaymentSuccess.tsx';
|
||||
import { PlanRequired } from '~/pages/auth/PlanRequired';
|
||||
import { SignInUp } from '~/pages/auth/SignInUp';
|
||||
import { VerifyEffect } from '~/pages/auth/VerifyEffect';
|
||||
import { DefaultHomePage } from '~/pages/DefaultHomePage';
|
||||
@ -47,7 +47,7 @@ import { SettingsWorkspaceMembers } from '~/pages/settings/SettingsWorkspaceMemb
|
||||
import { Tasks } from '~/pages/tasks/Tasks';
|
||||
|
||||
export const App = () => {
|
||||
const isSelfBillingEnabled = useIsFeatureEnabled('IS_SELF_BILLING_ENABLED');
|
||||
const billing = useRecoilValue(billingState());
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -63,12 +63,7 @@ export const App = () => {
|
||||
<Route path={AppPath.ResetPassword} element={<PasswordReset />} />
|
||||
<Route path={AppPath.CreateWorkspace} element={<CreateWorkspace />} />
|
||||
<Route path={AppPath.CreateProfile} element={<CreateProfile />} />
|
||||
<Route
|
||||
path={AppPath.PlanRequired}
|
||||
element={
|
||||
isSelfBillingEnabled ? <ChooseYourPlan /> : <PlanRequired />
|
||||
}
|
||||
/>
|
||||
<Route path={AppPath.PlanRequired} element={<ChooseYourPlan />} />
|
||||
<Route
|
||||
path={AppPath.PlanRequiredSuccess}
|
||||
element={<PaymentSuccess />}
|
||||
@ -115,10 +110,12 @@ export const App = () => {
|
||||
path={SettingsPath.AccountsEmailsInboxSettings}
|
||||
element={<SettingsAccountsEmailsInboxSettings />}
|
||||
/>
|
||||
<Route
|
||||
path={SettingsPath.Billing}
|
||||
element={<SettingsBilling />}
|
||||
/>
|
||||
{billing?.isBillingEnabled && (
|
||||
<Route
|
||||
path={SettingsPath.Billing}
|
||||
element={<SettingsBilling />}
|
||||
/>
|
||||
)}
|
||||
<Route
|
||||
path={SettingsPath.WorkspaceMembersPage}
|
||||
element={<SettingsWorkspaceMembers />}
|
||||
|
||||
@ -93,7 +93,10 @@ export const PageChangeEffect = () => {
|
||||
[OnboardingStatus.Unpaid, OnboardingStatus.Canceled].includes(
|
||||
onboardingStatus,
|
||||
) &&
|
||||
!isMatchingLocation(SettingsPath.Billing)
|
||||
!(
|
||||
isMatchingLocation(AppPath.SettingsCatchAll) ||
|
||||
isMatchingLocation(AppPath.PlanRequired)
|
||||
)
|
||||
) {
|
||||
navigate(
|
||||
`${AppPath.SettingsCatchAll.replace('/*', '')}/${SettingsPath.Billing}`,
|
||||
@ -110,7 +113,8 @@ export const PageChangeEffect = () => {
|
||||
) {
|
||||
navigate(AppPath.CreateProfile);
|
||||
} else if (
|
||||
onboardingStatus === OnboardingStatus.Completed &&
|
||||
(onboardingStatus === OnboardingStatus.Completed ||
|
||||
onboardingStatus === OnboardingStatus.CompletedWithoutSubscription) &&
|
||||
isMatchingOnboardingRoute
|
||||
) {
|
||||
navigate(AppPath.Index);
|
||||
|
||||
@ -65,6 +65,28 @@ export type Billing = {
|
||||
isBillingEnabled: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
export type BillingSubscription = {
|
||||
__typename?: 'BillingSubscription';
|
||||
id: Scalars['ID'];
|
||||
status: Scalars['String'];
|
||||
};
|
||||
|
||||
export type BillingSubscriptionFilter = {
|
||||
and?: InputMaybe<Array<BillingSubscriptionFilter>>;
|
||||
id?: InputMaybe<IdFilterComparison>;
|
||||
or?: InputMaybe<Array<BillingSubscriptionFilter>>;
|
||||
};
|
||||
|
||||
export type BillingSubscriptionSort = {
|
||||
direction: SortDirection;
|
||||
field: BillingSubscriptionSortFields;
|
||||
nulls?: InputMaybe<SortNulls>;
|
||||
};
|
||||
|
||||
export enum BillingSubscriptionSortFields {
|
||||
Id = 'id'
|
||||
}
|
||||
|
||||
export type BooleanFieldComparison = {
|
||||
is?: InputMaybe<Scalars['Boolean']>;
|
||||
isNot?: InputMaybe<Scalars['Boolean']>;
|
||||
@ -631,7 +653,9 @@ export type Workspace = {
|
||||
__typename?: 'Workspace';
|
||||
activationStatus: Scalars['String'];
|
||||
allowImpersonation: Scalars['Boolean'];
|
||||
billingSubscriptions?: Maybe<Array<BillingSubscription>>;
|
||||
createdAt: Scalars['DateTime'];
|
||||
currentBillingSubscription?: Maybe<BillingSubscription>;
|
||||
deletedAt?: Maybe<Scalars['DateTime']>;
|
||||
displayName?: Maybe<Scalars['String']>;
|
||||
domainName?: Maybe<Scalars['String']>;
|
||||
@ -644,6 +668,12 @@ export type Workspace = {
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceBillingSubscriptionsArgs = {
|
||||
filter?: BillingSubscriptionFilter;
|
||||
sorting?: Array<BillingSubscriptionSort>;
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceFeatureFlagsArgs = {
|
||||
filter?: FeatureFlagFilter;
|
||||
sorting?: Array<FeatureFlagSort>;
|
||||
@ -942,7 +972,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf
|
||||
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, defaultWorkspace: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, subscriptionStatus: string, activationStatus: string, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: string, key: string, value: boolean, workspaceId: string }> | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, domainName?: string | null } | null }> } };
|
||||
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, defaultWorkspace: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, subscriptionStatus: string, activationStatus: string, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: string, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', status: string } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, domainName?: string | null } | null }> } };
|
||||
|
||||
export type ActivateWorkspaceMutationVariables = Exact<{
|
||||
input: ActivateWorkspaceInput;
|
||||
@ -1917,6 +1947,9 @@ export const GetCurrentUserDocument = gql`
|
||||
value
|
||||
workspaceId
|
||||
}
|
||||
currentBillingSubscription {
|
||||
status
|
||||
}
|
||||
}
|
||||
workspaces {
|
||||
workspace {
|
||||
|
||||
@ -21,6 +21,9 @@ const currentWorkspace = {
|
||||
activationStatus: 'active',
|
||||
id: '1',
|
||||
allowImpersonation: true,
|
||||
currentBillingSubscription: {
|
||||
status: 'trialing',
|
||||
},
|
||||
};
|
||||
const currentWorkspaceMember = {
|
||||
id: '1',
|
||||
@ -240,4 +243,35 @@ describe('useOnboardingStatus', () => {
|
||||
|
||||
expect(result.current.onboardingStatus).toBe('unpaid');
|
||||
});
|
||||
|
||||
it('should return "completed_without_subscription"', async () => {
|
||||
const { result } = renderHooks();
|
||||
const {
|
||||
setTokenPair,
|
||||
setBilling,
|
||||
setCurrentWorkspace,
|
||||
setCurrentWorkspaceMember,
|
||||
} = result.current;
|
||||
|
||||
act(() => {
|
||||
setTokenPair(tokenPair);
|
||||
setBilling(billing);
|
||||
setCurrentWorkspace({
|
||||
...currentWorkspace,
|
||||
subscriptionStatus: 'trialing',
|
||||
currentBillingSubscription: null,
|
||||
});
|
||||
setCurrentWorkspaceMember({
|
||||
...currentWorkspaceMember,
|
||||
name: {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.onboardingStatus).toBe(
|
||||
'completed_without_subscription',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -11,6 +11,7 @@ export type CurrentWorkspace = Pick<
|
||||
| 'featureFlags'
|
||||
| 'subscriptionStatus'
|
||||
| 'activationStatus'
|
||||
| 'currentBillingSubscription'
|
||||
>;
|
||||
|
||||
export const currentWorkspaceState = createState<CurrentWorkspace | null>({
|
||||
|
||||
@ -10,6 +10,7 @@ export enum OnboardingStatus {
|
||||
OngoingWorkspaceActivation = 'ongoing_workspace_activation',
|
||||
OngoingProfileCreation = 'ongoing_profile_creation',
|
||||
Completed = 'completed',
|
||||
CompletedWithoutSubscription = 'completed_without_subscription',
|
||||
}
|
||||
|
||||
export const getOnboardingStatus = ({
|
||||
@ -75,5 +76,12 @@ export const getOnboardingStatus = ({
|
||||
return OnboardingStatus.Unpaid;
|
||||
}
|
||||
|
||||
if (
|
||||
isBillingEnabled === true &&
|
||||
!currentWorkspace.currentBillingSubscription
|
||||
) {
|
||||
return OnboardingStatus.CompletedWithoutSubscription;
|
||||
}
|
||||
|
||||
return OnboardingStatus.Completed;
|
||||
};
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { billingState } from '@/client-config/states/billingState.ts';
|
||||
import { SettingsNavigationDrawerItem } from '@/settings/components/SettingsNavigationDrawerItem';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
@ -35,7 +37,7 @@ export const SettingsNavigationDrawerItems = () => {
|
||||
}, [signOut, navigate]);
|
||||
|
||||
const isCalendarEnabled = useIsFeatureEnabled('IS_CALENDAR_ENABLED');
|
||||
const isSelfBillingEnabled = useIsFeatureEnabled('IS_SELF_BILLING_ENABLED');
|
||||
const billing = useRecoilValue(billingState());
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -88,12 +90,13 @@ export const SettingsNavigationDrawerItems = () => {
|
||||
path={SettingsPath.WorkspaceMembersPage}
|
||||
Icon={IconUsers}
|
||||
/>
|
||||
<SettingsNavigationDrawerItem
|
||||
label="Billing"
|
||||
path={SettingsPath.Billing}
|
||||
Icon={IconCurrencyDollar}
|
||||
soon={!isSelfBillingEnabled}
|
||||
/>
|
||||
{billing?.isBillingEnabled && (
|
||||
<SettingsNavigationDrawerItem
|
||||
label="Billing"
|
||||
path={SettingsPath.Billing}
|
||||
Icon={IconCurrencyDollar}
|
||||
/>
|
||||
)}
|
||||
<SettingsNavigationDrawerItem
|
||||
label="Data model"
|
||||
path={SettingsPath.Objects}
|
||||
|
||||
@ -83,7 +83,10 @@ export const DefaultLayout = ({ children }: DefaultLayoutProps) => {
|
||||
OnboardingStatus.OngoingProfileCreation,
|
||||
OnboardingStatus.OngoingWorkspaceActivation,
|
||||
].includes(onboardingStatus)) ||
|
||||
isMatchingLocation(AppPath.ResetPassword)
|
||||
isMatchingLocation(AppPath.ResetPassword) ||
|
||||
(isMatchingLocation(AppPath.PlanRequired) &&
|
||||
(OnboardingStatus.CompletedWithoutSubscription ||
|
||||
OnboardingStatus.Canceled))
|
||||
);
|
||||
}, [isMatchingLocation, onboardingStatus]);
|
||||
|
||||
|
||||
@ -35,6 +35,9 @@ export const GET_CURRENT_USER = gql`
|
||||
value
|
||||
workspaceId
|
||||
}
|
||||
currentBillingSubscription {
|
||||
status
|
||||
}
|
||||
}
|
||||
workspaces {
|
||||
workspace {
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
export type FeatureFlagKey =
|
||||
| 'IS_BLOCKLIST_ENABLED'
|
||||
| 'IS_CALENDAR_ENABLED'
|
||||
| 'IS_QUICK_ACTIONS_ENABLED'
|
||||
| 'IS_SELF_BILLING_ENABLED';
|
||||
| 'IS_QUICK_ACTIONS_ENABLED';
|
||||
|
||||
@ -1,47 +0,0 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { Logo } from '@/auth/components/Logo';
|
||||
import { SubTitle } from '@/auth/components/SubTitle';
|
||||
import { Title } from '@/auth/components/Title';
|
||||
import { billingState } from '@/client-config/states/billingState';
|
||||
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
||||
import { MainButton } from '@/ui/input/button/components/MainButton.tsx';
|
||||
import { AnimatedEaseIn } from '@/ui/utilities/animation/components/AnimatedEaseIn';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
|
||||
const StyledButtonContainer = styled.div`
|
||||
margin-top: ${({ theme }) => theme.spacing(8)};
|
||||
`;
|
||||
|
||||
export const PlanRequired = () => {
|
||||
const billing = useRecoilValue(billingState());
|
||||
|
||||
const handleButtonClick = () => {
|
||||
billing?.billingUrl && window.location.replace(billing.billingUrl);
|
||||
};
|
||||
|
||||
useScopedHotkeys('enter', handleButtonClick, PageHotkeyScope.PlanRequired, [
|
||||
handleButtonClick,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AnimatedEaseIn>
|
||||
<Logo />
|
||||
</AnimatedEaseIn>
|
||||
<Title>Plan required</Title>
|
||||
<SubTitle>
|
||||
Please select a subscription plan before proceeding to sign in.
|
||||
</SubTitle>
|
||||
<StyledButtonContainer>
|
||||
<MainButton
|
||||
title="Get started"
|
||||
onClick={handleButtonClick}
|
||||
width={200}
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -1,54 +0,0 @@
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { within } from '@storybook/test';
|
||||
import { graphql, HttpResponse } from 'msw';
|
||||
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedOnboardingUsersData } from '~/testing/mock-data/users';
|
||||
|
||||
import { PlanRequired } from '../PlanRequired';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/Auth/PlanRequired',
|
||||
component: PlanRequired,
|
||||
decorators: [PageDecorator],
|
||||
args: { routePath: AppPath.PlanRequired },
|
||||
parameters: {
|
||||
msw: {
|
||||
handlers: [
|
||||
graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
currentUser: {
|
||||
...mockedOnboardingUsersData[0],
|
||||
defaultWorkspace: {
|
||||
...mockedOnboardingUsersData[0].defaultWorkspace,
|
||||
subscriptionStatus: 'incomplete',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
graphqlMocks.handlers,
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export type Story = StoryObj<typeof PlanRequired>;
|
||||
|
||||
export const Default: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByRole('button', { name: 'Get started' });
|
||||
},
|
||||
};
|
||||
@ -1,15 +1,13 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import styled from '@emotion/styled';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus.ts';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState.ts';
|
||||
import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus.ts';
|
||||
import { SettingsBillingCoverImage } from '@/billing/components/SettingsBillingCoverImage.tsx';
|
||||
import { supportChatState } from '@/client-config/states/supportChatState.ts';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SupportChat } from '@/support/components/SupportChat.tsx';
|
||||
import { AppPath } from '@/types/AppPath.ts';
|
||||
import { IconCreditCard, IconCurrencyDollar } from '@/ui/display/icon';
|
||||
import { Info } from '@/ui/display/info/components/Info.tsx';
|
||||
import { H1Title } from '@/ui/display/typography/components/H1Title.tsx';
|
||||
@ -29,9 +27,8 @@ const StyledInvisibleChat = styled.div`
|
||||
`;
|
||||
|
||||
export const SettingsBilling = () => {
|
||||
const navigate = useNavigate();
|
||||
const onboardingStatus = useOnboardingStatus();
|
||||
const supportChat = useRecoilValue(supportChatState());
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState());
|
||||
const { data, loading } = useBillingPortalSessionQuery({
|
||||
variables: {
|
||||
returnUrlPath: '/settings/billing',
|
||||
@ -45,22 +42,17 @@ export const SettingsBilling = () => {
|
||||
const displaySubscriptionCanceledInfo =
|
||||
onboardingStatus === OnboardingStatus.Canceled;
|
||||
|
||||
const displaySubscribeInfo =
|
||||
onboardingStatus === OnboardingStatus.CompletedWithoutSubscription;
|
||||
|
||||
const openBillingPortal = () => {
|
||||
if (isDefined(data)) {
|
||||
window.location.replace(data.billingPortalSession.url);
|
||||
}
|
||||
};
|
||||
|
||||
const openChat = () => {
|
||||
if (isNonEmptyString(supportChat.supportDriver)) {
|
||||
window.FrontChat?.('show');
|
||||
} else {
|
||||
window.location.href =
|
||||
'mailto:felix@twenty.com?' +
|
||||
`subject=Subscription Recovery for workspace ${currentWorkspace?.id}&` +
|
||||
'body=Hey,%0D%0A%0D%0AMy subscription is canceled and I would like to subscribe a new one.' +
|
||||
'Can you help me?%0D%0A%0D%0ACheers';
|
||||
}
|
||||
const redirectToSubscribePage = () => {
|
||||
navigate(AppPath.PlanRequired);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -68,14 +60,6 @@ export const SettingsBilling = () => {
|
||||
<SettingsPageContainer>
|
||||
<StyledH1Title title="Billing" />
|
||||
<SettingsBillingCoverImage />
|
||||
{displaySubscriptionCanceledInfo && (
|
||||
<Info
|
||||
text={'Subscription canceled. Please contact us to start a new one'}
|
||||
buttonTitle={'Contact Us'}
|
||||
accent={'danger'}
|
||||
onClick={openChat}
|
||||
/>
|
||||
)}
|
||||
{displayPaymentFailInfo && (
|
||||
<Info
|
||||
text={'Last payment failed. Please update your billing details.'}
|
||||
@ -84,19 +68,37 @@ export const SettingsBilling = () => {
|
||||
onClick={openBillingPortal}
|
||||
/>
|
||||
)}
|
||||
<Section>
|
||||
<H2Title
|
||||
title="Manage your subscription"
|
||||
description="Edit payment method, see your invoices and more"
|
||||
{displaySubscriptionCanceledInfo && (
|
||||
<Info
|
||||
text={'Subscription canceled. Please start a new one'}
|
||||
buttonTitle={'Subscribe'}
|
||||
accent={'danger'}
|
||||
onClick={redirectToSubscribePage}
|
||||
/>
|
||||
<Button
|
||||
Icon={IconCreditCard}
|
||||
title="View billing details"
|
||||
variant="secondary"
|
||||
onClick={openBillingPortal}
|
||||
disabled={loading}
|
||||
)}
|
||||
{displaySubscribeInfo && (
|
||||
<Info
|
||||
text={'Your workspace does not have an active subscription'}
|
||||
buttonTitle={'Subscribe'}
|
||||
accent={'danger'}
|
||||
onClick={redirectToSubscribePage}
|
||||
/>
|
||||
</Section>
|
||||
)}
|
||||
{!displaySubscribeInfo && (
|
||||
<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={loading}
|
||||
/>
|
||||
</Section>
|
||||
)}
|
||||
</SettingsPageContainer>
|
||||
<StyledInvisibleChat>
|
||||
<SupportChat />
|
||||
|
||||
Reference in New Issue
Block a user