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:
@ -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 };
|
||||
};
|
||||
Reference in New Issue
Block a user