Repository navigation
feat: toast notifications + opt-in user registration (closes #29) - #33
Merged
Merged
Conversation
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.
This was referenced May 13, 2026
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.
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 imperativetoastexport so non-component code can fire one too.role="status"for success/info (announced politely),role="alert"for errors (announced assertively).<Toaster />mounted at the app root — outside the auth-stateRoutesbranches — so success toasts survive the navigate after login / logout / setup / save.AddPagesave success/failure (drops the old inline success banner; error toast replaces the per-page error banner so the visual language is consistent).EditPagesave + delete success/failure.Loginsuccess ("Welcome back, X."). Login errors stay inline — they're form-local validation feedback.Logoutsuccess ("Signed out.").Setupsuccess ("Account created.").Tagsdelete success/failure.Registration
AuthOptionsbinds theCollectify:Authsection. Single knob today:AllowRegistration(bool, defaultfalse).POST /api/auth/registerbehind the flag:/setup— no preempting the admin bootstrap from a stranger who hits the raw URL).UserManager.CreateAsync(which enforces uniqueness) and signs them in.GET /api/auth/meexposesallowRegistrationso the client can show / hide the affordance without a separate fetch./registerpage — username + password + confirm, client-side mismatch guard, welcome toast on success.allowRegistrationis true.CollectifyApiFactory— newAllowRegistrationinit-only property +ConfigureAppConfigurationinjection so tests can toggle the flag per factory instance.Configuration
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 acrossAuthEndpointsTests).npm test -- --run— client 66/66 (was 59; +7 acrosstoaster.test.tsx).New backend tests (
AuthEndpointsTests)/meexposesAllowRegistrationfrom config (false by default, true when toggled)./registerreturns 404 when disabled./registerreturns 400 when enabled but no users exist (preempts /setup edge case)./registersuccess path creates the user and signs them in (/meon the same client reports the new user).IdentityResult.Errors.New client tests (
toaster.test.tsx)role=statusfor success,role=alertfor errors.Manual
Collectify__Auth__AllowRegistration=true, restart./api/auth/mereturnsallowRegistration: true. Login page now shows the link →/registerrenders → submit creates an account, toasts "Welcome, X.", and lands logged-in./registerwhile signed out without first running/setup(zero users in the DB) → 400 error from the form.Out of scope
🤖 Generated with Claude Code
Generated by Claude Code