Files
twenty/packages/twenty-front/src/modules/app/hooks/useCreateAppRouter.tsx
T
Raphaël Bosi db7d8172f7 Add v2 onboarding invite team page (#22229)
<img width="3024" height="1500" alt="CleanShot 2026-06-26 at 18 09
47@2x"
src="https://github.com/user-attachments/assets/e91f30a5-2763-42a0-9abf-d9fa8400870c"
/>


Adds the v2 onboarding **Invite team** page (`INVITE_TEAM`), shown right
after the create-profile step for the onboarding-v2 cohort. It renders
full-screen under `BlankLayout` via the shared `OnboardingV2Layout`,
matching the Figma (340px column, email inputs with inline remove, dark
Invite, Skip).

Reuses all v1 invite-team logic via a new `useInviteTeam` hook (v1
`InviteTeam` now consumes it too; its UI is unchanged). Routing mirrors
`SyncEmailsV2`/`CreateProfileV2`: new `AppPath.InviteTeamV2`, lazy
route, and an `isOnboardingV2`-gated branch in
`usePageChangeEffectNavigateLocation` (+ tests and a Storybook story).

No backend changes.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22229?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. -->
2026-06-29 13:53:07 +02:00

353 lines
9.7 KiB
TypeScript

import { AppRouterProviders } from '@/app/components/AppRouterProviders';
import { LazyRoute } from '@/app/components/LazyRoute';
import { SettingsRoutes } from '@/app/components/SettingsRoutes';
import { VerifyLoginTokenEffect } from '@/auth/components/VerifyLoginTokenEffect';
import { VerifyEmailEffect } from '@/auth/components/VerifyEmailEffect';
import indexAppPath from '@/navigation/utils/indexAppPath';
import { RecordIndexSkeletonLoader } from '@/object-record/record-index/components/RecordIndexSkeletonLoader';
import { BlankLayout } from '@/ui/layout/page/components/BlankLayout';
import { DefaultLayout } from '@/ui/layout/page/components/DefaultLayout';
import { MainAppLayoutWithSidePanel } from '@/ui/layout/page/components/MainAppLayoutWithSidePanel';
import { AppPath, SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { lazy } from 'react';
import {
createBrowserRouter,
createRoutesFromElements,
Navigate,
Route,
} from 'react-router-dom';
const RecordIndexPage = lazy(() =>
import('~/pages/object-record/RecordIndexPage').then((module) => ({
default: module.RecordIndexPage,
})),
);
const RecordShowPage = lazy(() =>
import('~/pages/object-record/RecordShowPage').then((module) => ({
default: module.RecordShowPage,
})),
);
const SignInUp = lazy(() =>
import('~/pages/auth/SignInUp').then((module) => ({
default: module.SignInUp,
})),
);
const SignInUpV2 = lazy(() =>
import('~/pages/auth/SignInUpV2').then((module) => ({
default: module.SignInUpV2,
})),
);
const PasswordReset = lazy(() =>
import('~/pages/auth/PasswordReset').then((module) => ({
default: module.PasswordReset,
})),
);
const Authorize = lazy(() =>
import('~/pages/auth/Authorize').then((module) => ({
default: module.Authorize,
})),
);
const WorkspaceActivation = lazy(() =>
import('~/pages/onboarding/WorkspaceActivation').then((module) => ({
default: module.WorkspaceActivation,
})),
);
const WorkspaceActivationV2 = lazy(() =>
import('~/pages/onboarding/WorkspaceActivationV2').then((module) => ({
default: module.WorkspaceActivationV2,
})),
);
const CreateProfile = lazy(() =>
import('~/pages/onboarding/CreateProfile').then((module) => ({
default: module.CreateProfile,
})),
);
const CreateProfileV2 = lazy(() =>
import('~/pages/onboarding/CreateProfileV2').then((module) => ({
default: module.CreateProfileV2,
})),
);
const SyncEmails = lazy(() =>
import('~/pages/onboarding/SyncEmails').then((module) => ({
default: module.SyncEmails,
})),
);
const SyncEmailsV2 = lazy(() =>
import('~/pages/onboarding/SyncEmailsV2').then((module) => ({
default: module.SyncEmailsV2,
})),
);
const InviteTeam = lazy(() =>
import('~/pages/onboarding/InviteTeam').then((module) => ({
default: module.InviteTeam,
})),
);
const InviteTeamV2 = lazy(() =>
import('~/pages/onboarding/InviteTeamV2').then((module) => ({
default: module.InviteTeamV2,
})),
);
const ChooseYourPlan = lazy(() =>
import('~/pages/onboarding/ChooseYourPlan').then((module) => ({
default: module.ChooseYourPlan,
})),
);
const PaymentSuccess = lazy(() =>
import('~/pages/onboarding/PaymentSuccess').then((module) => ({
default: module.PaymentSuccess,
})),
);
const BookCallDecision = lazy(() =>
import('~/pages/onboarding/BookCallDecision').then((module) => ({
default: module.BookCallDecision,
})),
);
const BookCall = lazy(() =>
import('~/pages/onboarding/BookCall').then((module) => ({
default: module.BookCall,
})),
);
const StandalonePageLayoutPage = lazy(() =>
import('~/pages/page-layout/StandalonePageLayoutPage').then((module) => ({
default: module.StandalonePageLayoutPage,
})),
);
const NotFound = lazy(() =>
import('~/pages/not-found/NotFound').then((module) => ({
default: module.NotFound,
})),
);
export const useCreateAppRouter = (
isFunctionSettingsEnabled?: boolean,
isAdminPageEnabled?: boolean,
) =>
createBrowserRouter(
createRoutesFromElements(
<Route
element={<AppRouterProviders />}
// To switch state to `loading` temporarily to enable us
// to set scroll position before the page is rendered
loader={async () => Promise.resolve(null)}
>
<Route element={<DefaultLayout />}>
<Route path={AppPath.Verify} element={<VerifyLoginTokenEffect />} />
<Route path={AppPath.VerifyEmail} element={<VerifyEmailEffect />} />
<Route
path={AppPath.SignInUp}
element={
<LazyRoute fallback={null}>
<SignInUp />
</LazyRoute>
}
/>
<Route
path={AppPath.Invite}
element={
<LazyRoute fallback={null}>
<SignInUp />
</LazyRoute>
}
/>
<Route
path={AppPath.ResetPassword}
element={
<LazyRoute fallback={null}>
<PasswordReset />
</LazyRoute>
}
/>
<Route
path={AppPath.WorkspaceActivation}
element={
<LazyRoute fallback={null}>
<WorkspaceActivation />
</LazyRoute>
}
/>
<Route
path={AppPath.CreateProfile}
element={
<LazyRoute fallback={null}>
<CreateProfile />
</LazyRoute>
}
/>
<Route
path={AppPath.SyncEmails}
element={
<LazyRoute fallback={null}>
<SyncEmails />
</LazyRoute>
}
/>
<Route
path={AppPath.InviteTeam}
element={
<LazyRoute fallback={null}>
<InviteTeam />
</LazyRoute>
}
/>
<Route
path={AppPath.PlanRequired}
element={
<LazyRoute fallback={null}>
<ChooseYourPlan />
</LazyRoute>
}
/>
<Route
path={AppPath.PlanRequiredSuccess}
element={
<LazyRoute fallback={null}>
<PaymentSuccess />
</LazyRoute>
}
/>
<Route
path={AppPath.BookCallDecision}
element={
<LazyRoute fallback={null}>
<BookCallDecision />
</LazyRoute>
}
/>
<Route
path={AppPath.BookCall}
element={
<LazyRoute fallback={null}>
<BookCall />
</LazyRoute>
}
/>
<Route element={<MainAppLayoutWithSidePanel />}>
<Route path={indexAppPath.getIndexAppPath()} element={<></>} />
<Route
path={AppPath.RecordIndexPage}
element={
<LazyRoute fallback={<RecordIndexSkeletonLoader />}>
<RecordIndexPage />
</LazyRoute>
}
/>
<Route
path={AppPath.RecordShowPage}
element={
<LazyRoute>
<RecordShowPage />
</LazyRoute>
}
/>
<Route
path={AppPath.PageLayoutPage}
element={
<LazyRoute>
<StandalonePageLayoutPage />
</LazyRoute>
}
/>
<Route
path={AppPath.SettingsCatchAll}
element={
<SettingsRoutes
isFunctionSettingsEnabled={isFunctionSettingsEnabled}
isAdminPageEnabled={isAdminPageEnabled}
/>
}
/>
{/* Deep link for twenty.com/dpa → in-app generator. This route is
inside the authenticated layout, so an unauthenticated hit is
login-gated and returns here after sign-in. */}
<Route
path={AppPath.Dpa}
element={
<Navigate to={getSettingsPath(SettingsPath.LegalDpa)} replace />
}
/>
<Route
path={AppPath.NotFoundWildcard}
element={
<LazyRoute>
<NotFound />
</LazyRoute>
}
/>
</Route>
</Route>
<Route element={<BlankLayout />}>
<Route
path={AppPath.SignInUpV2}
element={
<LazyRoute fallback={null}>
<SignInUpV2 />
</LazyRoute>
}
/>
<Route
path={AppPath.WorkspaceActivationV2}
element={
<LazyRoute fallback={null}>
<WorkspaceActivationV2 />
</LazyRoute>
}
/>
<Route
path={AppPath.CreateProfileV2}
element={
<LazyRoute fallback={null}>
<CreateProfileV2 />
</LazyRoute>
}
/>
<Route
path={AppPath.SyncEmailsV2}
element={
<LazyRoute fallback={null}>
<SyncEmailsV2 />
</LazyRoute>
}
/>
<Route
path={AppPath.InviteTeamV2}
element={
<LazyRoute fallback={null}>
<InviteTeamV2 />
</LazyRoute>
}
/>
<Route
path={AppPath.Authorize}
element={
<LazyRoute>
<Authorize />
</LazyRoute>
}
/>
</Route>
</Route>,
),
);