fix(frontend): 🛠 resolve linting issues and update auth integration in CTAModal.tsx
This commit is contained in:
parent
605a547341
commit
f80c35596b
4 changed files with 171 additions and 89 deletions
|
|
@ -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:*",
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ const LayoutContainer = styled.div`
|
|||
const Main = styled.main`
|
||||
flex: 1;
|
||||
height: var(--content-height);
|
||||
overflow: hidden;
|
||||
overflow: auto;
|
||||
`;
|
||||
|
||||
interface MarketplaceLayoutProps {
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue