feat(web): додати useApiForm — уніфікований form-engine на RHF + zod#1614
Conversation
Закриває §3.1 з docs/diagnostics/2026-05-03-web-deep-dive — роадмап item #8. До цього у репо ~30+ форм використовували розрізнені підходи (useFormValidation hook, inline validate, ad-hoc submit handlers) → немає shared error UX, дублювання логіки 'disable submit if pristine', відсутня уніфікована server-error mapping. apps/web/src/shared/forms/useApiForm.ts: - Wrapper над react-hook-form + @hookform/resolvers/zod - Автоматичне мапування серверних 400-помилок з shape { error, details: [{ path, message }] } (як вертає apps/server/src/http/validate.ts) → setError(path, ...) на полях. Top-level помилки (без path) → window.serverError. - submit(): pre-bound handleSubmit для <form onSubmit> - isSubmitting: гарантовано клириться навіть якщо handler не кинув - lastResponse: typed response після успіху - resetOnSuccess: опція ресету форми - clearServerError: ручне скидання banner-помилки apps/web/src/shared/forms/useApiForm.test.tsx: - 12 тестів: happy path, zod валідація, server details mapping, top-level error, isSubmitting state, dirty-state, resetOnSuccess, clearServerError, повторний submit після помилки. apps/web/src/shared/hooks/useFormValidation.ts: - Помічено @deprecated. Існуючі споживачі (ManualExpenseSheet, ResetPasswordPage) лишаються — мігруються в окремих PR-ах. apps/web/package.json: + react-hook-form ^7.75, @hookform/resolvers ^5.2. Refs: - docs/diagnostics/2026-05-03-web-deep-dive/01-frontend-ergonomics.md §3.1 - apps/server/src/http/validate.ts (server details shape) - packages/api-client/src/ApiError.ts (ApiError.body)
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (5)
✨ Finishing Touches🧪 Generate unit tests (beta)
Review rate limit: 0/10 reviews remaining, refill in 2 minutes and 42 seconds. Comment |
⏱️ CI Pipeline Duration ReportBased on the last 50 successful runs on the default branch. Overall Pipeline
Trend (last 20 runs): Per-Job Breakdown
|
Foundation для useApiForm зайшла у #1614 (hook + 12 тестів). Цей PR — перший high-traffic call-site з roadmap-у. Закриває Item 8 follow-up з docs/diagnostics/2026-05-03-web-deep-dive. Зміни в AuthPage: * Виокремив дві суб-форми <LoginForm /> та <RegisterForm />, кожна на своєму useApiForm з власною zod-схемою (loginSchema vs registerSchema). Це надійніше за умовну схему всередині однієї форми — RHF фіксує zodResolver на mount. * loginSchema перевіряє email-формат і вимагає не-порожній пароль (мінімальна довжина — на сервері; legacy-акаунти мають короткі паролі). * registerSchema enforces 10-character minimum, до 128 max, optional name (fallback на email-prefix у onSubmit). * PasswordVisibilityToggle і FieldError виокремлені в локальні компоненти — щоб не дублювати JSX між login та register. useApiForm дає: * isSubmitting (замість руками тримати loading useState) * defaultValues + register() (замість chain useState + onChange) * formState.errors (per-field client-side помилки) Серверні помилки далі йдуть через AuthContext.authError — не інтегрував з form.serverError, бо authError уже володіє локалізацією Better Auth (translateAuthError) і використовується паралельно у forgot-password і Google flows. Кидаємо мовчазний new Error("") у onSubmit при ok=false щоб придушити onSuccess (toast / achievement) — текст помилки рендериться через authError. Тести (apps/web/src/core/auth/AuthPage.test.tsx, 8 тестів): * zod-валідація порожніх / невалідних полів * happy path login → toast 'Вхід виконано' * login fail → no toast, authError рендериться як alert * register: 10-char minimum * register: name fallback на email-prefix * register: явне ім'я використовується as-is * mode switch скидає authError Co-Authored-By: Андрій Виграв <dmytro.s.stakhov@gmail.com>
Summary
Закриває item #8 з
docs/diagnostics/2026-05-03-web-deep-dive/00-overview.md(Score 1.67, §3.1 frontend-ergonomics).До цього у репо ~30+ форм використовували розрізнені підходи:
useFormValidationcustom hook, inlinevalidate()функції, ad-hoc submit handlers — без shared error UX, з дублюванням логіки "disable submit if pristine", без уніфікованої server-error mapping.Цей PR створює foundation: новий
useApiFormhook на стандарт-стеку react-hook-form + @hookform/resolvers/zod. Існуючі форми мігруватимуться поступово в окремих follow-up PR-ах (auth, finyk transactions, fizruk template, nutrition food add, routine task create — найвищий traffic спочатку).Що додано
apps/web/src/shared/forms/useApiForm.ts— wrapper, який обʼєднує:register,handleSubmit,formState).@hookform/resolvers/zod— client-side валідація з тих самих схем, які можна шерити з сервером.ApiErrorзі структурою{ error, details: [{ path, message }] }(саме той формат, який віддаєapps/server/src/http/validate.ts) автоматично перетворюється наsetError(path, …)для кожного поля. Top-level помилки →serverErrorдля banner-у над формою.submit— pre-boundhandleSubmit(handle)для<form onSubmit={submit}>шаблону.isSubmitting— гарантовано clears навіть якщо handler не кинув виключення.lastResponse: TResponse | undefined— типізована відповідь після успіху.resetOnSuccess?: boolean— опція ресету форми.clearServerError()— ручне скидання banner-помилки (на focus наступного поля).apps/web/src/shared/forms/useApiForm.test.tsx— 12 тестів:onSubmitdetails[].path→ setError на поляdetails→ top-levelserverErrorisSubmitting: кнопка disabled під час pendingresetOnSuccess: значення скидаються після успіхуclearServerError: top-level error можна скинутиapps/web/src/shared/hooks/useFormValidation.ts— помічено@deprecated. Існуючі споживачі (ManualExpenseSheet,ResetPasswordPage) лишаються до окремих migration-PR-ів.apps/web/package.json— доданоreact-hook-form ^7.75.0та@hookform/resolvers ^5.2.2.Що НЕ покрито у цьому PR
prefer-use-api-formчерезeslint-plugin-sergeant-design— diagnostic згадує опціонально; раціональніше додати після першої хвилі міграцій, коли стає зрозуміло, які форми мають винятки.Governing Skill
sergeant-feature-deliverysergeant-web-ui(бо це shared UI hook)Playbook
Verification
Additional checks:
Docs and Governance
AGENTS.mdneeded an update.Updated docs:
Risk and Rollout
useFormValidationтільки помічений@deprecated, всі споживачі продовжують працювати.@hookform/resolvers/zod~1kb — зайдуть тільки коли перша форма мігрує. Tree-shake поки що ефективно випиляє hook.@deprecated-marker.Hard Rule #15
AGENTS.mdbefore coding.--no-verify. Pre-commit hook (lint-staged + ESLint + Prettier) пройшов чисто.Reviewer Notes
@hookform/resolvers. zod вже в репо як основна schema-library і шериться з сервером.apps/server/src/http/validate.ts:400 → { error: string, details: [{ path: string, message: string }] }. Якщо сервер змінить shape, треба оновити функціюapplyServerErrorуuseApiForm.ts. Контракт уже використовується скрізь (~30 endpoints).details[].pathяк dot-path. Server віддаєpath: "user.email"для nested fields — RHFsetErrorтеж приймає dot-path черезPath<TValues>тип. Сумісно out-of-the-box.isSubmittingмає внутрішній прапорець? RHFformState.isSubmittingсам слідкує за promise зhandleSubmit, але якщо handler-помилка ловиться уapplyServerError, RHF тимчасово думає що submit ще активний. ВнутрішнійinternalSubmittingгарантовано clears уfinally.Test coverage,check(governance-sync),Critical-flow E2E, markdown link checker — pre-existing наmain; цей PR не додає нових.Summary by cubic
Introduce
useApiForm: a unified form engine for web built onreact-hook-formand zod resolver. It standardizes validation, server error mapping, and submit UX across forms. Closes diagnostics item #8 (§3.1 frontend-ergonomics).New Features
useApiForm(shared/forms/useApiForm.ts) usingreact-hook-form+@hookform/resolvers(zod).ApiError{ error, details: [{ path, message }] }to field errors and top-levelserverError.submit,isSubmitting,lastResponse,resetOnSuccess,clearServerError.shared/forms/index.ts.useFormValidationas@deprecated(existing consumers keep working).Dependencies
react-hook-form^7.75.0 and@hookform/resolvers^5.2.2.Written for commit 034cc0f. Summary will update on new commits.