v2 onboarding: dedicated verify step and upgrade-free-trial as the last step (#22303)
https://github.com/user-attachments/assets/b1ee4f77-c6d7-4638-b9f1-dd801d1cc0db Completes the onboarding-v2 flow: a dedicated verify step, the reordering that makes the plan step come last, and the upgrade-free-trial page itself. ## Verify step (`/verify-v2`) After the cross-domain token exchange, v2 sign-ups land on a clean `BlankLayout` "Verifying your email" screen (fading Twenty logo) instead of the v1 `AuthModal` flashing over the background mock. The redirect target is chosen from `isOnboardingV2` (read from the Jotai store at redirect time). The pulsing logo is extracted into a shared `OnboardingPulsingLogo`, reused by the workspace-activation loader. `/verify-v2` joins the same exempt lists as `/verify` (ongoing-creation guard, metadata gater, apollo unauthenticated handler, captcha, page title) — intentionally not `useShowAuthModal`, which is what drops the modal. ## Plan step is now last `getOnboardingStatus` checks `PLAN_REQUIRED` after invite-team instead of first, so onboarding runs workspace activation → email → profile → invite → plan. This is what lets the upgrade step be reached as the final step instead of gating right after sign-up. Applies to both v1 and v2 (same order). ## Upgrade free trial page (`PlanRequiredV2` → `ChooseYourPlanV2` / `UpgradeFreeTrial`) The final step, full-screen under `BlankLayout` via `OnboardingV2Layout`, matching the Figma (billing card with the Stripe form, the "Basic / without credit card" option, trial + credits pills). Reuses the v1 `ChooseYourPlanContent` billing logic (`SubscriptionPaymentForm`, `useHandleCheckoutSession`). The "+N free credits" reward comes from `clientConfig.onboarding.upgradeCreditsReward` (sourced from `BILLING_FREE_WORKFLOW_CREDITS_FOR_TRIAL_PERIOD_WITH_CREDIT_CARD`). ## Also Fixes a latent staleness in the Apollo `onUnauthenticatedError` handler — it captured `location` from the memoized client, now read via a ref — so auth-path exemptions are correct after navigation. Note: the onboarding step order change affects v1 too (plan becomes its last step as well).
This commit is contained in:
@@ -10,6 +10,7 @@ export const ONBOARDING_PATHS = [
|
||||
AppPath.InviteTeam,
|
||||
AppPath.InviteTeamV2,
|
||||
AppPath.PlanRequired,
|
||||
AppPath.PlanRequiredV2,
|
||||
AppPath.PlanRequiredSuccess,
|
||||
AppPath.BookCallDecision,
|
||||
AppPath.BookCall,
|
||||
|
||||
@@ -6,4 +6,5 @@ export const ONGOING_USER_CREATION_PATHS = [
|
||||
AppPath.SignInUpV2,
|
||||
AppPath.VerifyEmail,
|
||||
AppPath.Verify,
|
||||
AppPath.VerifyV2,
|
||||
];
|
||||
|
||||
+3
-24
@@ -1,38 +1,17 @@
|
||||
import { SubTitle } from '@/auth/components/SubTitle';
|
||||
import { WORKSPACE_ACTIVATION_MESSAGES } from '@/auth/sign-in-up/constants/WorkspaceActivationMessages';
|
||||
import { OnboardingPulsingLogo } from '@/onboarding/components/OnboardingPulsingLogo';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { motion, useReducedMotion } from 'framer-motion';
|
||||
import { useContext } from 'react';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { ThemeContext } from 'twenty-ui/theme-constants';
|
||||
|
||||
const STEP_OPACITIES = [1, 0.4, 0.12];
|
||||
const VISIBLE_STEP_COUNT = STEP_OPACITIES.length;
|
||||
const STEP_HEIGHT_IN_PX = 28;
|
||||
const STEPS_CONTAINER_HEIGHT_IN_PX = STEP_HEIGHT_IN_PX * VISIBLE_STEP_COUNT;
|
||||
|
||||
const StyledLogo = styled.img`
|
||||
animation: signInUpWorkspaceActivationLogoPulse 0.8s ease-in-out infinite
|
||||
alternate;
|
||||
height: ${themeCssVariables.spacing[12]};
|
||||
margin-bottom: ${themeCssVariables.spacing[8]};
|
||||
width: ${themeCssVariables.spacing[12]};
|
||||
|
||||
@keyframes signInUpWorkspaceActivationLogoPulse {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
animation: none;
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledStepsContainer = styled.div`
|
||||
height: ${STEPS_CONTAINER_HEIGHT_IN_PX}px;
|
||||
position: relative;
|
||||
@@ -65,7 +44,7 @@ export const SignInUpWorkspaceActivationV2 = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledLogo src="/images/integrations/twenty-logo.svg" alt="" />
|
||||
<OnboardingPulsingLogo />
|
||||
<StyledStepsContainer>
|
||||
{messages.map((message, index) => {
|
||||
const stepOffset = index - messageIndex;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { isOnboardingV2State } from '@/auth/states/isOnboardingV2State';
|
||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useStore } from 'jotai';
|
||||
|
||||
export const useSignUpInNewWorkspace = () => {
|
||||
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
|
||||
@@ -20,6 +22,7 @@ export const useSignUpInNewWorkspace = () => {
|
||||
const isMultiWorkspaceEnabled = useAtomStateValue(
|
||||
isMultiWorkspaceEnabledState,
|
||||
);
|
||||
const store = useStore();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
const { t } = useLingui();
|
||||
|
||||
@@ -73,9 +76,11 @@ export const useSignUpInNewWorkspace = () => {
|
||||
return true;
|
||||
}
|
||||
|
||||
const isOnboardingV2 = store.get(isOnboardingV2State.atom);
|
||||
|
||||
await redirectToWorkspaceDomain(
|
||||
getWorkspaceUrl(data.signUpInNewWorkspace.workspace.workspaceUrls),
|
||||
AppPath.Verify,
|
||||
isOnboardingV2 ? AppPath.VerifyV2 : AppPath.Verify,
|
||||
{ loginToken },
|
||||
'_self',
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user