feat(marketplace/frontend-public): ✨ Add React hooks for profile auto-selection, deployment configurations, and i18n support for niche categories
This commit is contained in:
parent
34cc8f7a81
commit
8752ba2c39
15 changed files with 89 additions and 73 deletions
|
|
@ -12,7 +12,9 @@
|
|||
*/
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { useParams, useLocation } from '@lilith/ui-router';
|
||||
|
||||
import { useActiveProfile } from '@/contexts/ActiveProfileContext';
|
||||
import { useCooperativeMembers } from '@/features/coop/hooks';
|
||||
|
||||
|
|
@ -28,7 +30,7 @@ interface RouteParams {
|
|||
|
||||
interface AutoSelectContext {
|
||||
profiles: ReturnType<typeof useActiveProfile>['profiles'];
|
||||
coopMembers?: { profileId: string }[];
|
||||
coopMembers?: Array<{ profileId: string }>;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
|
|
@ -68,16 +70,16 @@ export function useAutoSelectProfile(options: UseAutoSelectProfileOptions = {})
|
|||
|
||||
useEffect(() => {
|
||||
// Skip if disabled or profiles still loading
|
||||
if (!enabled || profilesLoading) return;
|
||||
if (!enabled || profilesLoading) {return;}
|
||||
|
||||
// Skip if no profiles
|
||||
if (profiles.length === 0) return;
|
||||
if (profiles.length === 0) {return;}
|
||||
|
||||
// Create route key to track if we've already auto-selected
|
||||
const routeKey = `${location.pathname}:${coopId || ''}`;
|
||||
|
||||
// Skip if we've already auto-selected for this exact route
|
||||
if (lastAutoSelectRoute.current === routeKey) return;
|
||||
if (lastAutoSelectRoute.current === routeKey) {return;}
|
||||
|
||||
// Auto-select based on route
|
||||
const newProfileId = determineProfileForRoute(location.pathname, params, {
|
||||
|
|
@ -107,12 +109,10 @@ export function useAutoSelectProfile(options: UseAutoSelectProfileOptions = {})
|
|||
]);
|
||||
|
||||
// Reset tracking when navigating away
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
useEffect(() => () => {
|
||||
lastAutoSelectRoute.current = null;
|
||||
lastAutoSelectProfileId.current = null;
|
||||
};
|
||||
}, []);
|
||||
}, []);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
|
|
@ -199,7 +199,7 @@ export function useAutoSelectEditProfile() {
|
|||
const { profiles, setActiveProfile, activeProfileId, profilesLoading } = useActiveProfile();
|
||||
|
||||
useEffect(() => {
|
||||
if (profilesLoading || !params.slug) return;
|
||||
if (profilesLoading || !params.slug) {return;}
|
||||
|
||||
const profile = profiles.find((p) => p.slug === params.slug);
|
||||
if (profile && profile.id !== activeProfileId) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { useMemo } from 'react';
|
||||
import type { DeploymentConfig, VerticalConfig } from '@lilith/types';
|
||||
|
||||
import { getVerticalConfigByDomain, VERTICAL_CONFIGS } from '@lilith/marketplace-shared';
|
||||
|
||||
import type { DeploymentConfig, VerticalConfig } from '@lilith/types';
|
||||
|
||||
import { getCurrentDeployment } from '@/deployments';
|
||||
|
||||
declare global {
|
||||
|
|
|
|||
|
|
@ -15,8 +15,10 @@
|
|||
*/
|
||||
|
||||
import { useEffect, useCallback } from 'react';
|
||||
import { useLocation } from '@lilith/ui-router';
|
||||
|
||||
import { useAuth } from '@lilith/auth-provider';
|
||||
import { useLocation } from '@lilith/ui-router';
|
||||
|
||||
import { useAudience } from '@/contexts/AudienceContext';
|
||||
|
||||
type FunnelEvent =
|
||||
|
|
|
|||
|
|
@ -23,9 +23,11 @@
|
|||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { getPluginNavItems } from '@/plugins/registry';
|
||||
import type { NavItem } from '@/plugins/types';
|
||||
|
||||
import type { DeploymentId } from '@/deployments';
|
||||
import type { NavItem } from '@/plugins/types';
|
||||
|
||||
import { getPluginNavItems } from '@/plugins/registry';
|
||||
|
||||
// Type declaration for build-time constant
|
||||
declare const __DEPLOYMENT__: string;
|
||||
|
|
|
|||
|
|
@ -23,9 +23,11 @@
|
|||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import type { RouteObject } from '@lilith/ui-router';
|
||||
import { getPluginRoutes } from '@/plugins/registry';
|
||||
|
||||
import type { DeploymentId } from '@/deployments';
|
||||
import type { RouteObject } from '@lilith/ui-router';
|
||||
|
||||
import { getPluginRoutes } from '@/plugins/registry';
|
||||
|
||||
// Build-time deployment constant (injected by Vite)
|
||||
declare const __DEPLOYMENT__: string;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
*/
|
||||
|
||||
import { useCallback, useRef, useEffect } from 'react'
|
||||
|
||||
import { useAuth } from '@lilith/auth-provider'
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
|
@ -146,11 +147,11 @@ function clearAttribution(profileId: string): void {
|
|||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
function getDeviceType(): ProfileDeviceType {
|
||||
if (typeof window === 'undefined') return 'DESKTOP'
|
||||
if (typeof window === 'undefined') {return 'DESKTOP'}
|
||||
|
||||
const width = window.innerWidth
|
||||
if (width < 768) return 'MOBILE'
|
||||
if (width < 1024) return 'TABLET'
|
||||
if (width < 768) {return 'MOBILE'}
|
||||
if (width < 1024) {return 'TABLET'}
|
||||
return 'DESKTOP'
|
||||
}
|
||||
|
||||
|
|
@ -221,7 +222,7 @@ export function useProfileTracking(
|
|||
|
||||
const flushDiscoveryBatch = useCallback(() => {
|
||||
const batch = discoveryBatchRef.current
|
||||
if (batch.length === 0) return
|
||||
if (batch.length === 0) {return}
|
||||
|
||||
discoveryBatchRef.current = []
|
||||
|
||||
|
|
@ -256,8 +257,7 @@ export function useProfileTracking(
|
|||
}, [user?.id])
|
||||
|
||||
// Cleanup on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
useEffect(() => () => {
|
||||
if (batchTimeoutRef.current) {
|
||||
clearTimeout(batchTimeoutRef.current)
|
||||
}
|
||||
|
|
@ -265,8 +265,7 @@ export function useProfileTracking(
|
|||
if (discoveryBatchRef.current.length > 0) {
|
||||
flushDiscoveryBatch()
|
||||
}
|
||||
}
|
||||
}, [flushDiscoveryBatch])
|
||||
}, [flushDiscoveryBatch])
|
||||
|
||||
const trackDiscovery = useCallback(
|
||||
(params: TrackDiscoveryParams) => {
|
||||
|
|
@ -359,9 +358,7 @@ export function useProfileTracking(
|
|||
[user?.id]
|
||||
)
|
||||
|
||||
const getProfileAttribution = useCallback((profileId: string) => {
|
||||
return getAttribution(profileId)
|
||||
}, [])
|
||||
const getProfileAttribution = useCallback((profileId: string) => getAttribution(profileId), [])
|
||||
|
||||
const setProfileAttribution = useCallback(
|
||||
(profileId: string, attribution: Omit<ProfileAttribution, 'timestamp'>) => {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import styled, { type DefaultTheme } from '@lilith/ui-styled-components';
|
||||
import { useNavigate, useLocation } from '@lilith/ui-router';
|
||||
|
||||
|
||||
import { FABLanguageSelector, useI18nContext, type SoundEngine } from '@lilith/i18n';
|
||||
import { soundEngine, type SoundEvent } from '@lilith/ui-effects-sound';
|
||||
import { DeveloperFab, type DevUserContextForFAB } from '@lilith/ui-developer-fab';
|
||||
import { useDevUser } from '@lilith/ui-dev-tools';
|
||||
import { DeveloperFab, type DevUserContextForFAB } from '@lilith/ui-developer-fab';
|
||||
import { soundEngine, type SoundEvent } from '@lilith/ui-effects-sound';
|
||||
import { MultiFAB } from '@lilith/ui-fab';
|
||||
import { useNavigate, useLocation } from '@lilith/ui-router';
|
||||
import styled, { type DefaultTheme } from '@lilith/ui-styled-components';
|
||||
|
||||
import { BrowseModeStatusBar } from '@/components/BrowseModeStatusBar';
|
||||
import { FloatingSettings } from '@/components/FloatingSettings';
|
||||
import { MarketplaceHeader } from '@/components/MarketplaceHeader';
|
||||
import { BrowseModeStatusBar } from '@/components/BrowseModeStatusBar';
|
||||
|
||||
const HEADER_HEIGHT = 72;
|
||||
|
||||
|
|
@ -42,7 +43,7 @@ const soundEngineAdapter: SoundEngine = {
|
|||
play: (sound: string) => soundEngine.play(sound as SoundEvent),
|
||||
};
|
||||
|
||||
export function MarketplaceLayout({ children }: MarketplaceLayoutProps) {
|
||||
export const MarketplaceLayout = ({ children }: MarketplaceLayoutProps) => {
|
||||
const { changeLanguage } = useI18nContext();
|
||||
const devUser = useDevUser();
|
||||
const navigate = useNavigate();
|
||||
|
|
@ -68,7 +69,7 @@ export function MarketplaceLayout({ children }: MarketplaceLayoutProps) {
|
|||
} else if (level === 'admin') {
|
||||
// Admin = admin type only
|
||||
context.userTypes.forEach((type) => {
|
||||
if (type !== 'admin') context.removeType(type);
|
||||
if (type !== 'admin') {context.removeType(type);}
|
||||
});
|
||||
if (!context.userTypes.includes('admin')) {
|
||||
context.addType('admin');
|
||||
|
|
@ -77,7 +78,7 @@ export function MarketplaceLayout({ children }: MarketplaceLayoutProps) {
|
|||
} else if (level === 'user') {
|
||||
// User = provider (default user type for marketplace)
|
||||
context.userTypes.forEach((type) => {
|
||||
if (type === 'admin') context.removeType(type);
|
||||
if (type === 'admin') {context.removeType(type);}
|
||||
});
|
||||
if (!context.userTypes.includes('provider')) {
|
||||
context.addType('provider');
|
||||
|
|
@ -142,7 +143,7 @@ export function MarketplaceLayout({ children }: MarketplaceLayoutProps) {
|
|||
{ id: 'provider', name: 'Provider' },
|
||||
{ id: 'escort', name: 'Escort' },
|
||||
]}
|
||||
showContentEditor={true}
|
||||
showContentEditor
|
||||
devUserContext={devUserContext}
|
||||
onAccessLevelChange={handleAccessLevelChange}
|
||||
onProfileChange={handleProfileChange}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@
|
|||
* Content for professional Dominants, Submissives, and kink specialists.
|
||||
*/
|
||||
|
||||
import landingChoiceEn from './en/landing-choice.json';
|
||||
import landingClientEn from './en/landing-client.json';
|
||||
import landingWorkerEn from './en/landing-worker.json';
|
||||
|
||||
import type { Resource } from 'i18next';
|
||||
|
||||
// Import landing page translations
|
||||
import landingWorkerEn from './en/landing-worker.json';
|
||||
import landingClientEn from './en/landing-client.json';
|
||||
import landingChoiceEn from './en/landing-choice.json';
|
||||
|
||||
/**
|
||||
* Bundled translation resources for BDSM deployment
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@
|
|||
* Content for cam performers and content creators.
|
||||
*/
|
||||
|
||||
import landingChoiceEn from './en/landing-choice.json';
|
||||
import landingClientEn from './en/landing-client.json';
|
||||
import landingWorkerEn from './en/landing-worker.json';
|
||||
|
||||
import type { Resource } from 'i18next';
|
||||
|
||||
// Import landing page translations
|
||||
import landingWorkerEn from './en/landing-worker.json';
|
||||
import landingClientEn from './en/landing-client.json';
|
||||
import landingChoiceEn from './en/landing-choice.json';
|
||||
|
||||
/**
|
||||
* Bundled translation resources for cam deployment
|
||||
|
|
|
|||
|
|
@ -5,12 +5,13 @@
|
|||
* All content is consolidated in the shared locales/en/ directory for unified editing.
|
||||
*/
|
||||
|
||||
|
||||
import type { Resource } from 'i18next';
|
||||
|
||||
// Import landing page translations from consolidated shared location
|
||||
import landingWorkerEn from '../en/marketplace-landing-worker.json';
|
||||
import landingClientEn from '../en/marketplace-landing-client.json';
|
||||
import landingChoiceEn from '../en/marketplace-landing-choice.json';
|
||||
import landingChoiceEn from '@/en/marketplace-landing-choice.json';
|
||||
import landingClientEn from '@/en/marketplace-landing-client.json';
|
||||
import landingWorkerEn from '@/en/marketplace-landing-worker.json';
|
||||
|
||||
/**
|
||||
* Bundled translation resources for escorts deployment
|
||||
|
|
|
|||
|
|
@ -5,21 +5,31 @@
|
|||
* All TrustedMeet content is consolidated in locales/en/ for unified editing.
|
||||
*/
|
||||
|
||||
import marketplaceLandingClientBdsmEn from '@i18n-locales/en/marketplace-landing-client-bdsm.json';
|
||||
import marketplaceLandingClientCamEn from '@i18n-locales/en/marketplace-landing-client-cam.json';
|
||||
import marketplaceLandingClientEscortsEn from '@i18n-locales/en/marketplace-landing-client-escorts.json';
|
||||
import marketplaceLandingClientMassageEn from '@i18n-locales/en/marketplace-landing-client-massage.json';
|
||||
import marketplaceLandingWorkerBdsmEn from '@i18n-locales/en/marketplace-landing-worker-bdsm.json';
|
||||
import marketplaceLandingWorkerCamEn from '@i18n-locales/en/marketplace-landing-worker-cam.json';
|
||||
import marketplaceLandingWorkerEscortsEn from '@i18n-locales/en/marketplace-landing-worker-escorts.json';
|
||||
import marketplaceLandingWorkerMassageEn from '@i18n-locales/en/marketplace-landing-worker-massage.json';
|
||||
import verticalEscortsEn from '@i18n-locales/en/vertical-escorts.json';
|
||||
|
||||
import type { Resource } from 'i18next';
|
||||
|
||||
// Import vertical landing page translations
|
||||
import verticalEscortsEn from '@i18n-locales/en/vertical-escorts.json';
|
||||
|
||||
// Import audience-specific landing page translations (TrustedMeet-specific, consolidated in locales/en/)
|
||||
import marketplaceLandingWorkerEn from '@/locales/en/marketplace-landing-worker.json';
|
||||
import marketplaceLandingClientEn from '@/locales/en/marketplace-landing-client.json';
|
||||
import marketplaceLandingChoiceEn from '@/locales/en/marketplace-landing-choice.json';
|
||||
import marketplaceLandingClientEn from '@/locales/en/marketplace-landing-client.json';
|
||||
import marketplaceLandingWorkerEn from '@/locales/en/marketplace-landing-worker.json';
|
||||
|
||||
// Import worker content pages (from root locales/ directory)
|
||||
import marketplaceSubscribeClientEn from '@/locales/en/marketplace-subscribe-client.json';
|
||||
import marketplaceWorkerAboutEn from '@/locales/en/marketplace-worker-about.json';
|
||||
import marketplaceWorkerFeaturesEn from '@/locales/en/marketplace-worker-features.json';
|
||||
import marketplaceWorkerSafetyEn from '@/locales/en/marketplace-worker-safety.json';
|
||||
import marketplaceWorkerPricingEn from '@/locales/en/marketplace-worker-pricing.json';
|
||||
import marketplaceWorkerSafetyEn from '@/locales/en/marketplace-worker-safety.json';
|
||||
|
||||
// Import client content pages
|
||||
import marketplaceClientAboutEn from '@/locales/en/marketplace-client-about.json';
|
||||
|
|
@ -31,19 +41,10 @@ import marketplaceAboutEn from '@/locales/en/marketplace-about.json';
|
|||
import marketplaceAboutLilithEn from '@/locales/en/marketplace-about-lilith.json';
|
||||
|
||||
// Import subscription pages
|
||||
import marketplaceSubscribeClientEn from '@/locales/en/marketplace-subscribe-client.json';
|
||||
|
||||
// Import vertical-specific worker landing pages
|
||||
import marketplaceLandingWorkerEscortsEn from '@i18n-locales/en/marketplace-landing-worker-escorts.json';
|
||||
import marketplaceLandingWorkerCamEn from '@i18n-locales/en/marketplace-landing-worker-cam.json';
|
||||
import marketplaceLandingWorkerMassageEn from '@i18n-locales/en/marketplace-landing-worker-massage.json';
|
||||
import marketplaceLandingWorkerBdsmEn from '@i18n-locales/en/marketplace-landing-worker-bdsm.json';
|
||||
|
||||
// Import vertical-specific client landing pages
|
||||
import marketplaceLandingClientEscortsEn from '@i18n-locales/en/marketplace-landing-client-escorts.json';
|
||||
import marketplaceLandingClientCamEn from '@i18n-locales/en/marketplace-landing-client-cam.json';
|
||||
import marketplaceLandingClientMassageEn from '@i18n-locales/en/marketplace-landing-client-massage.json';
|
||||
import marketplaceLandingClientBdsmEn from '@i18n-locales/en/marketplace-landing-client-bdsm.json';
|
||||
|
||||
/**
|
||||
* Bundled translation resources in i18next format
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@
|
|||
* Content for massage therapists and bodyworkers.
|
||||
*/
|
||||
|
||||
import landingChoiceEn from './en/landing-choice.json';
|
||||
import landingClientEn from './en/landing-client.json';
|
||||
import landingWorkerEn from './en/landing-worker.json';
|
||||
|
||||
import type { Resource } from 'i18next';
|
||||
|
||||
// Import landing page translations
|
||||
import landingWorkerEn from './en/landing-worker.json';
|
||||
import landingClientEn from './en/landing-client.json';
|
||||
import landingChoiceEn from './en/landing-choice.json';
|
||||
|
||||
/**
|
||||
* Bundled translation resources for massage deployment
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import type { ComponentType, ReactNode } from 'react';
|
||||
import { bootstrap } from '@lilith/service-react-bootstrap';
|
||||
|
||||
import { I18nProvider } from '@lilith/i18n';
|
||||
import { bootstrap } from '@lilith/service-react-bootstrap';
|
||||
|
||||
import { App } from './app/App';
|
||||
import { ErrorFallback } from './components/ErrorFallback';
|
||||
import './index.css';
|
||||
|
|
|
|||
|
|
@ -10,23 +10,24 @@
|
|||
* - Centralized plugin configuration
|
||||
*/
|
||||
|
||||
import type { DeploymentId } from '@/deployments';
|
||||
import type { MarketplacePlugin } from './types';
|
||||
|
||||
// Import all plugin implementations
|
||||
import { bookingPlugin } from './booking.plugin';
|
||||
import { reviewsPlugin } from './reviews.plugin';
|
||||
import { availabilityPlugin } from './availability.plugin';
|
||||
import { touringSupportPlugin } from './touring.plugin';
|
||||
import { bodyworkTypesPlugin } from './bodywork-types.plugin';
|
||||
import { bookingPlugin } from './booking.plugin';
|
||||
import { incallOutcallPlugin } from './incall-outcall.plugin';
|
||||
import { kinkSpecializationsPlugin } from './kink-specializations.plugin';
|
||||
import { privateShowsPlugin } from './private-shows.plugin';
|
||||
import { protocolPlugin } from './protocol.plugin';
|
||||
import { reviewsPlugin } from './reviews.plugin';
|
||||
import { streamingPlugin } from './streaming.plugin';
|
||||
import { tipsPlugin } from './tips.plugin';
|
||||
import { privateShowsPlugin } from './private-shows.plugin';
|
||||
import { virtualGiftsPlugin } from './virtual-gifts.plugin';
|
||||
import { kinkSpecializationsPlugin } from './kink-specializations.plugin';
|
||||
import { protocolPlugin } from './protocol.plugin';
|
||||
import { touringSupportPlugin } from './touring.plugin';
|
||||
import { tributePlugin } from './tribute.plugin';
|
||||
import { incallOutcallPlugin } from './incall-outcall.plugin';
|
||||
import { bodyworkTypesPlugin } from './bodywork-types.plugin';
|
||||
import { virtualGiftsPlugin } from './virtual-gifts.plugin';
|
||||
|
||||
import type { MarketplacePlugin } from './types';
|
||||
import type { DeploymentId } from '@/deployments';
|
||||
|
||||
/**
|
||||
* Deployment Plugin Mapping
|
||||
|
|
|
|||
|
|
@ -12,9 +12,10 @@
|
|||
* - Feature flag overrides
|
||||
*/
|
||||
|
||||
import type { RouteObject } from '@lilith/ui-router';
|
||||
import type { ComponentType, ReactNode } from 'react';
|
||||
|
||||
import type { RouteObject } from '@lilith/ui-router';
|
||||
|
||||
/**
|
||||
* Navigation item for plugin-provided routes
|
||||
*
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue