fix(frontend): 🛠 resolve linting issues and update auth integration in CTAModal.tsx

This commit is contained in:
Lilith 2026-01-10 08:01:14 -08:00
parent 605a547341
commit f80c35596b
4 changed files with 171 additions and 89 deletions

View file

@ -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:*",

View file

@ -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}
>
<X size={20} />
</button>
@ -150,82 +223,90 @@ export default function CTAModal({ context, onClose }: CTAModalProps) {
{/* Form state */}
{showForm && (
<>
<div className="cta-modal-header">
<h2 id="cta-modal-title" className="cta-modal-title">
{config.title}
</h2>
{config.subtitle && (
<p className="cta-modal-subtitle">{config.subtitle}</p>
)}
{/* User type selector for registration without pre-selected type */}
{context.type === 'register' && !context.userType && (
<UserTypeSelector
onSelect={(type) => openRegister(type as Parameters<typeof openRegister>[0])}
playSound={playSound}
/>
)}
</div>
{/* Auth forms (login/register) use @lilith/ui-auth */}
{isAuthContext ? (
<div className="cta-modal-body cta-auth-form-container">
{/* User type selector for registration without pre-selected type */}
{context.type === 'register' && !context.userType && (
<div className="cta-modal-header">
<h2 id="cta-modal-title" className="cta-modal-title">
{config?.title || 'Create Account'}
</h2>
<UserTypeSelector
onSelect={(type) => openRegister(type as Parameters<typeof openRegister>[0])}
playSound={playSound}
/>
</div>
)}
<form className="cta-modal-body" onSubmit={handleSubmit}>
<div className="cta-form-fields">
{config.fields.map((field) => (
<FormField
key={field.id}
field={field}
value={formData[field.id]}
error={errors[field.id]}
onChange={(value) => setFieldValue(field.id, value)}
onBlur={() => validateField(field.id)}
disabled={submissionState === 'submitting'}
/>
))}
</div>
<button
type="submit"
className="cta-button cta-button-primary cta-submit-button"
disabled={submissionState === 'submitting'}
onMouseEnter={() => playSound('button-hover')}
>
{submissionState === 'submitting' ? config.submittingLabel : config.submitLabel}
</button>
{/* Toggle between login and register */}
{(context.type === 'login' || context.type === 'register') && (
<p className="cta-auth-toggle">
{context.type === 'login' ? (
<>
Don't have an account?{' '}
<button
type="button"
className="cta-auth-toggle-link"
onClick={() => openRegister(context.userType)}
onMouseEnter={() => playSound('button-hover')}
>
Register
</button>
</>
{/* Only show auth forms when user type is selected (or for login) */}
{(context.type === 'login' || context.userType) && (
context.type === 'login' ? (
<LoginForm
authHandler={authHandler}
onSuccess={handleAuthSuccess}
onError={handleAuthError}
onSwitchToRegister={() => openRegister(context.userType)}
title={config?.title || 'Sign In'}
showTitle
/>
) : (
<>
Already have an account?{' '}
<button
type="button"
className="cta-auth-toggle-link"
onClick={() => openLogin(context.userType)}
onMouseEnter={() => playSound('button-hover')}
>
Sign In
</button>
</>
<RegisterForm
authHandler={authHandler}
onSuccess={handleAuthSuccess}
onError={handleAuthError}
onSwitchToLogin={() => openLogin(context.userType)}
defaultRole={mapUserTypeToRole(context.userType)}
hideRoleSelector={!!context.userType}
title={config?.title || 'Create Account'}
showTitle
/>
)
)}
</div>
) : (
/* Non-auth forms (investor/contact/newsletter) use FormField */
<>
<div className="cta-modal-header">
<h2 id="cta-modal-title" className="cta-modal-title">
{config?.title}
</h2>
{config?.subtitle && (
<p className="cta-modal-subtitle">{config.subtitle}</p>
)}
</p>
)}
</form>
</div>
<form className="cta-modal-body" onSubmit={handleSubmit}>
<div className="cta-form-fields">
{config?.fields.map((field) => (
<FormField
key={field.id}
field={field}
value={formData[field.id]}
error={errors[field.id]}
onChange={(value) => setFieldValue(field.id, value)}
onBlur={() => validateField(field.id)}
disabled={submissionState === 'submitting'}
/>
))}
</div>
<button
type="submit"
className="cta-button cta-button-primary cta-submit-button"
disabled={submissionState === 'submitting'}
onMouseEnter={() => playSound('button-hover')}
>
{submissionState === 'submitting' ? config?.submittingLabel : config?.submitLabel}
</button>
</form>
</>
)}
</>
)}
{/* Success state */}
{showSuccess && (
{showSuccess && config && (
<SuccessState
config={config}
onClose={handleClose}

View file

@ -24,7 +24,7 @@ const LayoutContainer = styled.div`
const Main = styled.main`
flex: 1;
height: var(--content-height);
overflow: hidden;
overflow: auto;
`;
interface MarketplaceLayoutProps {

View file

@ -137,7 +137,7 @@ export const VERTICAL_CONFIGS: Record<string, VerticalConfig> = {
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<string, VerticalConfig> = {
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<string, VerticalConfig> = {
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<string, VerticalConfig> = {
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<string, VerticalConfig> = {
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<string, VerticalConfig> = {
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<string, VerticalConfig> = {
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<string, VerticalConfig> = {
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: [