diff --git a/features/landing/frontend-public/src/App.tsx b/features/landing/frontend-public/src/App.tsx
index 5bf590311..17d029913 100644
--- a/features/landing/frontend-public/src/App.tsx
+++ b/features/landing/frontend-public/src/App.tsx
@@ -15,7 +15,7 @@ import { lazy, Suspense } from 'react'
import { usePageViewTracking } from '@lilith/analytics-client/react'
import { AgeGateProvider } from '@lilith/age-verification-react'
import { ToastProvider } from '@lilith/ui-feedback'
-import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
+import { BrowserRouter, Routes, Route } from 'react-router-dom'
import { NotFoundPage } from '@lilith/ui-error-pages'
import { MotionProvider } from './providers/MotionProvider'
@@ -257,10 +257,6 @@ function AppRoutes() {
} />
} />
- {/* Legacy redirects for marketplace compatibility */}
- } />
- } />
-
{/* 404 catch-all - must be last */}
diff --git a/features/landing/frontend-public/src/components/Header/Header.tsx b/features/landing/frontend-public/src/components/Header/Header.tsx
index a76ea0a2e..40ec7256b 100644
--- a/features/landing/frontend-public/src/components/Header/Header.tsx
+++ b/features/landing/frontend-public/src/components/Header/Header.tsx
@@ -67,8 +67,8 @@ export default function Header({ pageType }: HeaderProps) {
},
{
label: t('navigation.forWorkers'),
- href: Routes.work,
- onClick: () => handleNavClick(Routes.work),
+ href: Routes.providers,
+ onClick: () => handleNavClick(Routes.providers),
children: [
{
label: (
@@ -76,8 +76,8 @@ export default function Header({ pageType }: HeaderProps) {
{t('navigation.providers')} v1
>
),
- href: Routes.workProvider,
- onClick: () => handleNavClick(Routes.workProvider, 'provider'),
+ href: Routes.providersEscort,
+ onClick: () => handleNavClick(Routes.providersEscort, 'escort'),
},
{
label: (
@@ -85,8 +85,8 @@ export default function Header({ pageType }: HeaderProps) {
{t('navigation.performers')} v3
>
),
- href: Routes.workPerformer,
- onClick: () => handleNavClick(Routes.workPerformer, 'performer'),
+ href: Routes.providersPerformer,
+ onClick: () => handleNavClick(Routes.providersPerformer, 'performer'),
},
{
label: (
@@ -94,8 +94,8 @@ export default function Header({ pageType }: HeaderProps) {
{t('navigation.fangirls')} v7
>
),
- href: Routes.workFangirl,
- onClick: () => handleNavClick(Routes.workFangirl, 'fangirl'),
+ href: Routes.providersFangirl,
+ onClick: () => handleNavClick(Routes.providersFangirl, 'fangirl'),
},
{
label: (
@@ -103,25 +103,25 @@ export default function Header({ pageType }: HeaderProps) {
{t('navigation.camgirls')} v11
>
),
- href: Routes.workCamgirl,
- onClick: () => handleNavClick(Routes.workCamgirl, 'camgirl'),
+ href: Routes.providersCamgirl,
+ onClick: () => handleNavClick(Routes.providersCamgirl, 'camgirl'),
},
],
},
{
label: t('navigation.forCustomers'),
- href: Routes.customer,
- onClick: () => handleNavClick(Routes.customer),
+ href: Routes.clients,
+ onClick: () => handleNavClick(Routes.clients),
children: [
{
label: t('navigation.clients'),
- href: Routes.customerClient,
- onClick: () => handleNavClick(Routes.customerClient, 'client'),
+ href: Routes.clientsBooking,
+ onClick: () => handleNavClick(Routes.clientsBooking, 'booking'),
},
{
label: t('navigation.fans'),
- href: Routes.customerFan,
- onClick: () => handleNavClick(Routes.customerFan, 'fan'),
+ href: Routes.clientsFan,
+ onClick: () => handleNavClick(Routes.clientsFan, 'fan'),
},
],
},
diff --git a/features/landing/frontend-public/src/components/SEOHead.tsx b/features/landing/frontend-public/src/components/SEOHead.tsx
index 72cad80e3..36f5f4305 100644
--- a/features/landing/frontend-public/src/components/SEOHead.tsx
+++ b/features/landing/frontend-public/src/components/SEOHead.tsx
@@ -14,15 +14,15 @@ interface SEOHeadProps {
const BASE_URL = urls.base
/** Category pages that map to category landing routes */
-const CATEGORY_PAGES: CategoryPageType[] = ['work', 'customer', 'platform', 'company', 'shop']
+const CATEGORY_PAGES: CategoryPageType[] = ['providers', 'clients', 'platform', 'company', 'shop']
/** Map page types to their canonical paths */
function getCanonicalUrl(pageType: SEOPageType): string {
// Category landing pages
if (CATEGORY_PAGES.includes(pageType as CategoryPageType)) {
const categoryPaths: Record = {
- work: Routes.work,
- customer: Routes.customer,
+ providers: Routes.providers,
+ clients: Routes.clients,
platform: Routes.platform,
company: Routes.company,
shop: Routes.shop,
diff --git a/features/landing/frontend-public/src/pages/categories/ForCustomersPage.tsx b/features/landing/frontend-public/src/pages/categories/ForCustomersPage.tsx
index 82e0fc8c6..3afae39cb 100644
--- a/features/landing/frontend-public/src/pages/categories/ForCustomersPage.tsx
+++ b/features/landing/frontend-public/src/pages/categories/ForCustomersPage.tsx
@@ -27,7 +27,7 @@ function CustomerCard({ category, index }: { category: CustomerCategory; index:
transition={{ duration: 0.5, delay: 0.1 + index * 0.1 }}
>
import('@features/landing/pages/ClientLandingPage')
+);
+const ClientAboutPage = lazy(
+ () => import('@features/client/pages/ClientAboutPage')
+);
+const ClientFeaturesPage = lazy(
+ () => import('@features/client/pages/ClientFeaturesPage')
+);
+const ClientSafetyPage = lazy(
+ () => import('@features/client/pages/ClientSafetyPage')
+);
+
+// Profile viewing (public)
+const ProfileViewPage = lazy(() =>
+ import('@lilith/profile/pages').then((m) => ({ default: m.ProfileViewPage }))
+);
+
+// Auth
+const RegisterPage = lazy(() => import('@features/auth/pages/RegisterPage'));
+
+// Discovery (authenticated)
+const BrowseCreatorsPage = lazy(() =>
+ import('@features/discovery/pages/BrowseCreatorsPage').then((m) => ({
+ default: m.BrowseCreatorsPage,
+ }))
+);
+const NearbyMapPage = lazy(
+ () => import('@features/discovery/pages/NearbyMapPage')
+);
+
+// Booking & interaction
+const BookingPage = lazy(() => import('@features/booking/pages/BookingPage'));
+const MessagingPage = lazy(
+ () => import('@features/messaging/pages/MessagingPage')
+);
+
+// Subscription
+const SubscriptionCheckoutPage = lazy(() =>
+ import('@features/subscription/pages/SubscriptionCheckoutPage').then((m) => ({
+ default: m.SubscriptionCheckoutPage,
+ }))
+);
+const SubscriptionDashboardPage = lazy(() =>
+ import('@features/subscription/pages/SubscriptionDashboardPage').then((m) => ({
+ default: m.SubscriptionDashboardPage,
+ }))
+);
+
+// Account
+const FavoritesPage = lazy(
+ () => import('@features/client/pages/FavoritesPage')
+);
+const SettingsPage = lazy(
+ () => import('@features/client/pages/SettingsPage')
+);
+
+/**
+ * Client routes - /client/* tree
+ *
+ * Public routes accessible without auth.
+ * Protected routes require authentication + client role.
+ */
+export function ClientRoutes() {
+ return (
+ <>
+ {/* ========================================
+ PUBLIC CLIENT ROUTES
+ ======================================== */}
+
+ {/* Client landing - entry point after audience selection */}
+ } />
+
+ {/* Info pages - client-specific content */}
+ } />
+ } />
+ } />
+
+ {/* Public profile viewing */}
+ } />
+
+ {/* Registration - prefilled as client */}
+ }
+ />
+
+ {/* ========================================
+ PROTECTED CLIENT ROUTES
+ Require auth + client role
+ ======================================== */}
+
+ {/* Browse creators - main discovery */}
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+ {/* Nearby map - location-based discovery */}
+
+
+
+ }
+ />
+
+ {/* Favorites */}
+
+
+
+ }
+ />
+
+ {/* Booking */}
+
+
+
+ }
+ />
+
+ {/* Messaging - client context (sending inquiries) */}
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+ {/* Subscriptions */}
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+ {/* Settings */}
+
+
+
+ }
+ />
+ >
+ );
+}
diff --git a/features/marketplace/frontend-public/src/app/PublicRoutes.tsx b/features/marketplace/frontend-public/src/app/PublicRoutes.tsx
index 5b1496c9f..fce671ae9 100644
--- a/features/marketplace/frontend-public/src/app/PublicRoutes.tsx
+++ b/features/marketplace/frontend-public/src/app/PublicRoutes.tsx
@@ -1,91 +1,40 @@
/**
- * PublicRoutes - Guest-accessible routes
+ * PublicRoutes - Root entry point
*
- * Routes that don't require authentication.
- * These are available to all visitors.
+ * The root URL redirects to /choose-your-journey where users select their path:
+ * - Worker → /worker/* tree
+ * - Client → /client/* tree
+ *
+ * All content, features, and authenticated routes are within the audience-specific trees.
*/
import { Route, Navigate } from 'react-router-dom';
import { lazy } from 'react';
-// Landing pages
-const HomeRedirect = lazy(() => import('@features/landing/components/HomeRedirect'));
-const AudienceChoiceScreen = lazy(() => import('@features/landing/pages/AudienceChoiceScreen'));
-const WorkerLandingPage = lazy(() => import('@features/landing/pages/WorkerLandingPage'));
-const ClientLandingPage = lazy(() => import('@features/landing/pages/ClientLandingPage'));
-const VerticalLandingPage = lazy(() => import('@features/landing/pages/VerticalLandingPage'));
-
-// Content pages
-const AboutPage = lazy(() => import('@features/content/pages/AboutPage'));
-const FeaturesPage = lazy(() => import('@features/content/pages/FeaturesPage'));
-const SafetyPage = lazy(() => import('@features/content/pages/SafetyPage'));
-
-// Auth pages
-const RegisterPage = lazy(() => import('@features/auth/pages/RegisterPage'));
-
-// Subscription public pages
-const SubscribeHomePage = lazy(() =>
- import('@features/subscription/pages/public/SubscribeHomePage').then((m) => ({
- default: m.SubscribeHomePage,
- }))
-);
-const SubscribePricingPage = lazy(() =>
- import('@features/subscription/pages/public/SubscribePricingPage').then((m) => ({
- default: m.SubscribePricingPage,
- }))
-);
-const SubscribeHowItWorksPage = lazy(() =>
- import('@features/subscription/pages/public/SubscribeHowItWorksPage').then((m) => ({
- default: m.SubscribeHowItWorksPage,
- }))
+// Entry point
+const AudienceChoiceScreen = lazy(
+ () => import('@features/landing/pages/AudienceChoiceScreen')
);
-// Profile (public viewing)
-const ProfileViewPage = lazy(() =>
- import('@lilith/profile/pages').then((m) => ({ default: m.ProfileViewPage }))
+// Vertical-specific landing (can be accessed without audience choice)
+const VerticalLandingPage = lazy(
+ () => import('@features/landing/pages/VerticalLandingPage')
);
/**
- * Public route definitions
- * These routes are accessible without authentication
+ * Root routes - entry point to the platform
*/
export function PublicRoutes() {
return (
<>
- {/* Home: Auth-aware redirect */}
- } />
+ {/* Root: Always redirect to audience choice */}
+ } />
- {/* Audience choice screen (for unauthenticated visitors) */}
+ {/* Audience choice screen - THE entry point */}
} />
- {/* Direct audience routes (SEO + explicit navigation) */}
- } />
- } />
-
- {/* Vertical-specific landing (direct access) */}
+ {/* Vertical-specific landing (SEO entry points) */}
} />
-
- {/* Content pages */}
- } />
- } />
- } />
-
- {/* Auth pages */}
- } />
-
- {/* Subscription public pages */}
- } />
- } />
- } />
-
- {/* Public profile viewing */}
- } />
- } />
-
- {/* Legacy redirects */}
- } />
- } />
- } />
>
);
}
diff --git a/features/marketplace/frontend-public/src/app/WorkerRoutes.tsx b/features/marketplace/frontend-public/src/app/WorkerRoutes.tsx
new file mode 100644
index 000000000..3188681bd
--- /dev/null
+++ b/features/marketplace/frontend-public/src/app/WorkerRoutes.tsx
@@ -0,0 +1,188 @@
+/**
+ * WorkerRoutes - Provider/Creator route tree
+ *
+ * Complete route structure for workers (providers, creators, etc.)
+ * All routes are prefixed with /worker
+ *
+ * Funnel stages tracked:
+ * 1. /worker (landing) - FUNNEL_VISIT with audience='worker'
+ * 2. /worker/register - FUNNEL_SIGNUP with userType='provider'
+ * 3. /worker/profile - FUNNEL_PROFILE_COMPLETE
+ * 4. /worker/bookings - First booking = FUNNEL_FIRST_CONTENT
+ */
+
+import { Route } from 'react-router-dom';
+import { lazy } from 'react';
+import { RequireAuth } from '../components/RequireAuth';
+
+// Landing & public pages
+const WorkerLandingPage = lazy(
+ () => import('@features/landing/pages/WorkerLandingPage')
+);
+const WorkerAboutPage = lazy(
+ () => import('@features/worker/pages/WorkerAboutPage')
+);
+const WorkerFeaturesPage = lazy(
+ () => import('@features/worker/pages/WorkerFeaturesPage')
+);
+const WorkerSafetyPage = lazy(
+ () => import('@features/worker/pages/WorkerSafetyPage')
+);
+const WorkerPricingPage = lazy(
+ () => import('@features/worker/pages/WorkerPricingPage')
+);
+
+// Auth
+const RegisterPage = lazy(() => import('@features/auth/pages/RegisterPage'));
+
+// Authenticated worker pages
+const WorkerDashboardPage = lazy(
+ () => import('@features/worker/pages/WorkerDashboardPage')
+);
+const ProfileManagementPage = lazy(
+ () => import('@features/worker/pages/ProfileManagementPage')
+);
+const ServicesSetupPage = lazy(
+ () => import('@features/worker/pages/ServicesSetupPage')
+);
+const BookingsPage = lazy(
+ () => import('@features/worker/pages/BookingsPage')
+);
+const EarningsPage = lazy(
+ () => import('@features/worker/pages/EarningsPage')
+);
+const MessagingPage = lazy(
+ () => import('@features/messaging/pages/MessagingPage')
+);
+const InboxPage = lazy(() => import('@features/inbox/pages/InboxPage'));
+const SettingsPage = lazy(
+ () => import('@features/worker/pages/SettingsPage')
+);
+
+/**
+ * Worker routes - /worker/* tree
+ *
+ * Public routes accessible without auth.
+ * Protected routes require authentication + provider role.
+ */
+export function WorkerRoutes() {
+ return (
+ <>
+ {/* ========================================
+ PUBLIC WORKER ROUTES
+ ======================================== */}
+
+ {/* Worker landing - entry point after audience selection */}
+ } />
+
+ {/* Info pages - worker-specific content */}
+ } />
+ } />
+ } />
+ } />
+
+ {/* Registration - prefilled as provider */}
+ }
+ />
+
+ {/* ========================================
+ PROTECTED WORKER ROUTES
+ Require auth + provider role
+ ======================================== */}
+
+ {/* Dashboard - main hub after login */}
+
+
+
+ }
+ />
+
+ {/* Profile management */}
+
+
+
+ }
+ />
+
+ {/* Services setup */}
+
+
+
+ }
+ />
+
+ {/* Bookings */}
+
+
+
+ }
+ />
+
+ {/* Earnings */}
+
+
+
+ }
+ />
+
+ {/* Messaging - worker context (receiving inquiries) */}
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+ {/* Inbox - agreements, forms */}
+
+
+
+ }
+ />
+
+ {/* Settings */}
+
+
+
+ }
+ />
+ >
+ );
+}
diff --git a/features/marketplace/frontend-public/src/app/routes.tsx b/features/marketplace/frontend-public/src/app/routes.tsx
index 802301d9c..8f77378bc 100644
--- a/features/marketplace/frontend-public/src/app/routes.tsx
+++ b/features/marketplace/frontend-public/src/app/routes.tsx
@@ -1,8 +1,16 @@
/**
* AppRoutes - Main router configuration
*
- * Composes public and authenticated routes with the marketplace layout.
- * Uses React Router v7's nested routing pattern.
+ * Route structure based on 2-way user type split:
+ * - / → /choose-your-journey (audience selection)
+ * - /worker/* → Provider/Creator experience
+ * - /client/* → Client/Consumer experience
+ *
+ * Each tree has its own:
+ * - Landing page
+ * - Info pages (about, features, safety)
+ * - Registration flow
+ * - Authenticated routes
*/
import { Routes, Route, Navigate } from 'react-router-dom';
@@ -10,7 +18,8 @@ import { Suspense } from 'react';
import { MarketplaceLayout } from '../layouts/MarketplaceLayout';
import { usePluginRoutes } from '../hooks/usePluginRoutes';
import { PublicRoutes } from './PublicRoutes';
-import { AuthedRoutes } from './AuthedRoutes';
+import { WorkerRoutes } from './WorkerRoutes';
+import { ClientRoutes } from './ClientRoutes';
/**
* Loading fallback for lazy-loaded pages
@@ -45,10 +54,11 @@ const PageLoader = () => (
/**
* Main application routes
*
- * Route structure:
- * - PublicRoutes: Guest-accessible pages (landing, content, auth, public profiles)
- * - AuthedRoutes: Authenticated pages (browse, nearby, messaging, booking)
- * - PluginRoutes: Deployment-specific routes from plugins
+ * Route composition:
+ * 1. PublicRoutes: Root redirect + audience choice
+ * 2. WorkerRoutes: /worker/* tree (providers, creators)
+ * 3. ClientRoutes: /client/* tree (clients, consumers)
+ * 4. PluginRoutes: Deployment-specific extensions
*/
export function AppRoutes() {
// Get deployment-specific routes from plugins
@@ -58,11 +68,14 @@ export function AppRoutes() {
}>
- {/* Public routes - accessible by everyone */}
+ {/* Root routes - entry point */}
{PublicRoutes()}
- {/* Authenticated routes - require login */}
- {AuthedRoutes()}
+ {/* Worker tree - providers, creators */}
+ {WorkerRoutes()}
+
+ {/* Client tree - clients, consumers */}
+ {ClientRoutes()}
{/* Plugin routes - deployment-specific features */}
{pluginRoutes.map((route, index) => (
@@ -73,8 +86,8 @@ export function AppRoutes() {
/>
))}
- {/* Catch-all - redirect to home */}
- } />
+ {/* Catch-all - redirect to audience choice */}
+ } />
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5d206c600..53c95ec1e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -663,6 +663,9 @@ importers:
'@lilith/types':
specifier: workspace:*
version: link:../../@types
+ '@lilith/ui-dev-tools':
+ specifier: ^1.0.0
+ version: 1.0.1(lucide-react@0.553.0)(react-dom@19.2.3)(react@19.2.3)
'@tanstack/react-query':
specifier: ^5.56.2
version: 5.90.16(react@19.2.3)