import { useEffect, useState, type FormEvent } from 'react' import { Link, useSearchParams } from 'react-router-dom' import { Helmet } from 'react-helmet-async' import { PublicLayout } from '../../layouts/PublicLayout' import { TEMPLATE_LIMIT, usePageContext, usePreviewUrl } from '../../shared/pageContext' import type { SimulationTemplateOption } from '../../services/api/contracts' import { createPublicSimulation, listPublicSimulationTemplates, uploadMedia, } from '../../services/api/simulations' import { DEFAULT_USER_AVATAR_URL } from '../../shared/uiAssets' import { resolveLocalizedTemplateName } from '../../shared/demoContent' export const CUSTOMER_SIMULATION_CODE_KEY = 'aitori-customer-simulation-code' export function CustomerSimulationPage() { const { locale, navigate, path, copy } = usePageContext() const [searchParams] = useSearchParams() const pageCopy = copy.customerSimulationFlow const [code, setCode] = useState(() => (searchParams.get('code') || '').trim().toUpperCase()) const [codeChecked, setCodeChecked] = useState(false) const [stylistName, setStylistName] = useState('Aitori Stylist') const [file, setFile] = useState(null) const [source, setSource] = useState<'stylist' | 'system'>('stylist') const [stylistTemplates, setStylistTemplates] = useState([]) const [systemTemplates, setSystemTemplates] = useState([]) const [selectedTemplates, setSelectedTemplates] = useState([]) const [applying, setApplying] = useState(false) const [submitting, setSubmitting] = useState(false) const [error, setError] = useState('') const previewUrl = usePreviewUrl(file, DEFAULT_USER_AVATAR_URL) const templates = source === 'stylist' ? stylistTemplates : systemTemplates const toggleTemplate = (templateId: string) => { setSelectedTemplates((current) => current.includes(templateId) ? current.filter((item) => item !== templateId) : current.length >= TEMPLATE_LIMIT ? current : [...current, templateId]) } const handleApplyCode = async (nextCode = code) => { const normalizedCode = nextCode.trim().toUpperCase() if (normalizedCode.length !== 8) { setError(pageCopy.errors.codeLength) return } setApplying(true) setError('') try { const data = await listPublicSimulationTemplates(normalizedCode) setCode(normalizedCode) setCodeChecked(true) setStylistName(data.stylist.name) setStylistTemplates(data.stylistTemplates.map((template) => ({ ...template, templateName: resolveLocalizedTemplateName(template.templateName, template.templateNameI18n, locale), }))) setSystemTemplates(data.systemTemplates.map((template) => ({ ...template, templateName: resolveLocalizedTemplateName(template.templateName, template.templateNameI18n, locale, copy.systemTemplateNames), }))) setSource('stylist') setSelectedTemplates([]) window.sessionStorage.setItem(CUSTOMER_SIMULATION_CODE_KEY, normalizedCode) } catch (err) { setCodeChecked(false) setStylistName('Aitori Stylist') setStylistTemplates([]) setSystemTemplates([]) setSelectedTemplates([]) setError(err instanceof Error ? err.message : pageCopy.errors.invalidCode) } finally { setApplying(false) } } useEffect(() => { const initialCode = (searchParams.get('code') || '').trim().toUpperCase() if (initialCode.length === 8) { void handleApplyCode(initialCode) } // Apply invite code from the public profile once on mount. // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const handleSubmit = async (event: FormEvent) => { event.preventDefault() if (!codeChecked) return setError(pageCopy.errors.applyCode) if (!file) return setError(pageCopy.errors.photo) if (!selectedTemplates.length) return setError(pageCopy.errors.template) setSubmitting(true) setError('') try { const uploaded = await uploadMedia(file, 'customer-simulation-origin-image') const result = await createPublicSimulation({ inviteCode: code, originImage: uploaded.url, templateIds: selectedTemplates, }) window.sessionStorage.setItem(CUSTOMER_SIMULATION_CODE_KEY, code) navigate(path(`/customer-simulation/result/${result.simulationId}`)) } catch (err) { setError(err instanceof Error ? err.message : pageCopy.errors.submit) } finally { setSubmitting(false) } } return ( {`Aitori Stylist | ${pageCopy.title}`}
{pageCopy.back}
{pageCopy.badge}

{pageCopy.title}

{pageCopy.lead}

1
{pageCopy.step1.kicker}

{pageCopy.step1.title}

{pageCopy.step1.cardTitle}{pageCopy.step1.cardBody}
{ setCode(event.target.value.toUpperCase().replace(/[^A-Z0-9]/g, '')); setCodeChecked(false); setStylistName('Aitori Stylist'); setStylistTemplates([]); setSystemTemplates([]); setSelectedTemplates([]) }} required />
{pageCopy.step1.noCode}{pageCopy.step1.contact}
2

{pageCopy.step2.title}

3

{pageCopy.step3.title}

{codeChecked ? {pageCopy.step3.stylist.replace('{{name}}', stylistName)} : null}
{codeChecked ? <>
{pageCopy.step3.selected} {selectedTemplates.length}
{templates.length ?
{templates.map((template) => { const active = selectedTemplates.includes(template.id); return })}
:

{pageCopy.step3.empty}

} :
{pageCopy.step3.codeRequired}
}
{pageCopy.action.title}{pageCopy.action.body}{error ? {error} : null}
{copy.common.cancel}
) }