What's Happening
The project ships react-hook-form, zod, and @hookform/resolvers in package.json — and none of the app's forms use them. Creation and auth forms are hand-rolled useState + bare required attributes, with several concrete defects verified in the source:
components/organisms/create/book-create-form.jsx:
- Every
<Label> says htmlFor="title" — the description, category, and price labels all point at the (nonexistent) title id, and the inputs themselves have name but no id, so no label is programmatically associated with its field (clicking a label focuses nothing; screen readers announce unlabeled inputs). The file upload labels use id="thumbail" (typo) / id="file" on the <Label> itself instead of htmlFor.
- Errors surface via
alert(data.message || 'Book creation failed') and alert('Something went wrong!').
- The price placeholder is
"Price (₦)" (naira) while the platform sells in USDC (PaymentModal renders ${item?.price} USDC) — no min/step constraints, so negative prices pass client-side.
components/organisms/create/course-create-form.jsx: same duplicated htmlFor="title" on all four labels, same Label id= misuse.
components/organisms/create/space-create-form.jsx: correct htmlFors but fields lack matching ids, and failure is alert('Something went wrong!').
components/organisms/auth/signup-form.jsx / login-form.jsx: manual useState handling, no inline validation messages (only toasts after submit).
No numeric coercion is done on price (it stays a string from the input), and no file-type/size validation runs before upload even though lib/utils/cloudinaryUpload.js exports a ready-made validateFile helper.
Where to Find This
components/organisms/create/book-create-form.jsx, course-create-form.jsx, space-create-form.jsx
components/organisms/auth/signup-form.jsx, login-form.jsx
components/ui/form.jsx — the shadcn Form wrapper for react-hook-form already exists in the repo, unused
lib/utils/cloudinaryUpload.js — validateFile
What We Want
- Migrate the three create forms to
react-hook-form + zodResolver with zod schemas (title/description lengths, category required, price coerced to a number >= 0 with a sensible max, required files with type/size checks via validateFile).
- Render field-level error messages inline (use the existing
components/ui/form.jsx primitives); remove every alert() in these forms in favor of inline errors + sonner toasts for submit-level failures.
- Fix all label/input associations: unique
id per input, matching htmlFor per label (this alone fixes a WCAG 1.3.1/4.1.2 failure).
- Correct the currency affordance: label/prefix the price field as USD/USDC to match the Stellar checkout.
- Migrate signup/login to the same pattern (email format, min password length consistent with
hooks/passwordChecker.js if applicable) — inline errors before submit.
Technical Context
zod@^3.24, react-hook-form@^7.56, @hookform/resolvers@^5 are already installed — no new dependencies.
- Keep the existing submit actions (
createBook, createCourse, create-space actions in lib/actions/) and their FormData shapes; only the client-side layer changes.
- The forms render inside the custom
Modal — check that error text doesn't overflow the modal's max-h scroll container.
Button here is the custom components/atoms/form/Button (has loading prop) — keep it.
Acceptance Criteria
What's Happening
The project ships
react-hook-form,zod, and@hookform/resolversinpackage.json— and none of the app's forms use them. Creation and auth forms are hand-rolleduseState+ barerequiredattributes, with several concrete defects verified in the source:components/organisms/create/book-create-form.jsx:<Label>sayshtmlFor="title"— the description, category, and price labels all point at the (nonexistent)titleid, and the inputs themselves havenamebut noid, so no label is programmatically associated with its field (clicking a label focuses nothing; screen readers announce unlabeled inputs). The file upload labels useid="thumbail"(typo) /id="file"on the<Label>itself instead ofhtmlFor.alert(data.message || 'Book creation failed')andalert('Something went wrong!')."Price (₦)"(naira) while the platform sells in USDC (PaymentModalrenders${item?.price} USDC) — no min/step constraints, so negative prices pass client-side.components/organisms/create/course-create-form.jsx: same duplicatedhtmlFor="title"on all four labels, sameLabel id=misuse.components/organisms/create/space-create-form.jsx: correcthtmlFors but fields lack matchingids, and failure isalert('Something went wrong!').components/organisms/auth/signup-form.jsx/login-form.jsx: manualuseStatehandling, no inline validation messages (only toasts after submit).No numeric coercion is done on
price(it stays a string from the input), and no file-type/size validation runs before upload even thoughlib/utils/cloudinaryUpload.jsexports a ready-madevalidateFilehelper.Where to Find This
components/organisms/create/book-create-form.jsx,course-create-form.jsx,space-create-form.jsxcomponents/organisms/auth/signup-form.jsx,login-form.jsxcomponents/ui/form.jsx— the shadcnFormwrapper for react-hook-form already exists in the repo, unusedlib/utils/cloudinaryUpload.js—validateFileWhat We Want
react-hook-form+zodResolverwith zod schemas (title/description lengths, category required,pricecoerced to a number>= 0with a sensible max, required files with type/size checks viavalidateFile).components/ui/form.jsxprimitives); remove everyalert()in these forms in favor of inline errors +sonnertoasts for submit-level failures.idper input, matchinghtmlForper label (this alone fixes a WCAG 1.3.1/4.1.2 failure).hooks/passwordChecker.jsif applicable) — inline errors before submit.Technical Context
zod@^3.24,react-hook-form@^7.56,@hookform/resolvers@^5are already installed — no new dependencies.createBook,createCourse,create-spaceactions inlib/actions/) and their FormData shapes; only the client-side layer changes.Modal— check that error text doesn't overflow the modal'smax-hscroll container.Buttonhere is the customcomponents/atoms/form/Button(hasloadingprop) — keep it.Acceptance Criteria
alert()calls remain in them.pricereaches the API as a non-negative number and the UI presents it as USDC/USD, not ₦.npm run lintandnpm run buildpass.