feat(auth): build sign-in form and validation states - #36
Conversation
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Task-Id: 01KZXZVXYVRV0CJEVT1ZDXQN1Q Prompt-Version: 1c9c10e027a2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements TG-17: Build sign-in form and validation states — an accessible sign-in form that consumes the shared authentication state model added in the parent epic.
What was added
src/components/SignInForm.tsx— a controlled, accessible sign-in form:<label htmlFor>bindings,autoComplete, and appropriatetype/inputMode.isValidEmail/isValidPasswordhelpers fromsrc/auth. Fields report required/format/length errors; invalid fields setaria-invalidand link their message viaaria-describedby. Errors re-validate live after the first submit attempt so users get feedback as they correct input.onSubmitis pending (or the externalloadingprop is set) the button shows "Signing in…", setsaria-busy, and inputs are disabled. Supports both internally-managed (promise-based) and externally-controlled loading.role="alert"region, sourced from either a rejectedonSubmitpromise or an externally-providederrorprop (e.g. anexpiredauth state).src/components/SignInForm.css— styling consistent with the app's existing dark theme, including visible focus rings and invalid-field states.src/components/SignInForm.test.tsx— 11 component tests covering rendering, empty/format/length validation, aria wiring, successful submit, live error clearing, internal + external loading states, rejection and external error display, and keyboard operation.Test infrastructure
The repo had Vitest configured but no DOM environment or React testing utilities. Added:
@testing-library/react,@testing-library/user-event,@testing-library/jest-dom,jsdom.src/test/setup.ts— registers jest-dom matchers and cleans up between tests.vitest.config.ts— a separate config (jsdom environment + setup file). It is kept apart fromvite.config.tsbecause the repo uses Vite 8 (rolldown) at the root while Vitest bundles Vite 7; combining the two plugin types in one config tripstsc -b. Thetestnpm script points at this config.Build & test results
mise run build(tsc -b && vite build): PASS — built in ~150ms, no type errors.mise run lint(oxlint): PASS — no warnings.npm test(vitest run): PASS — 33/33 tests (11 new component tests + 22 existing auth tests).Decisions made
isValidEmail/isValidPasswordrather than re-implementing validation, keeping this UI aligned with sibling issues.error/loadingprops so the form works standalone or wired to externally-managed auth state.onSubmit; it deliberately does not implement the authentication request or global state wiring, which belong to sibling issues in the epic.Agent notes
src/authmodule was clean and pure, so validation reuse was straightforward. Native form semantics gave keyboard accessibility for free.testtovite.config.tsfailtsc -b; isolating it invitest.config.tsresolved it without touching the production build config.noUnusedLocals/noUnusedParameters,verbatimModuleSyntaxrequiringimport type); oxlint with react/typescript plugins; components live insrc/componentsand are function components with named exports.AuthProvider/context would let this form wire directly into global auth state.By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of the project license.