Remove v1 onboarding and rely only on v2 (#22398)
https://github.com/user-attachments/assets/a6bfaac3-6c79-4fd5-999a-e6a70cff8ac8 Removes the old (v1) signup and onboarding flow now that v2 is the only path, and drops the `isOnboardingV2` flag entirely. The surviving (formerly-v2) pages reclaim the canonical `AppPath` members and clean URLs (`/welcome`, `/verify`, `/workspace-activation`, `/create/profile`, `/sync/emails`, `/install-apps`, `/invite-team`, `/plan-required`). - Deletes the v1 pages, the v1 workspace-creation form, the `isOnboardingV2State` flag + `onboardingV2` URL-param plumbing, and `InstallAppsAutoSkipEffect`. - Collapses the router and page-change navigation matrix to a single set of paths, and renames the v2 components/stories to drop the `V2` suffix. Follow-up fixes so the single flow behaves correctly on every deployment: - Restore the captcha-token, query-param and pageview effects on the default (root) domain, and serve `/authorize` there so OAuth login keeps working. - Gate the invite-team → `/plan-required` interception on billing so billing-disabled instances aren't trapped on the upgrade page. - On a cold boot to an auth/onboarding path, show the onboarding loader instead of the CRM skeleton, and add `/verify-email` and `/plan-required/payment-success` to that loader path list. - Add a retry to PaymentSuccess after the confirmation timeout, fix the InstallApps icon crossfade, restyle the book-call pages for the full-page layout, and delete code orphaned by the v1 removal. - Extract the pageview/captcha/query-param logic out of `PageChangeEffect` into standalone Effect components shared by the root and workspace app trees. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22398?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
@@ -1,38 +0,0 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
type TrialCardProps = {
|
||||
duration: number;
|
||||
withCreditCard: boolean;
|
||||
};
|
||||
|
||||
const StyledTrialCardContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const StyledTrialDurationContainer = styled.div`
|
||||
color: ${themeCssVariables.font.color.secondary};
|
||||
display: flex;
|
||||
font-size: ${themeCssVariables.font.size.md};
|
||||
margin-bottom: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledCreditCardRequirementContainer = styled.div`
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
display: flex;
|
||||
font-size: ${themeCssVariables.font.size.md};
|
||||
`;
|
||||
|
||||
export const TrialCard = ({ duration, withCreditCard }: TrialCardProps) => {
|
||||
const { t } = useLingui();
|
||||
return (
|
||||
<StyledTrialCardContainer>
|
||||
<StyledTrialDurationContainer>{t`${duration} days trial`}</StyledTrialDurationContainer>
|
||||
<StyledCreditCardRequirementContainer>
|
||||
{withCreditCard ? t`With Credit Card` : t`No Credit Card`}
|
||||
</StyledCreditCardRequirementContainer>
|
||||
</StyledTrialCardContainer>
|
||||
);
|
||||
};
|
||||
@@ -32,11 +32,42 @@ export const useStripeAppearance = (): Appearance => {
|
||||
return {
|
||||
theme: isDark ? 'night' : 'stripe',
|
||||
variables: {
|
||||
fontFamily: theme.font.family,
|
||||
fontSizeBase: '14px',
|
||||
colorPrimary: toStripeColor(theme.color.blue),
|
||||
colorBackground: toStripeColor(theme.background.primary),
|
||||
colorText: toStripeColor(theme.font.color.primary),
|
||||
colorTextSecondary: toStripeColor(theme.font.color.tertiary),
|
||||
colorTextPlaceholder: toStripeColor(theme.font.color.light),
|
||||
colorDanger: toStripeColor(theme.font.color.danger),
|
||||
borderRadius: '8px',
|
||||
colorIcon: toStripeColor(theme.font.color.tertiary),
|
||||
borderRadius: theme.border.radius.md,
|
||||
spacingGridRow: '12px',
|
||||
},
|
||||
rules: {
|
||||
'.Label': {
|
||||
color: toStripeColor(theme.font.color.secondary),
|
||||
fontWeight: String(theme.font.weight.medium),
|
||||
fontSize: '13px',
|
||||
marginBottom: '6px',
|
||||
},
|
||||
'.Input': {
|
||||
backgroundColor: toStripeColor(theme.background.tertiary),
|
||||
border: `1px solid ${toStripeColor(theme.border.color.medium)}`,
|
||||
boxShadow: 'none',
|
||||
padding: '8px 12px',
|
||||
},
|
||||
'.Input:focus': {
|
||||
border: `1px solid ${toStripeColor(theme.border.color.blue)}`,
|
||||
boxShadow: 'none',
|
||||
},
|
||||
'.Input--invalid': {
|
||||
border: `1px solid ${toStripeColor(theme.border.color.danger)}`,
|
||||
boxShadow: 'none',
|
||||
},
|
||||
'.Input::placeholder': {
|
||||
color: toStripeColor(theme.font.color.light),
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+7
-90
@@ -1,71 +1,39 @@
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { useStripeAppearance } from '@/settings/billing/hooks/useStripeAppearance';
|
||||
import { useStripePromise } from '@/settings/billing/hooks/useStripePromise';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { CombinedGraphQLErrors } from '@apollo/client/errors';
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
import { styled } from '@linaria/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
Elements,
|
||||
PaymentElement,
|
||||
useElements,
|
||||
useStripe,
|
||||
} from '@stripe/react-stripe-js';
|
||||
import { useElements, useStripe } from '@stripe/react-stripe-js';
|
||||
import { useState } from 'react';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Info, Loader } from 'twenty-ui/feedback';
|
||||
import { MainButton } from 'twenty-ui/input';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import {
|
||||
type BillingPlanKey,
|
||||
type SubscriptionInterval,
|
||||
CreateSubscriptionPaymentIntentDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
type SubscriptionPaymentFormContentProps = {
|
||||
type UseSubmitSubscriptionPaymentParams = {
|
||||
plan: BillingPlanKey;
|
||||
recurringInterval: SubscriptionInterval;
|
||||
};
|
||||
|
||||
type SubscriptionPaymentFormProps = SubscriptionPaymentFormContentProps & {
|
||||
amount: number;
|
||||
};
|
||||
|
||||
const StyledFormContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${themeCssVariables.spacing[4]};
|
||||
margin-bottom: ${themeCssVariables.spacing[8]};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledButtonContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const SubscriptionPaymentFormContent = ({
|
||||
export const useSubmitSubscriptionPayment = ({
|
||||
plan,
|
||||
recurringInterval,
|
||||
}: SubscriptionPaymentFormContentProps) => {
|
||||
}: UseSubmitSubscriptionPaymentParams) => {
|
||||
const stripe = useStripe();
|
||||
const elements = useElements();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const customerEmail = useAtomStateValue(currentUserState)?.email;
|
||||
|
||||
const [createSubscriptionPaymentIntent] = useMutation(
|
||||
CreateSubscriptionPaymentIntentDocument,
|
||||
);
|
||||
|
||||
const isStripeReady = isDefined(stripe) && isDefined(elements);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!isStripeReady) {
|
||||
const submit = async () => {
|
||||
if (!isDefined(stripe) || !isDefined(elements)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -135,56 +103,5 @@ const SubscriptionPaymentFormContent = ({
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledFormContainer>
|
||||
<PaymentElement
|
||||
options={{
|
||||
defaultValues: isDefined(customerEmail)
|
||||
? { billingDetails: { email: customerEmail } }
|
||||
: undefined,
|
||||
}}
|
||||
/>
|
||||
<StyledButtonContainer>
|
||||
<MainButton
|
||||
title={t`Continue`}
|
||||
onClick={handleSubmit}
|
||||
width={200}
|
||||
Icon={() => (isSubmitting ? <Loader /> : null)}
|
||||
disabled={!isStripeReady || isSubmitting}
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
</StyledFormContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export const SubscriptionPaymentForm = ({
|
||||
plan,
|
||||
recurringInterval,
|
||||
amount,
|
||||
}: SubscriptionPaymentFormProps) => {
|
||||
const stripePromise = useStripePromise();
|
||||
const appearance = useStripeAppearance();
|
||||
|
||||
if (!isDefined(stripePromise)) {
|
||||
return (
|
||||
<StyledFormContainer>
|
||||
<Info
|
||||
accent="danger"
|
||||
text={t`Card payment is currently unavailable. Please verify your Stripe configuration or contact your workspace admin.`}
|
||||
/>
|
||||
</StyledFormContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Elements
|
||||
stripe={stripePromise}
|
||||
options={{ mode: 'subscription', amount, currency: 'usd', appearance }}
|
||||
>
|
||||
<SubscriptionPaymentFormContent
|
||||
plan={plan}
|
||||
recurringInterval={recurringInterval}
|
||||
/>
|
||||
</Elements>
|
||||
);
|
||||
return { submit, isSubmitting, isStripeReady };
|
||||
};
|
||||
Reference in New Issue
Block a user