From 743d38377300061beb07e0c550193a18b7ade44e Mon Sep 17 00:00:00 2001 From: Joao Date: Sun, 17 May 2026 14:04:43 -0500 Subject: [PATCH 1/7] =?UTF-8?q?feat(auth):=20single-step=20signup=20?= =?UTF-8?q?=E2=80=94=20drop=20fields=20the=20backend=20can't=20persist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signup was a two-step form: Step 1 (name + email + password) → Step 2 (organization, role select, avatar). But the submit handler only shipped {email, password, display_name}; organization, role, and avatar were silently dropped, and the backend User model has no fields for them today. Reduce to single-step (name + email + password). Removes the unused RoleOptionKey type and ROLE_OPTION_KEYS export from agents.ts. If these fields are needed later we can add them back together with the backend schema, instead of asking users to fill them into the void. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lib/agents.ts | 17 --- src/pages/Signup.tsx | 314 ++++++++++--------------------------------- 2 files changed, 69 insertions(+), 262 deletions(-) diff --git a/src/lib/agents.ts b/src/lib/agents.ts index 6d1a516..6531576 100644 --- a/src/lib/agents.ts +++ b/src/lib/agents.ts @@ -85,20 +85,3 @@ export const AGENTS: Agent[] = [ export const AGENT_BY_ID: Record = Object.fromEntries( AGENTS.map((a) => [a.id, a]), ) as Record; - -export type RoleOptionKey = - | 'mothertongue' - | 'facilitator' - | 'advisor' - | 'consultant' - | 'administrator' - | 'other'; - -export const ROLE_OPTION_KEYS: RoleOptionKey[] = [ - 'mothertongue', - 'facilitator', - 'advisor', - 'consultant', - 'administrator', - 'other', -]; diff --git a/src/pages/Signup.tsx b/src/pages/Signup.tsx index 99b28b2..c144e91 100644 --- a/src/pages/Signup.tsx +++ b/src/pages/Signup.tsx @@ -1,10 +1,8 @@ import { useState, type FormEvent } from 'react'; import { useTranslation } from 'react-i18next'; import { Link, useLocation } from 'wouter'; -import { Icon } from '../components/Icon'; -import { Alert, Button, Input, Select } from '../components/primitives'; +import { Alert, Button, Input } from '../components/primitives'; import { AuthShell } from '../components/shells'; -import { ROLE_OPTION_KEYS, type RoleOptionKey } from '../lib/agents'; import { useToast } from '../lib/hooks/useToast'; import { useAuthStore } from '../lib/stores/authStore'; @@ -14,8 +12,6 @@ interface SignupForm { email: string; password: string; confirmPassword: string; - organization: string; - role: RoleOptionKey; } const initialForm = (): SignupForm => ({ @@ -24,21 +20,22 @@ const initialForm = (): SignupForm => ({ email: '', password: '', confirmPassword: '', - organization: '', - role: ROLE_OPTION_KEYS[0], }); export default function Signup() { const { t } = useTranslation(); const [, navigate] = useLocation(); - const [step, setStep] = useState<1 | 2>(1); const [form, setForm] = useState(initialForm); const [error, setError] = useState(null); const [submitting, setSubmitting] = useState(false); const toast = useToast(); const signup = useAuthStore((s) => s.signup); - const onCreate = async () => { + const patch = (key: keyof SignupForm, value: string) => + setForm((prev) => ({ ...prev, [key]: value })); + + const onSubmit = async (e: FormEvent) => { + e.preventDefault(); setError(null); if (form.password !== form.confirmPassword) { setError(t('auth.passwordsDontMatch')); @@ -85,264 +82,91 @@ export default function Signup() { return ( - -
- {step === 1 ? t('auth.createYourAccount') : t('auth.tellAboutYourWork')} + {t('auth.createYourAccount')}
- {step === 1 ? t('auth.fewDetailsToSetUp') : t('auth.helpsUsTailor')} + {t('auth.fewDetailsToSetUp')}
- {step === 1 ? ( - setStep(2)} /> - ) : ( - setStep(1)} - onCreate={onCreate} - submitting={submitting} - /> - )} - -
-
- {t('auth.alreadyHaveAccount')}{' '} - - {t('auth.signIn')} - -
-
-
- ); -} - -function ProgressBar({ step }: { step: 1 | 2 }) { - const { t } = useTranslation(); - return ( -
-
- {t('auth.stepOf', { current: step, total: 2 })} - - {step === 1 ? t('auth.stepAccount') : t('auth.stepProfile')} - -
-
-
-
+ patch('firstName', e.currentTarget.value)} + disabled={submitting} + required + /> + patch('lastName', e.currentTarget.value)} + disabled={submitting} + required + /> +
+ patch('email', e.currentTarget.value)} + disabled={submitting} + required /> -
-
- ); -} - -function AccountStep({ - form, - setForm, - onNext, -}: { - form: SignupForm; - setForm: (updater: (prev: SignupForm) => SignupForm) => void; - onNext: () => void; -}) { - const { t } = useTranslation(); - const patch = (key: keyof SignupForm, value: string) => - setForm((prev) => ({ ...prev, [key]: value })); - - const onSubmit = (e: FormEvent) => { - e.preventDefault(); - onNext(); - }; - - return ( -
-
patch('firstName', e.currentTarget.value)} + label={t('auth.password')} + type="password" + placeholder={t('auth.passwordCreatePlaceholder')} + leadingIcon="lock" + hint={t('auth.passwordHint')} + autoComplete="new-password" + value={form.password} + onChange={(e) => patch('password', e.currentTarget.value)} + disabled={submitting} required /> patch('lastName', e.currentTarget.value)} + label={t('auth.confirmPassword')} + type="password" + placeholder={t('auth.confirmPasswordPlaceholder')} + leadingIcon="lock" + autoComplete="new-password" + value={form.confirmPassword} + onChange={(e) => patch('confirmPassword', e.currentTarget.value)} + disabled={submitting} required /> -
- patch('email', e.currentTarget.value)} - required - /> - patch('password', e.currentTarget.value)} - required - /> - patch('confirmPassword', e.currentTarget.value)} - required - /> - -
- ); -} - -function ProfileStep({ - form, - setForm, - onBack, - onCreate, - submitting, -}: { - form: SignupForm; - setForm: (updater: (prev: SignupForm) => SignupForm) => void; - onBack: () => void; - onCreate: () => void; - submitting: boolean; -}) { - const { t } = useTranslation(); - const patch = (key: K, value: SignupForm[K]) => - setForm((prev) => ({ ...prev, [key]: value })); - - return ( -
- - patch('organization', e.currentTarget.value)} - disabled={submitting} - /> -