Багатошаговий реєстраційний майстер для веб-сайту
Multi-step wizard зменшує когнітивну навантаження: замість довгої форми на одній сторінці — кілька коротких кроків. Застосовується, коли при реєстрації потрібно зібрати багато даних: профіль + компанія + роль + налаштування сповіщень.
Коли майстер виправданий
Виправдано при 5+ полях, які логічно діляться на групи. Для 3–4 полів (ім'я, email, пароль) — майстер зайвий, простіше одна форма.
Типова структура для SaaS B2B:
- Аккаунт (email, пароль)
- Профіль (ім'я, посада, фото)
- Компанія (назва, розмір, сфера)
- Тариф
- Підтвердження email
React — управління кроками
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
const STEPS = [
{ id: 'account', title: 'Аккаунт', schema: accountSchema },
{ id: 'profile', title: 'Профіль', schema: profileSchema },
{ id: 'company', title: 'Компанія', schema: companySchema },
];
export function RegistrationWizard() {
const [currentStep, setCurrentStep] = useState(0);
const [formData, setFormData] = useState({});
const methods = useForm({
resolver: zodResolver(STEPS[currentStep].schema),
mode: 'onBlur',
});
const onNext = methods.handleSubmit((data) => {
setFormData(prev => ({ ...prev, ...data }));
if (currentStep < STEPS.length - 1) {
setCurrentStep(s => s + 1);
methods.reset(); // сброс для наступного кроку
} else {
submitRegistration({ ...formData, ...data });
}
});
return (
<FormProvider {...methods}>
{/* Прогресс-бар */}
<StepProgress steps={STEPS} current={currentStep} />
{/* Крок */}
<form onSubmit={onNext}>
{currentStep === 0 && <AccountStep />}
{currentStep === 1 && <ProfileStep />}
{currentStep === 2 && <CompanyStep />}
<div className="flex justify-between mt-6">
{currentStep > 0 && (
<button type="button" onClick={() => setCurrentStep(s => s - 1)}>
Назад
</button>
)}
<button type="submit">
{currentStep < STEPS.length - 1 ? 'Далі' : 'Завершити реєстрацію'}
</button>
</div>
</form>
</FormProvider>
);
}
Збереження прогресу
// Зберігати в localStorage — користувач повернеться й не втратить дані
useEffect(() => {
const saved = localStorage.getItem('registration_progress');
if (saved) {
const { step, data } = JSON.parse(saved);
setCurrentStep(step);
setFormData(data);
}
}, []);
// Зберігати при кожному кроці
const saveProgress = (step: number, data: object) => {
localStorage.setItem('registration_progress', JSON.stringify({ step, data }));
};
// Очистити після успішної реєстрації
const clearProgress = () => {
localStorage.removeItem('registration_progress');
};
Backend: поетапна реєстрація
Два підходи:
Single request: всі дані відправляються одним запросом в кінці. Простіше для backend.
Incremental: кожен крок — окремий endpoint. Дозволяє створювати «чорновик» та возобновляти реєстрацію пізніше.
// Incremental approach
// POST /api/registration — створити pending user після кроку 1
public function createAccount(AccountStepRequest $request)
{
$user = User::create([
'email' => $request->email,
'password' => Hash::make($request->password),
'status' => 'pending', // не активен до завершення
]);
// Тимчасовий токен для продовження реєстрації
$token = $user->createToken('registration', ['registration:continue'])->plainTextToken;
return response()->json(['registration_token' => $token], 201);
}
// PUT /api/registration/profile — крок 2
public function updateProfile(ProfileStepRequest $request)
{
$user = $request->user(); // по registration_token
$user->update(['name' => $request->name, 'avatar' => $request->avatar]);
return response()->json(['success' => true]);
}
// PUT /api/registration/complete — фінальний крок
public function complete(CompleteRequest $request)
{
$user = $request->user();
$user->update(['status' => 'active']);
$user->sendEmailVerificationNotification();
// Видати повноцінний токен замість registration token
$user->tokens()->where('name', 'registration')->delete();
$token = $user->createToken('auth')->plainTextToken;
return response()->json(['token' => $token]);
}
Прогресс-бар
function StepProgress({ steps, current }: { steps: Step[]; current: number }) {
return (
<div className="flex items-center mb-8">
{steps.map((step, index) => (
<React.Fragment key={step.id}>
<div className={`flex items-center gap-2 ${index <= current ? 'text-blue-600' : 'text-gray-400'}`}>
<div className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium
${index < current ? 'bg-blue-600 text-white' : ''}
${index === current ? 'border-2 border-blue-600 text-blue-600' : ''}
${index > current ? 'border-2 border-gray-300 text-gray-400' : ''}
`}>
{index < current ? '✓' : index + 1}
</div>
<span className="text-sm hidden sm:block">{step.title}</span>
</div>
{index < steps.length - 1 && (
<div className={`flex-1 h-0.5 mx-3 ${index < current ? 'bg-blue-600' : 'bg-gray-200'}`} />
)}
</React.Fragment>
))}
</div>
);
}
Тимчасовість
Multi-step wizard з React Hook Form, localStorage збереження прогресу, поетапний backend API, прогресс-бар: 3–5 днів.







