From f80c35596bd0e465fc9be9d9bf45be596389afeb Mon Sep 17 00:00:00 2001 From: Lilith Date: Sat, 10 Jan 2026 08:01:14 -0800 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20=F0=9F=9B=A0=20resolve=20linti?= =?UTF-8?q?ng=20issues=20and=20update=20auth=20integration=20in=20CTAModal?= =?UTF-8?q?.tsx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- features/landing/frontend-public/package.json | 1 + .../src/components/CTAModal/CTAModal.tsx | 241 ++++++++++++------ .../src/layouts/MarketplaceLayout.tsx | 2 +- .../shared/src/verticals.config.ts | 16 +- 4 files changed, 171 insertions(+), 89 deletions(-) diff --git a/features/landing/frontend-public/package.json b/features/landing/frontend-public/package.json index e44aa229f..4fcf74c6d 100644 --- a/features/landing/frontend-public/package.json +++ b/features/landing/frontend-public/package.json @@ -37,6 +37,7 @@ "@lilith/analytics-client": "workspace:*", "@lilith/api-client": "workspace:*", "@lilith/auth-provider": "workspace:*", + "@lilith/ui-auth": "^1.1.0", "@lilith/design-tokens": "workspace:*", "@lilith/i18n": "workspace:*", "@lilith/image-security": "workspace:*", diff --git a/features/landing/frontend-public/src/components/CTAModal/CTAModal.tsx b/features/landing/frontend-public/src/components/CTAModal/CTAModal.tsx index c31e186be..344f6e44c 100644 --- a/features/landing/frontend-public/src/components/CTAModal/CTAModal.tsx +++ b/features/landing/frontend-public/src/components/CTAModal/CTAModal.tsx @@ -9,11 +9,18 @@ * - Sub-components in ./components/ (FormField, SuccessState, etc.) * - Business logic in ./hooks/ (useCTAModal, useModalRouting, useModalBehavior) * - Configuration in ./contexts/ (form configs, themes) + * + * Auth Integration: + * - Login/Register forms use @lilith/ui-auth components + * - Connected to @lilith/auth-provider for actual authentication + * - Investor/Contact/Newsletter use custom FormField implementation */ import { m, AnimatePresence } from 'framer-motion' import { X } from 'lucide-react' import { useState, useMemo } from 'react' +import { useAuth } from '@lilith/auth-provider' +import { LoginForm, RegisterForm, type AuthHandler, type User, type UserRole } from '@lilith/ui-auth' import { useCTAModal } from './hooks/useCTAModal' import { useModalRouting } from './hooks/useModalRouting' @@ -33,9 +40,31 @@ import { getNewsletterConfig, } from './contexts' import type { CTAContext, FormConfig } from './types' +import type { UserType } from '@lilith/i18n' import './CTAModal.css' +/** + * Map landing UserType to auth-provider role + * Landing has: creator, fan, client, provider, investor + * Auth has: user, provider, client + */ +function mapUserTypeToRole(userType?: UserType): UserRole | undefined { + if (!userType) return undefined + switch (userType) { + case 'creator': + case 'fan': + case 'investor': + return 'user' + case 'client': + return 'client' + case 'provider': + return 'provider' + default: + return 'user' + } +} + interface CTAModalProps { context: CTAContext onClose: () => void @@ -47,13 +76,34 @@ interface CTAModalProps { export default function CTAModal({ context, onClose }: CTAModalProps) { const { openLogin, openRegister } = useModalRouting() - // Get form configuration based on context - const config = useMemo((): FormConfig => { + // Auth provider integration for login/register forms + const { + loginWithCredentials, + registerWithCredentials, + isLoading: authLoading, + user: authUser, + } = useAuth() + + // Create authHandler for @lilith/ui-auth components + const authHandler: AuthHandler = useMemo(() => ({ + loginWithCredentials, + registerWithCredentials, + isLoading: authLoading, + user: authUser, + }), [loginWithCredentials, registerWithCredentials, authLoading, authUser]) + + // Check if this is an auth context (login or register) + const isAuthContext = context.type === 'login' || context.type === 'register' + + // Get form configuration based on context (only needed for non-auth forms) + const config = useMemo((): FormConfig | null => { switch (context.type) { case 'register': - return getRegistrationConfig(context.userType) case 'login': - return getLoginConfig(context.userType) + // Auth forms use @lilith/ui-auth - still need config for theming + return context.type === 'register' + ? getRegistrationConfig(context.userType) + : getLoginConfig(context.userType) case 'investor': return getInvestorConfig() case 'contact': @@ -66,7 +116,7 @@ export default function CTAModal({ context, onClose }: CTAModalProps) { } }, [context]) - // Form state management + // Form state management - only used for non-auth contexts const { formData, errors, @@ -74,20 +124,28 @@ export default function CTAModal({ context, onClose }: CTAModalProps) { setFieldValue, validateField, handleSubmit, - } = useCTAModal({ config }) + } = useCTAModal({ config: isAuthContext ? undefined : (config ?? undefined) }) + + // Auth-specific success state + const [authSuccess, setAuthSuccess] = useState(false) // Feature waitlist step state const [showFeatureStep, setShowFeatureStep] = useState(false) const [featureStepComplete, setFeatureStepComplete] = useState(false) + // Combined submission state for auth and non-auth contexts + const isSubmitting = isAuthContext ? authLoading : submissionState === 'submitting' + const isSuccess = isAuthContext ? authSuccess : submissionState === 'success' + // Modal behavior (focus, keyboard, animations) const { modalRef, playSound } = useModalBehavior({ onClose, - isSubmitting: submissionState === 'submitting', + isSubmitting, }) // Get theme CSS variables const themeVars = useMemo(() => { + if (!config) return {} as React.CSSProperties const { theme } = config return { '--modal-primary': theme.primary, @@ -102,14 +160,29 @@ export default function CTAModal({ context, onClose }: CTAModalProps) { } const handleOverlayClick = (e: React.MouseEvent) => { - if (e.target === e.currentTarget && submissionState !== 'submitting') { + if (e.target === e.currentTarget && !isSubmitting) { handleClose() } } + // Handle auth success from @lilith/ui-auth forms + const handleAuthSuccess = (_user: User, _sessionId: string) => { + setAuthSuccess(true) + // Check if we should show feature waitlist + if (config?.showFeatureWaitlist) { + setShowFeatureStep(true) + } + } + + // Handle auth error + const handleAuthError = (error: Error) => { + console.error('Auth error:', error) + // Errors are handled within the form components themselves + } + // Determine what to show - const showForm = submissionState !== 'success' && !showFeatureStep - const showSuccess = submissionState === 'success' && !showFeatureStep && !featureStepComplete + const showForm = !isSuccess && !showFeatureStep + const showSuccess = isSuccess && !showFeatureStep && !featureStepComplete const showFeatures = showFeatureStep && !featureStepComplete const showFinalSuccess = featureStepComplete @@ -142,7 +215,7 @@ export default function CTAModal({ context, onClose }: CTAModalProps) { onClick={handleClose} onMouseEnter={() => playSound('button-hover')} aria-label="Close modal" - disabled={submissionState === 'submitting'} + disabled={isSubmitting} > @@ -150,82 +223,90 @@ export default function CTAModal({ context, onClose }: CTAModalProps) { {/* Form state */} {showForm && ( <> -
-

- {config.title} -

- {config.subtitle && ( -

{config.subtitle}

- )} - {/* User type selector for registration without pre-selected type */} - {context.type === 'register' && !context.userType && ( - openRegister(type as Parameters[0])} - playSound={playSound} - /> - )} -
+ {/* Auth forms (login/register) use @lilith/ui-auth */} + {isAuthContext ? ( +
+ {/* User type selector for registration without pre-selected type */} + {context.type === 'register' && !context.userType && ( +
+

+ {config?.title || 'Create Account'} +

+ openRegister(type as Parameters[0])} + playSound={playSound} + /> +
+ )} -
-
- {config.fields.map((field) => ( - setFieldValue(field.id, value)} - onBlur={() => validateField(field.id)} - disabled={submissionState === 'submitting'} - /> - ))} -
- - - - {/* Toggle between login and register */} - {(context.type === 'login' || context.type === 'register') && ( -

- {context.type === 'login' ? ( - <> - Don't have an account?{' '} - - + {/* Only show auth forms when user type is selected (or for login) */} + {(context.type === 'login' || context.userType) && ( + context.type === 'login' ? ( + openRegister(context.userType)} + title={config?.title || 'Sign In'} + showTitle + /> ) : ( - <> - Already have an account?{' '} - - + openLogin(context.userType)} + defaultRole={mapUserTypeToRole(context.userType)} + hideRoleSelector={!!context.userType} + title={config?.title || 'Create Account'} + showTitle + /> + ) + )} +

+ ) : ( + /* Non-auth forms (investor/contact/newsletter) use FormField */ + <> +
+

+ {config?.title} +

+ {config?.subtitle && ( +

{config.subtitle}

)} -

- )} - +
+ +
+
+ {config?.fields.map((field) => ( + setFieldValue(field.id, value)} + onBlur={() => validateField(field.id)} + disabled={submissionState === 'submitting'} + /> + ))} +
+ + +
+ + )} )} {/* Success state */} - {showSuccess && ( + {showSuccess && config && ( = { guestNavigation: [ { label: 'Browse', path: '/browse' }, { label: 'About', path: '/about' }, - { label: 'About AtLilith', path: 'https://atlilith.com', external: true }, + { label: 'About Lilith', path: 'https://atlilith.com', external: true }, { label: 'Features', path: '/features' }, ], // Navigation for authenticated users @@ -237,7 +237,7 @@ export const VERTICAL_CONFIGS: Record = { guestNavigation: [ { label: 'Live Now', path: '/live' }, { label: 'About', path: '/about' }, - { label: 'About AtLilith', path: 'https://atlilith.com', external: true }, + { label: 'About Lilith', path: 'https://atlilith.com', external: true }, { label: 'Features', path: '/features' }, ], authNavigation: [ @@ -333,7 +333,7 @@ export const VERTICAL_CONFIGS: Record = { guestNavigation: [ { label: 'Browse', path: '/browse' }, { label: 'About', path: '/about' }, - { label: 'About AtLilith', path: 'https://atlilith.com', external: true }, + { label: 'About Lilith', path: 'https://atlilith.com', external: true }, { label: 'Features', path: '/features' }, ], authNavigation: [ @@ -426,7 +426,7 @@ export const VERTICAL_CONFIGS: Record = { guestNavigation: [ { label: 'Browse', path: '/browse' }, { label: 'About', path: '/about' }, - { label: 'About AtLilith', path: 'https://atlilith.com', external: true }, + { label: 'About Lilith', path: 'https://atlilith.com', external: true }, { label: 'Features', path: '/features' }, ], authNavigation: [ @@ -521,7 +521,7 @@ export const VERTICAL_CONFIGS: Record = { guestNavigation: [ { label: 'Browse', path: '/browse' }, { label: 'About', path: '/about' }, - { label: 'About AtLilith', path: 'https://atlilith.com', external: true }, + { label: 'About Lilith', path: 'https://atlilith.com', external: true }, { label: 'Features', path: '/features' }, ], authNavigation: [ @@ -617,7 +617,7 @@ export const VERTICAL_CONFIGS: Record = { guestNavigation: [ { label: 'Browse', path: '/browse' }, { label: 'About', path: '/about' }, - { label: 'About AtLilith', path: 'https://atlilith.com', external: true }, + { label: 'About Lilith', path: 'https://atlilith.com', external: true }, { label: 'Features', path: '/features' }, ], authNavigation: [ @@ -710,7 +710,7 @@ export const VERTICAL_CONFIGS: Record = { guestNavigation: [ { label: 'Browse', path: '/browse' }, { label: 'About', path: '/about' }, - { label: 'About AtLilith', path: 'https://atlilith.com', external: true }, + { label: 'About Lilith', path: 'https://atlilith.com', external: true }, { label: 'Features', path: '/features' }, ], authNavigation: [ @@ -798,7 +798,7 @@ export const VERTICAL_CONFIGS: Record = { guestNavigation: [ { label: 'Get Verified', path: '/verify' }, { label: 'About', path: '/about' }, - { label: 'About AtLilith', path: 'https://atlilith.com', external: true }, + { label: 'About Lilith', path: 'https://atlilith.com', external: true }, { label: 'Pricing', path: '/pricing' }, ], authNavigation: [