dashboard fix
This commit is contained in:
@@ -7,19 +7,21 @@ import { useDashboardI18n } from '@/components/I18nProvider'
|
||||
import { apiFetch } from '@/lib/api'
|
||||
import PublicShell from '@/components/layout/PublicShell'
|
||||
import { marketplaceUrl } from '@/lib/urls'
|
||||
import { BilingualField, BilingualInput, bilingualPrimary, emptyBilingual } from '@/components/ui/BilingualInput'
|
||||
|
||||
type SignupForm = {
|
||||
firstName: string
|
||||
lastName: string
|
||||
firstName: BilingualField
|
||||
lastName: BilingualField
|
||||
email: string
|
||||
password: string
|
||||
confirmPassword: string
|
||||
companyName: string
|
||||
preferredLanguage: 'en' | 'fr' | 'ar' | ''
|
||||
companyName: BilingualField
|
||||
legalForm: string
|
||||
registrationNumber: string
|
||||
streetAddress: string
|
||||
city: string
|
||||
country: string
|
||||
streetAddress: BilingualField
|
||||
city: BilingualField
|
||||
country: BilingualField
|
||||
zipCode: string
|
||||
companyPhone: string
|
||||
fax: string
|
||||
@@ -53,7 +55,7 @@ const LEGAL_FORM_OPTIONS = ['SARL', 'SARL AU', 'SA', 'SAS', 'AUTO_ENTREPRENEUR',
|
||||
const YEARS_ACTIVE_OPTIONS = ['LESS_THAN_1', 'ONE_TO_FIVE', 'MORE_THAN_5'] as const
|
||||
|
||||
export default function SignUpPage() {
|
||||
const { language } = useDashboardI18n()
|
||||
const { language, setLanguage } = useDashboardI18n()
|
||||
const searchParams = useSearchParams()
|
||||
const initialPlan = normalizePlan(searchParams.get('plan'))
|
||||
const initialBillingPeriod = normalizeBillingPeriod(searchParams.get('billing'))
|
||||
@@ -63,17 +65,18 @@ export default function SignUpPage() {
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [completedSignup, setCompletedSignup] = useState<CompletedSignup | null>(null)
|
||||
const [form, setForm] = useState<SignupForm>({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
firstName: emptyBilingual(),
|
||||
lastName: emptyBilingual(),
|
||||
email: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
companyName: '',
|
||||
preferredLanguage: '',
|
||||
companyName: emptyBilingual(),
|
||||
legalForm: '',
|
||||
registrationNumber: '',
|
||||
streetAddress: '',
|
||||
city: '',
|
||||
country: '',
|
||||
streetAddress: emptyBilingual(),
|
||||
city: emptyBilingual(),
|
||||
country: emptyBilingual(),
|
||||
zipCode: '',
|
||||
companyPhone: '',
|
||||
fax: '',
|
||||
@@ -130,6 +133,9 @@ export default function SignUpPage() {
|
||||
invalidPassword: 'Choose a password with at least 8 characters.',
|
||||
passwordMismatch: 'Passwords do not match.',
|
||||
missingRequired: 'Fill in all required fields.',
|
||||
missingLanguage: 'Please select your preferred language.',
|
||||
preferredLanguageLabel: 'Preferred language',
|
||||
languageOptions: { en: 'English', fr: 'Français', ar: 'العربية' } as Record<string, string>,
|
||||
companyFallback: 'Your company',
|
||||
couldNotCreate: 'Could not create workspace',
|
||||
legalFormOptions: {
|
||||
@@ -206,6 +212,9 @@ export default function SignUpPage() {
|
||||
invalidPassword: 'Choisissez un mot de passe d’au moins 8 caractères.',
|
||||
passwordMismatch: 'Les mots de passe ne correspondent pas.',
|
||||
missingRequired: 'Renseignez tous les champs obligatoires.',
|
||||
missingLanguage: 'Veuillez sélectionner votre langue préférée.',
|
||||
preferredLanguageLabel: 'Langue préférée',
|
||||
languageOptions: { en: 'English', fr: 'Français', ar: 'العربية' } as Record<string, string>,
|
||||
companyFallback: 'Votre entreprise',
|
||||
couldNotCreate: 'Impossible de créer l’espace',
|
||||
legalFormOptions: {
|
||||
@@ -282,6 +291,9 @@ export default function SignUpPage() {
|
||||
invalidPassword: 'اختر كلمة مرور من 8 أحرف على الأقل.',
|
||||
passwordMismatch: 'كلمتا المرور غير متطابقتين.',
|
||||
missingRequired: 'أكمل جميع الحقول الإلزامية.',
|
||||
missingLanguage: 'يرجى اختيار اللغة المفضلة.',
|
||||
preferredLanguageLabel: 'اللغة المفضلة',
|
||||
languageOptions: { en: 'English', fr: 'Français', ar: 'العربية' } as Record<string, string>,
|
||||
companyFallback: 'شركتك',
|
||||
couldNotCreate: 'تعذر إنشاء المساحة',
|
||||
legalFormOptions: {
|
||||
@@ -316,6 +328,15 @@ export default function SignUpPage() {
|
||||
},
|
||||
}[language]
|
||||
|
||||
function handleStep1Continue() {
|
||||
if (!form.preferredLanguage) {
|
||||
setError(dict.missingLanguage)
|
||||
return
|
||||
}
|
||||
setError(null)
|
||||
setStep(2)
|
||||
}
|
||||
|
||||
async function submitSignup() {
|
||||
if (form.password.length < 8) {
|
||||
setError(dict.invalidPassword)
|
||||
@@ -339,16 +360,23 @@ export default function SignUpPage() {
|
||||
const response = await apiFetch<SignupResponse>('/auth/company/signup', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
firstName: form.firstName,
|
||||
lastName: form.lastName,
|
||||
firstName: form.firstName.fr || form.firstName.ar,
|
||||
firstNameAr: form.firstName.ar || undefined,
|
||||
lastName: form.lastName.fr || form.lastName.ar,
|
||||
lastNameAr: form.lastName.ar || undefined,
|
||||
email: form.email,
|
||||
password: form.password,
|
||||
companyName: form.companyName,
|
||||
preferredLanguage: form.preferredLanguage || 'en',
|
||||
companyName: form.companyName.fr || form.companyName.ar,
|
||||
companyNameAr: form.companyName.ar || undefined,
|
||||
legalForm: form.legalForm,
|
||||
registrationNumber: form.registrationNumber,
|
||||
streetAddress: form.streetAddress,
|
||||
city: form.city,
|
||||
country: form.country,
|
||||
streetAddress: form.streetAddress.fr || form.streetAddress.ar,
|
||||
streetAddressAr: form.streetAddress.ar || undefined,
|
||||
city: form.city.fr || form.city.ar,
|
||||
cityAr: form.city.ar || undefined,
|
||||
country: form.country.fr || form.country.ar,
|
||||
countryAr: form.country.ar || undefined,
|
||||
zipCode: form.zipCode,
|
||||
companyPhone: form.companyPhone,
|
||||
fax: form.fax || undefined,
|
||||
@@ -361,7 +389,7 @@ export default function SignUpPage() {
|
||||
})
|
||||
|
||||
setCompletedSignup({
|
||||
companyName: form.companyName,
|
||||
companyName: form.companyName.fr || form.companyName.ar,
|
||||
email: form.email,
|
||||
emailWarning: getEmailWarning(response.emailDelivery, language),
|
||||
})
|
||||
@@ -426,18 +454,40 @@ export default function SignUpPage() {
|
||||
{step === 1 ? (
|
||||
<div className="space-y-5">
|
||||
<SectionIntro title={dict.ownerTitle} body={dict.ownerBody} />
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<LabeledInput label={dict.firstName} required maxLength={80} value={form.firstName} onChange={(value) => setForm((current) => ({ ...current, firstName: normalizeTitleCase(value) }))} />
|
||||
<LabeledInput label={dict.lastName} required maxLength={80} value={form.lastName} onChange={(value) => setForm((current) => ({ ...current, lastName: normalizeTitleCase(value) }))} />
|
||||
</div>
|
||||
<BilingualInput label={dict.firstName} required maxLength={80} value={form.firstName} onChange={(value) => setForm((current) => ({ ...current, firstName: value }))} />
|
||||
<BilingualInput label={dict.lastName} required maxLength={80} value={form.lastName} onChange={(current) => setForm((f) => ({ ...f, lastName: current }))} />
|
||||
<LabeledInput label={dict.ownerEmail} required type="email" maxLength={254} value={form.email} onChange={(value) => setForm((current) => ({ ...current, email: normalizeEmail(value) }))} />
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<LabeledInput label={dict.password} required type="password" maxLength={128} value={form.password} onChange={(value) => setForm((current) => ({ ...current, password: value }))} />
|
||||
<LabeledInput label={dict.confirmPassword} required type="password" maxLength={128} value={form.confirmPassword} onChange={(value) => setForm((current) => ({ ...current, confirmPassword: value }))} />
|
||||
</div>
|
||||
<p className="text-xs text-slate-500">{dict.passwordHint}</p>
|
||||
<div>
|
||||
<span className="mb-1.5 block text-sm font-medium text-slate-700">
|
||||
{dict.preferredLanguageLabel} <span className="text-red-600">*</span>
|
||||
</span>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{(['en', 'fr', 'ar'] as const).map((lang) => (
|
||||
<button
|
||||
key={lang}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setForm((current) => ({ ...current, preferredLanguage: lang }))
|
||||
setLanguage(lang)
|
||||
}}
|
||||
className={`rounded-2xl border py-3 text-sm font-semibold transition ${
|
||||
form.preferredLanguage === lang
|
||||
? 'border-slate-900 bg-slate-900 text-white'
|
||||
: 'border-slate-200 bg-white text-slate-600 hover:border-slate-300 hover:bg-slate-50'
|
||||
}`}
|
||||
>
|
||||
{dict.languageOptions[lang]}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<button type="button" onClick={() => setStep(2)} className="btn-primary">{dict.continue}</button>
|
||||
<button type="button" onClick={handleStep1Continue} className="btn-primary">{dict.continue}</button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -446,21 +496,19 @@ export default function SignUpPage() {
|
||||
<div className="space-y-5">
|
||||
<SectionIntro title={dict.partnerTitle} body={dict.partnerBody} />
|
||||
<div className="space-y-4">
|
||||
<LabeledInput label={dict.companyName} required maxLength={120} value={form.companyName} onChange={(value) => setForm((current) => ({ ...current, companyName: normalizeTitleCase(value) }))} />
|
||||
<BilingualInput label={dict.companyName} required maxLength={120} value={form.companyName} onChange={(value) => setForm((current) => ({ ...current, companyName: value }))} />
|
||||
<LabeledSelect label={dict.legalForm} required value={form.legalForm} options={['', ...LEGAL_FORM_OPTIONS]} labels={dict.legalFormOptions} onChange={(value) => setForm((current) => ({ ...current, legalForm: value }))} />
|
||||
<LabeledInput label={dict.registrationNumber} required maxLength={120} value={form.registrationNumber} onChange={(value) => setForm((current) => ({ ...current, registrationNumber: normalizeTitleCase(value) }))} />
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<LabeledInput label={dict.companyPhone} required maxLength={80} value={form.companyPhone} onChange={(value) => setForm((current) => ({ ...current, companyPhone: value }))} />
|
||||
<LabeledInput label={dict.fax} maxLength={80} value={form.fax} onChange={(value) => setForm((current) => ({ ...current, fax: value }))} />
|
||||
</div>
|
||||
<BilingualInput label={dict.streetAddress} required maxLength={200} value={form.streetAddress} onChange={(value) => setForm((current) => ({ ...current, streetAddress: value }))} />
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<LabeledInput label={dict.streetAddress} required maxLength={200} value={form.streetAddress} onChange={(value) => setForm((current) => ({ ...current, streetAddress: normalizeUpper(value) }))} />
|
||||
<LabeledInput label={dict.city} required maxLength={120} value={form.city} onChange={(value) => setForm((current) => ({ ...current, city: normalizeUpper(value) }))} />
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<LabeledInput label={dict.country} required maxLength={120} value={form.country} onChange={(value) => setForm((current) => ({ ...current, country: normalizeUpper(value) }))} />
|
||||
<LabeledInput label={dict.zipCode} required maxLength={40} value={form.zipCode} onChange={(value) => setForm((current) => ({ ...current, zipCode: value.toUpperCase() }))} />
|
||||
<BilingualInput label={dict.city} required maxLength={120} value={form.city} onChange={(value) => setForm((current) => ({ ...current, city: value }))} />
|
||||
<BilingualInput label={dict.country} required maxLength={120} value={form.country} onChange={(value) => setForm((current) => ({ ...current, country: value }))} />
|
||||
</div>
|
||||
<LabeledInput label={dict.zipCode} required maxLength={40} value={form.zipCode} onChange={(value) => setForm((current) => ({ ...current, zipCode: value.toUpperCase() }))} />
|
||||
<LabeledSelect label={dict.yearsActive} required value={form.yearsActive} options={['', ...YEARS_ACTIVE_OPTIONS]} labels={dict.yearsActiveOptions} onChange={(value) => setForm((current) => ({ ...current, yearsActive: value }))} />
|
||||
</div>
|
||||
<NavActions onBack={() => setStep(1)} onNext={() => setStep(3)} nextLabel={dict.continue} backLabel={dict.back} loadingLabel={dict.working} />
|
||||
@@ -496,7 +544,7 @@ export default function SignUpPage() {
|
||||
<div className="space-y-5">
|
||||
<SectionIntro title={dict.reviewTitle} body={dict.reviewBody} />
|
||||
<div className="rounded-3xl border border-slate-200 bg-slate-50 p-5 text-sm text-slate-600">
|
||||
<p><span className="font-semibold text-slate-900">{dict.reviewWorkspace}:</span> {form.companyName || dict.companyFallback}</p>
|
||||
<p><span className="font-semibold text-slate-900">{dict.reviewWorkspace}:</span> {form.companyName.fr || form.companyName.ar || dict.companyFallback}{form.companyName.fr && form.companyName.ar ? <span className="ml-2 text-slate-400">/ {form.companyName.ar}</span> : null}</p>
|
||||
<p className="mt-2"><span className="font-semibold text-slate-900">{dict.reviewPlan}:</span> {form.plan} · {dict.billingPeriodOptions[form.billingPeriod]} · {form.currency}</p>
|
||||
<p className="mt-2"><span className="font-semibold text-slate-900">{dict.reviewOwnerEmail}:</span> {form.email || '—'}</p>
|
||||
<p className="mt-2"><span className="font-semibold text-slate-900">{dict.reviewPhone}:</span> {form.companyPhone || '—'}</p>
|
||||
@@ -519,17 +567,15 @@ export default function SignUpPage() {
|
||||
}
|
||||
|
||||
function hasRequiredPartnerFields(form: SignupForm) {
|
||||
return [
|
||||
form.companyName,
|
||||
form.legalForm,
|
||||
form.registrationNumber,
|
||||
form.streetAddress,
|
||||
form.city,
|
||||
form.country,
|
||||
form.zipCode,
|
||||
form.companyPhone,
|
||||
form.yearsActive,
|
||||
].every((value) => value.trim().length > 0)
|
||||
const bilingualFilled = (f: BilingualField) => f.fr.trim().length > 0 || f.ar.trim().length > 0
|
||||
return (
|
||||
bilingualFilled(form.companyName) &&
|
||||
bilingualFilled(form.streetAddress) &&
|
||||
bilingualFilled(form.city) &&
|
||||
bilingualFilled(form.country) &&
|
||||
[form.legalForm, form.registrationNumber, form.zipCode, form.companyPhone, form.yearsActive]
|
||||
.every((v) => v.trim().length > 0)
|
||||
)
|
||||
}
|
||||
|
||||
function SectionIntro({ title, body }: { title: string; body: string }) {
|
||||
@@ -600,6 +646,7 @@ function LabeledInput({
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
function LabeledSelect({
|
||||
label,
|
||||
value,
|
||||
|
||||
Reference in New Issue
Block a user