Update ChooseYourPlan page with new trial period options (#9628)
### Context - Update /plan-required page to let users get free trial without credit card plan - Update usePageChangeEffectNavigateLocation to redirect paused and canceled subscription (suspended workspace) to /settings/billing page ### To do - [x] Update usePageChangeEffectNavigateLocation test - [x] Update ChooseYourPlan sb test closes #9520 --------- Co-authored-by: etiennejouan <jouan.etienne@gmail.com>
This commit is contained in:
@ -19,7 +19,6 @@ describe('useSignInWithGoogle', () => {
|
||||
plan: BillingPlanKey.Pro,
|
||||
interval: SubscriptionInterval.Month,
|
||||
requirePaymentMethod: true,
|
||||
skipPlanPage: false,
|
||||
};
|
||||
|
||||
const Wrapper = getJestMetadataAndApolloMocksWrapper({
|
||||
@ -31,7 +30,7 @@ describe('useSignInWithGoogle', () => {
|
||||
const mockUseParams = { workspaceInviteHash: 'testHash' };
|
||||
|
||||
const mockSearchParams = new URLSearchParams(
|
||||
'inviteToken=testToken&billingCheckoutSessionState={"plan":"Pro","interval":"Month","requirePaymentMethod":true,"skipPlanPage":false}',
|
||||
'inviteToken=testToken&billingCheckoutSessionState={"plan":"Pro","interval":"Month","requirePaymentMethod":true}',
|
||||
);
|
||||
|
||||
(useParams as jest.Mock).mockReturnValue(mockUseParams);
|
||||
|
||||
@ -22,7 +22,6 @@ describe('useSignInWithMicrosoft', () => {
|
||||
plan: 'PRO',
|
||||
interval: 'Month',
|
||||
requirePaymentMethod: true,
|
||||
skipPlanPage: false,
|
||||
};
|
||||
|
||||
it('should call signInWithMicrosoft with the correct parameters', () => {
|
||||
|
||||
@ -12,7 +12,6 @@ export const useSignInWithGoogle = () => {
|
||||
plan: 'PRO',
|
||||
interval: 'Month',
|
||||
requirePaymentMethod: true,
|
||||
skipPlanPage: false,
|
||||
} as BillingCheckoutSession;
|
||||
|
||||
const { signInWithGoogle } = useAuth();
|
||||
|
||||
@ -1,16 +1,11 @@
|
||||
import { BillingCheckoutSession } from '@/auth/types/billingCheckoutSession.type';
|
||||
import { BILLING_CHECKOUT_SESSION_DEFAULT_VALUE } from '@/billing/constants/BillingCheckoutSessionDefaultValue';
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
import { syncEffect } from 'recoil-sync';
|
||||
import { BillingPlanKey, SubscriptionInterval } from '~/generated/graphql';
|
||||
|
||||
export const billingCheckoutSessionState = createState<BillingCheckoutSession>({
|
||||
key: 'billingCheckoutSessionState',
|
||||
defaultValue: {
|
||||
plan: BillingPlanKey.Pro,
|
||||
interval: SubscriptionInterval.Month,
|
||||
requirePaymentMethod: true,
|
||||
skipPlanPage: false,
|
||||
},
|
||||
defaultValue: BILLING_CHECKOUT_SESSION_DEFAULT_VALUE,
|
||||
effects: [
|
||||
syncEffect({
|
||||
refine: (value: unknown) => {
|
||||
@ -19,8 +14,7 @@ export const billingCheckoutSessionState = createState<BillingCheckoutSession>({
|
||||
value !== null &&
|
||||
'plan' in value &&
|
||||
'interval' in value &&
|
||||
'requirePaymentMethod' in value &&
|
||||
'skipPlanPage' in value
|
||||
'requirePaymentMethod' in value
|
||||
) {
|
||||
return {
|
||||
type: 'success',
|
||||
|
||||
@ -5,5 +5,4 @@ export type BillingCheckoutSession = {
|
||||
plan: BillingPlanKey;
|
||||
interval: SubscriptionInterval;
|
||||
requirePaymentMethod: boolean;
|
||||
skipPlanPage: boolean;
|
||||
};
|
||||
|
||||
@ -1,41 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { SubscriptionCardPrice } from '@/billing/components/SubscriptionCardPrice';
|
||||
import { capitalize } from 'twenty-shared';
|
||||
|
||||
type SubscriptionCardProps = {
|
||||
type?: string;
|
||||
price: number;
|
||||
info: string;
|
||||
};
|
||||
|
||||
const StyledSubscriptionCardContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const StyledTypeContainer = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const StyledInfoContainer = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
export const SubscriptionCard = ({
|
||||
type,
|
||||
price,
|
||||
info,
|
||||
}: SubscriptionCardProps) => {
|
||||
return (
|
||||
<StyledSubscriptionCardContainer>
|
||||
<StyledTypeContainer>{capitalize(type || '')}</StyledTypeContainer>
|
||||
<SubscriptionCardPrice price={price} />
|
||||
<StyledInfoContainer>{info}</StyledInfoContainer>
|
||||
</StyledSubscriptionCardContainer>
|
||||
);
|
||||
};
|
||||
@ -1,33 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
type SubscriptionCardPriceProps = {
|
||||
price: number;
|
||||
};
|
||||
const StyledSubscriptionCardPriceContainer = styled.div`
|
||||
align-items: baseline;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.betweenSiblingsGap};
|
||||
margin: ${({ theme }) => theme.spacing(1)} 0
|
||||
${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
const StyledPriceSpan = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
font-size: ${({ theme }) => theme.font.size.xl};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
`;
|
||||
const StyledSeatSpan = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
`;
|
||||
export const SubscriptionCardPrice = ({
|
||||
price,
|
||||
}: SubscriptionCardPriceProps) => {
|
||||
return (
|
||||
<StyledSubscriptionCardPriceContainer>
|
||||
<StyledPriceSpan>${price}</StyledPriceSpan>
|
||||
<StyledSeatSpan>/</StyledSeatSpan>
|
||||
<StyledSeatSpan>seat</StyledSeatSpan>
|
||||
</StyledSubscriptionCardPriceContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,29 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { SubscriptionInterval } from '~/generated-metadata/graphql';
|
||||
|
||||
type SubscriptionPriceProps = {
|
||||
type: SubscriptionInterval;
|
||||
price: number;
|
||||
};
|
||||
|
||||
const StyledPriceSpan = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
font-size: ${({ theme }) => theme.font.size.xxl};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledPriceUnitSpan = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
`;
|
||||
|
||||
export const SubscriptionPrice = ({ type, price }: SubscriptionPriceProps) => {
|
||||
return (
|
||||
<>
|
||||
<StyledPriceSpan>{`$${price}`}</StyledPriceSpan>
|
||||
<StyledPriceUnitSpan>{`seat / ${type.toLocaleLowerCase()}`}</StyledPriceUnitSpan>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,33 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
type TrialCardProps = {
|
||||
duration: number;
|
||||
withCreditCard: boolean;
|
||||
};
|
||||
|
||||
const StyledTrialCardContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const StyledTrialDurationContainer = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
display: flex;
|
||||
margin-bottom: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const StyledCreditCardRequirementContainer = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
export const TrialCard = ({ duration, withCreditCard }: TrialCardProps) => {
|
||||
return (
|
||||
<StyledTrialCardContainer>
|
||||
<StyledTrialDurationContainer>{`${duration} days trial`}</StyledTrialDurationContainer>
|
||||
<StyledCreditCardRequirementContainer>{`${withCreditCard ? 'With Credit Card' : 'Without Credit Card'}`}</StyledCreditCardRequirementContainer>
|
||||
</StyledTrialCardContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,11 @@
|
||||
import { BillingCheckoutSession } from '@/auth/types/billingCheckoutSession.type';
|
||||
import {
|
||||
BillingPlanKey,
|
||||
SubscriptionInterval,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const BILLING_CHECKOUT_SESSION_DEFAULT_VALUE: BillingCheckoutSession = {
|
||||
plan: BillingPlanKey.Pro,
|
||||
interval: SubscriptionInterval.Month,
|
||||
requirePaymentMethod: true,
|
||||
};
|
||||
@ -0,0 +1,50 @@
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
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 { useState } from 'react';
|
||||
import {
|
||||
BillingPlanKey,
|
||||
SubscriptionInterval,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { useCheckoutSessionMutation } from '~/generated/graphql';
|
||||
|
||||
export const useHandleCheckoutSession = ({
|
||||
recurringInterval,
|
||||
plan,
|
||||
requirePaymentMethod,
|
||||
}: {
|
||||
recurringInterval: SubscriptionInterval;
|
||||
plan: BillingPlanKey;
|
||||
requirePaymentMethod: boolean;
|
||||
}) => {
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
|
||||
const [checkoutSession] = useCheckoutSessionMutation();
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const handleCheckoutSession = async () => {
|
||||
setIsSubmitting(true);
|
||||
const { data } = await checkoutSession({
|
||||
variables: {
|
||||
recurringInterval,
|
||||
successUrlPath: `${AppPath.Settings}/${SettingsPath.Billing}`,
|
||||
plan,
|
||||
requirePaymentMethod,
|
||||
},
|
||||
});
|
||||
setIsSubmitting(false);
|
||||
if (!data?.checkoutSession.url) {
|
||||
enqueueSnackBar(
|
||||
'Checkout session error. Please retry or contact Twenty team',
|
||||
{
|
||||
variant: SnackBarVariant.Error,
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
window.location.replace(data.checkoutSession.url);
|
||||
};
|
||||
return { isSubmitting, handleCheckoutSession };
|
||||
};
|
||||
@ -6,7 +6,10 @@ export const GET_CLIENT_CONFIG = gql`
|
||||
billing {
|
||||
isBillingEnabled
|
||||
billingUrl
|
||||
billingFreeTrialDurationInDays
|
||||
trialPeriods {
|
||||
duration
|
||||
isCreditCardRequired
|
||||
}
|
||||
}
|
||||
authProviders {
|
||||
google
|
||||
|
||||
@ -17,12 +17,14 @@ export const InformationBanner = ({
|
||||
buttonTitle,
|
||||
buttonIcon,
|
||||
buttonOnClick,
|
||||
isButtonDisabled = false,
|
||||
}: {
|
||||
message: string;
|
||||
variant?: BannerVariant;
|
||||
buttonTitle?: string;
|
||||
buttonIcon?: IconComponent;
|
||||
buttonOnClick?: () => void;
|
||||
isButtonDisabled?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<StyledBanner variant={variant}>
|
||||
@ -35,6 +37,7 @@ export const InformationBanner = ({
|
||||
size="small"
|
||||
inverted
|
||||
onClick={buttonOnClick}
|
||||
disabled={isButtonDisabled}
|
||||
/>
|
||||
)}
|
||||
</StyledBanner>
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
import { InformationBannerBillingSubscriptionPaused } from '@/information-banner/components/billing/InformationBannerBillingSubscriptionPaused';
|
||||
import { InformationBannerFailPaymentInfo } from '@/information-banner/components/billing/InformationBannerFailPaymentInfo';
|
||||
import { InformationBannerNoBillingSubscription } from '@/information-banner/components/billing/InformationBannerNoBillingSubscription';
|
||||
import { InformationBannerReconnectAccountEmailAliases } from '@/information-banner/components/reconnect-account/InformationBannerReconnectAccountEmailAliases';
|
||||
import { InformationBannerReconnectAccountInsufficientPermissions } from '@/information-banner/components/reconnect-account/InformationBannerReconnectAccountInsufficientPermissions';
|
||||
import { useIsWorkspaceActivationStatusSuspended } from '@/workspace/hooks/useIsWorkspaceActivationStatusSuspended';
|
||||
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
|
||||
import styled from '@emotion/styled';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
import { SubscriptionStatus } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledInformationBannerWrapper = styled.div`
|
||||
height: 40px;
|
||||
@ -12,10 +19,30 @@ const StyledInformationBannerWrapper = styled.div`
|
||||
`;
|
||||
|
||||
export const InformationBannerWrapper = () => {
|
||||
const subscriptionStatus = useSubscriptionStatus();
|
||||
const isWorkspaceSuspended = useIsWorkspaceActivationStatusSuspended();
|
||||
|
||||
const displayBillingSubscriptionPausedBanner =
|
||||
isWorkspaceSuspended && subscriptionStatus === SubscriptionStatus.Paused;
|
||||
|
||||
const displayBillingSubscriptionCanceledBanner =
|
||||
isWorkspaceSuspended && !isDefined(subscriptionStatus);
|
||||
|
||||
const displayFailPaymentInfoBanner =
|
||||
subscriptionStatus === SubscriptionStatus.PastDue ||
|
||||
subscriptionStatus === SubscriptionStatus.Unpaid;
|
||||
|
||||
return (
|
||||
<StyledInformationBannerWrapper>
|
||||
<InformationBannerReconnectAccountInsufficientPermissions />
|
||||
<InformationBannerReconnectAccountEmailAliases />
|
||||
{displayBillingSubscriptionPausedBanner && (
|
||||
<InformationBannerBillingSubscriptionPaused />
|
||||
)}
|
||||
{displayBillingSubscriptionCanceledBanner && (
|
||||
<InformationBannerNoBillingSubscription />
|
||||
)}
|
||||
{displayFailPaymentInfoBanner && <InformationBannerFailPaymentInfo />}
|
||||
</StyledInformationBannerWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
import { InformationBanner } from '@/information-banner/components/InformationBanner';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
import { useBillingPortalSessionQuery } from '~/generated/graphql';
|
||||
|
||||
export const InformationBannerBillingSubscriptionPaused = () => {
|
||||
const { data, loading } = useBillingPortalSessionQuery({
|
||||
variables: {
|
||||
returnUrlPath: `${AppPath.Settings}/${SettingsPath.Billing}`,
|
||||
},
|
||||
});
|
||||
|
||||
const openBillingPortal = () => {
|
||||
if (isDefined(data) && isDefined(data.billingPortalSession.url)) {
|
||||
window.location.replace(data.billingPortalSession.url);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<InformationBanner
|
||||
variant="danger"
|
||||
message={'Trial expired. Please update your billing details'}
|
||||
buttonTitle="Update"
|
||||
buttonOnClick={() => openBillingPortal()}
|
||||
isButtonDisabled={loading || !isDefined(data)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,29 @@
|
||||
import { InformationBanner } from '@/information-banner/components/InformationBanner';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
import { useBillingPortalSessionQuery } from '~/generated/graphql';
|
||||
|
||||
export const InformationBannerFailPaymentInfo = () => {
|
||||
const { data, loading } = useBillingPortalSessionQuery({
|
||||
variables: {
|
||||
returnUrlPath: `${AppPath.Settings}/${SettingsPath.Billing}`,
|
||||
},
|
||||
});
|
||||
|
||||
const openBillingPortal = () => {
|
||||
if (isDefined(data) && isDefined(data.billingPortalSession.url)) {
|
||||
window.location.replace(data.billingPortalSession.url);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<InformationBanner
|
||||
variant="danger"
|
||||
message={'Last payment failed. Please update your billing details.'}
|
||||
buttonTitle="Update"
|
||||
buttonOnClick={() => openBillingPortal()}
|
||||
isButtonDisabled={loading || !isDefined(data)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,21 @@
|
||||
import { BILLING_CHECKOUT_SESSION_DEFAULT_VALUE } from '@/billing/constants/BillingCheckoutSessionDefaultValue';
|
||||
import { useHandleCheckoutSession } from '@/billing/hooks/useHandleCheckoutSession';
|
||||
import { InformationBanner } from '@/information-banner/components/InformationBanner';
|
||||
|
||||
export const InformationBannerNoBillingSubscription = () => {
|
||||
const { handleCheckoutSession, isSubmitting } = useHandleCheckoutSession({
|
||||
recurringInterval: BILLING_CHECKOUT_SESSION_DEFAULT_VALUE.interval,
|
||||
plan: BILLING_CHECKOUT_SESSION_DEFAULT_VALUE.plan,
|
||||
requirePaymentMethod: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<InformationBanner
|
||||
variant="danger"
|
||||
message={`Your workspace does not have an active subscription`}
|
||||
buttonTitle="Subscribe"
|
||||
buttonOnClick={() => handleCheckoutSession()}
|
||||
isButtonDisabled={isSubmitting}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -10,11 +10,14 @@ import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig';
|
||||
import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery';
|
||||
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
|
||||
import { View } from '@/views/types/View';
|
||||
import { useIsWorkspaceActivationStatusSuspended } from '@/workspace/hooks/useIsWorkspaceActivationStatusSuspended';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const PrefetchRunQueriesEffect = () => {
|
||||
const currentUser = useRecoilValue(currentUserState);
|
||||
|
||||
const isWorkspaceSuspended = useIsWorkspaceActivationStatusSuspended();
|
||||
|
||||
const { upsertRecordsInCache: upsertViewsInCache } =
|
||||
usePrefetchRunQuery<View>({
|
||||
prefetchKey: PrefetchKey.AllViews,
|
||||
@ -42,7 +45,7 @@ export const PrefetchRunQueriesEffect = () => {
|
||||
|
||||
const { result } = useCombinedFindManyRecords({
|
||||
operationSignatures,
|
||||
skip: !currentUser,
|
||||
skip: !currentUser || isWorkspaceSuspended,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { prefetchIsLoadedFamilyState } from '@/prefetch/states/prefetchIsLoadedFamilyState';
|
||||
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
|
||||
import { useIsWorkspaceActivationStatusSuspended } from '@/workspace/hooks/useIsWorkspaceActivationStatusSuspended';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
export const useIsPrefetchLoading = () => {
|
||||
const isWorkspaceSuspended = useIsWorkspaceActivationStatusSuspended();
|
||||
const isFavoriteFoldersPrefetched = useRecoilValue(
|
||||
prefetchIsLoadedFamilyState(PrefetchKey.AllFavoritesFolders),
|
||||
);
|
||||
@ -15,8 +17,9 @@ export const useIsPrefetchLoading = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
!areViewsPrefetched ||
|
||||
!areFavoritesPrefetched ||
|
||||
!isFavoriteFoldersPrefetched
|
||||
!isWorkspaceSuspended &&
|
||||
(!areViewsPrefetched ||
|
||||
!areFavoritesPrefetched ||
|
||||
!isFavoriteFoldersPrefetched)
|
||||
);
|
||||
};
|
||||
|
||||
@ -6,6 +6,7 @@ import { IconX, UndecoratedLink } from 'twenty-ui';
|
||||
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||
import { navigationDrawerExpandedMemorizedState } from '@/ui/navigation/states/navigationDrawerExpandedMemorizedState';
|
||||
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||
import { useIsWorkspaceActivationStatusSuspended } from '@/workspace/hooks/useIsWorkspaceActivationStatusSuspended';
|
||||
|
||||
type NavigationDrawerBackButtonProps = {
|
||||
title: string;
|
||||
@ -51,6 +52,11 @@ export const NavigationDrawerBackButton = ({
|
||||
navigationDrawerExpandedMemorizedState,
|
||||
);
|
||||
|
||||
const isWorkspaceSuspended = useIsWorkspaceActivationStatusSuspended();
|
||||
if (isWorkspaceSuspended) {
|
||||
return <StyledContainer />;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<UndecoratedLink
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { WorkspaceActivationStatus } from '~/generated/graphql';
|
||||
|
||||
export const useIsWorkspaceActivationStatusSuspended = (): boolean => {
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
return (
|
||||
currentWorkspace?.activationStatus === WorkspaceActivationStatus.Suspended
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user