Skip to content

feat: toast notifications + opt-in user registration (closes #29) - #33

Merged
mforce merged 1 commit into
mainfrom
feat/toasts-and-register
May 11, 2026
Merged

mforce merged 1 commit into
mainfrom
feat/toasts-and-register

Conversation

@mforce

@mforce mforce commented May 11, 2026 •

Copy link
Copy Markdown
Owner

Closes #29. Two coupled threads in one PR — both touch the auth screens and rely on the same toast layer.

Summary

Toasts

  • components/toaster.tsx — module-level store + <Toaster /> renderer + useToast() hook + an imperative toast export so non-component code can fire one too.
    • Success / info auto-dismiss (2.5s / 4s); errors stick until manually closed.
    • Max 4 visible at a time; oldest evicted on overflow.
    • role="status" for success/info (announced politely), role="alert" for errors (announced assertively).
  • <Toaster /> mounted at the app root — outside the auth-state Routes branches — so success toasts survive the navigate after login / logout / setup / save.
  • Wired into:
    • AddPage save success/failure (drops the old inline success banner; error toast replaces the per-page error banner so the visual language is consistent).
    • EditPage save + delete success/failure.
    • Login success ("Welcome back, X."). Login errors stay inline — they're form-local validation feedback.
    • Logout success ("Signed out.").
    • Setup success ("Account created.").
    • Tags delete success/failure.

Registration

  • AuthOptions binds the Collectify:Auth section. Single knob today: AllowRegistration (bool, default false).
  • POST /api/auth/register behind the flag:
    • 404 when disabled so the client can use one signal to decide whether to render the link.
    • Refuses when no users exist (first-run still belongs to /setup — no preempting the admin bootstrap from a stranger who hits the raw URL).
    • Otherwise creates the user via UserManager.CreateAsync (which enforces uniqueness) and signs them in.
  • GET /api/auth/me exposes allowRegistration so the client can show / hide the affordance without a separate fetch.
  • /register page — username + password + confirm, client-side mismatch guard, welcome toast on success.
  • Login page — surfaces a "Don't have an account? Create one" link when allowRegistration is true.
  • CollectifyApiFactory — new AllowRegistration init-only property + ConfigureAppConfiguration injection so tests can toggle the flag per factory instance.

Configuration

Collectify__Auth__AllowRegistration=true

With the flag off (default), the register endpoint 404s and the Login page hides the link — single-user installs aren't accidentally opened up.

Test plan

CI

  • dotnet test — server 278/278 (was 271; +7 across AuthEndpointsTests).
  • npm test -- --run — client 66/66 (was 59; +7 across toaster.test.tsx).
  • Server + Vite builds clean.

New backend tests (AuthEndpointsTests)

  • /me exposes AllowRegistration from config (false by default, true when toggled).
  • /register returns 404 when disabled.
  • /register returns 400 when enabled but no users exist (preempts /setup edge case).
  • /register success path creates the user and signs them in (/me on the same client reports the new user).
  • Duplicate username → 400 via IdentityResult.Errors.
  • Blank username / password → 400.
  • Short password (below the Identity options floor) → 400.

New client tests (toaster.test.tsx)

  • Empty queue renders nothing.
  • role=status for success, role=alert for errors.
  • Success toast auto-dismisses after its TTL.
  • Error toast survives past the auto-dismiss window.
  • Close button dismisses on click.
  • 4-deep FIFO stack cap with newest-on-top.

Manual

  • Default config: log in / out / save / delete each fire a toast that survives navigation; login errors stay inline. The login screen has no "Create one" link.
  • Set Collectify__Auth__AllowRegistration=true, restart. /api/auth/me returns allowRegistration: true. Login page now shows the link → /register renders → submit creates an account, toasts "Welcome, X.", and lands logged-in.
  • Try /register while signed out without first running /setup (zero users in the DB) → 400 error from the form.

Out of scope

  • Persistent action toasts ("Item deleted — Undo") — needs the underlying mutations to support reversal. Separate ticket if we want it.
  • Email verification / password reset for self-registered users — file separately if multi-user usage actually picks up.
  • Per-user API key overrides + admin user management UI — Phase 4 deferred items the issue calls out.

🤖 Generated with Claude Code


Generated by Claude Code

Two coupled threads in one PR.

Toasts
- New components/toaster.tsx with a module-level store + Toaster
  renderer + useToast() hook (returns the same imperative `toast`
  object for components that prefer DI). Success/info auto-dismiss
  (2.5s / 4s); errors stick until manually closed. Max 4 visible at a
  time -- oldest evicted on overflow. role=status for non-errors,
  role=alert for errors so screen readers announce errors assertively.
- <Toaster /> mounted at the app root (outside the auth-branch
  Routes) so success toasts survive the post-login / post-logout /
  post-save navigate.
- Wired into existing flows:
    * AddPage save success/failure (drops the old inline success
      banner; error toast replaces the per-page error banner so the
      visual language is consistent).
    * EditPage save + delete success/failure.
    * Login success ("Welcome back, X"). Login errors stay inline --
      they're contextual to the form.
    * Logout success ("Signed out.").
    * Setup success ("Account created.").
    * Tags page delete success/failure.

Registration
- Collectify.Infrastructure.Identity.AuthOptions binds the
  Collectify:Auth section. Single knob today: AllowRegistration (bool,
  default false). appsettings.Development.json gets the placeholder.
- POST /api/auth/register: 404 when AllowRegistration is off so the
  client can use one signal (404) to decide whether to render the
  link. Refuses when no users exist (first-run still belongs to
  /setup, no preempting the admin bootstrap). Otherwise creates the
  user + signs them in via SignInManager.
- GET /api/auth/me exposes `allowRegistration` so the client can show
  or hide the link without a separate flag fetch.
- New /register page in the client: username + password +
  confirmation, client-side mismatch guard, useRegister mutation
  that fires a welcome toast on success.
- Login page: when `allowRegistration` is true, surfaces a "Create
  one" link to /register.
- CollectifyApiFactory: AllowRegistration init-only property +
  ConfigureAppConfiguration injection so tests toggle the flag per
  factory instance.

Tests
- Server (+7): AuthEndpointsTests covering /me exposing the flag,
  register 404 when disabled, register refusing before setup,
  register success path signs the new user in, duplicate username,
  blank fields, short password.
- Client (+7): toaster.test.tsx -- empty queue renders nothing;
  status/alert roles per kind; success auto-dismiss after TTL; error
  sticks past the window; close-button dismiss; 4-deep FIFO stack
  cap.

Server 278/278 (was 271). Client 66/66 (was 59). Build clean.
@mforce
mforce merged commit a4d1b9d into main May 11, 2026
1 check passed
@mforce
mforce deleted the feat/toasts-and-register branch May 11, 2026 08:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Toast notifications for user actions (save / delete / auth / …)

2 participants