Skip to content

feat: update auth navigation flows and guest navbar behavior#176

Merged
gulcetahtasiz merged 3 commits into
developmentfrom
feature/web-auth-state-flow
Apr 1, 2026
Merged

feat: update auth navigation flows and guest navbar behavior#176
gulcetahtasiz merged 3 commits into
developmentfrom
feature/web-auth-state-flow

Conversation

@erinc00

@erinc00 erinc00 commented Mar 29, 2026

Copy link
Copy Markdown
Contributor

This PR refines web auth/navigation behavior by separating guest vs authenticated flows, improving the login verification experience, and simplifying legal-page navigation.

What Changed

Added centralized auth route gating

  • Introduced a reusable route gate for guest-only and protected pages.
  • Ensures protected pages require auth, while guest-only pages redirect authenticated users appropriately.

Updated guest access behavior

  • Guests can now access:
    • Home
    • News
    • Emergency Numbers
  • Guest navbar is simplified (no Profile, Privacy, Security items).

Improved login UX

  • Added Verify email? action inside the expanded email login form (after Continue with Email).
  • Wired resend action to backend resend verification endpoint.
  • Added safe returnTo handling for post-login redirects.

Refined navbar behavior

  • Navbar order is stable (no shifting based on auth transitions).
  • Avatar dropdown interactions were made more reliable.
  • Guest dropdown includes Log In and Create Account actions with updated styling.

Legal pages switched to navbar-based navigation

  • Privacy Policy and Terms of Service now use the shared app shell/navbar flow.
  • Removed back-button-driven legal navigation approach.

Root route behavior updated

  • / now redirects directly to home instead of showing the previous welcome screen.
    Auth showcase visual update
  • Signup/Login showcase background is adjusted to a red-dominant variant using existing theme tokens (no hardcoded color dependency).

Note: For the MVP scope, the planned end-to-end web user flow is now considered complete (guest navigation, auth entry points, protected routing behavior, legal page navigation, and core login/verification UX). Further changes can continue as post-MVP refinements.

Closes #173

@kckagancan kckagancan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

web/src/components/auth/AuthRouteGate.tsx:

1. ready state can preserve a stale authorization result across transitions

ready is only ever set to true and is never reset when mode, pathname, or auth state changes. If the same AuthRouteGate instance remains mounted across client-side transitions, this can briefly render protected or guest-only content before the redirect effect runs. I think the gate should avoid relying on a previously computed ready=true value from another route/state.

2. The gate treats any stored token as authenticated without validating it

Access is currently granted purely based on getAccessToken() returning a non-empty string. An expired or otherwise invalid token will still pass this gate and allow rendering until a later API call fails. That makes the route protection unreliable in practice. I think this should be backed by validated auth state (for example session context or a verified /me state) or enforced at the middleware/server level.

web/src/app/(auth)/page.tsx:

Removing the welcome page is not a good idea I think. It would also be inconsistend with android side since we have welcome page there as well. No need to do these extra unnecessary works I think for now.

web/src/components/feature/auth/LoginForm.tsx:

1. Login success is coupled too tightly to fetchMyProfile()

Right now the login flow only persists the access token after fetchMyProfile() succeeds, except for the 404 complete-profile case. That means a user can successfully authenticate, receive a valid access token, and still be shown a login failure if /profiles/me temporarily fails with a network error or a 5xx response.

This makes the login flow brittle and can incorrectly block already authenticated users. I think the authenticated session should not depend on profile fetch succeeding. The token should be persisted once login succeeds, and profile fetch should only decide the post-login route (/home vs /complete-profile) or show a recoverable error state.

2. returnTo blocking only checks exact route strings

safeReturnTo blocks "/", "/login", "/signup", "/forgot-password", and "/verify-email" only as exact strings. Values like "/login?foo=1" or "/signup?step=2" are not blocked and will still be treated as safe targets.

That can send an already authenticated user back into guest-only auth routes and create redirect confusion. I think the route check should normalize returnTo to its pathname before comparing against the blocked auth routes.

web/src/components/layout/TopNavbar.tsx:

1. Switch Account does not actually switch accounts

The Switch Account action only does router.push("/login") but does not clear the current access token first. If the login page is guest-only, an already authenticated user will immediately be redirected away from /login, so this action will not work as expected.

I think this should either clear the current session before navigating to the login page, or use a dedicated account-switch flow that bypasses the guest-only redirect logic.

2. Navbar auth state is only refreshed on pathname changes

isAuthenticated is derived from getAccessToken() inside an effect that depends only on pathname. That means auth state changes that happen without a route change (token cleared elsewhere, token updated, cross-tab logout, session expiry handling, etc.) will leave the navbar in a stale state until navigation happens.

I think the navbar should read from a shared auth state/store/context instead of keeping its own route-triggered copy of authentication state.

@erinc00

erinc00 commented Mar 30, 2026

Copy link
Copy Markdown
Contributor Author

web/src/components/auth/AuthRouteGate.tsx:

1. ready state can preserve a stale authorization result across transitions

ready is only ever set to true and is never reset when mode, pathname, or auth state changes. If the same AuthRouteGate instance remains mounted across client-side transitions, this can briefly render protected or guest-only content before the redirect effect runs. I think the gate should avoid relying on a previously computed ready=true value from another route/state.

2. The gate treats any stored token as authenticated without validating it

Access is currently granted purely based on getAccessToken() returning a non-empty string. An expired or otherwise invalid token will still pass this gate and allow rendering until a later API call fails. That makes the route protection unreliable in practice. I think this should be backed by validated auth state (for example session context or a verified /me state) or enforced at the middleware/server level.

web/src/app/(auth)/page.tsx:

Removing the welcome page is not a good idea I think. It would also be inconsistend with android side since we have welcome page there as well. No need to do these extra unnecessary works I think for now.

web/src/components/feature/auth/LoginForm.tsx:

1. Login success is coupled too tightly to fetchMyProfile()

Right now the login flow only persists the access token after fetchMyProfile() succeeds, except for the 404 complete-profile case. That means a user can successfully authenticate, receive a valid access token, and still be shown a login failure if /profiles/me temporarily fails with a network error or a 5xx response.

This makes the login flow brittle and can incorrectly block already authenticated users. I think the authenticated session should not depend on profile fetch succeeding. The token should be persisted once login succeeds, and profile fetch should only decide the post-login route (/home vs /complete-profile) or show a recoverable error state.

2. returnTo blocking only checks exact route strings

safeReturnTo blocks "/", "/login", "/signup", "/forgot-password", and "/verify-email" only as exact strings. Values like "/login?foo=1" or "/signup?step=2" are not blocked and will still be treated as safe targets.

That can send an already authenticated user back into guest-only auth routes and create redirect confusion. I think the route check should normalize returnTo to its pathname before comparing against the blocked auth routes.

web/src/components/layout/TopNavbar.tsx:

1. Switch Account does not actually switch accounts

The Switch Account action only does router.push("/login") but does not clear the current access token first. If the login page is guest-only, an already authenticated user will immediately be redirected away from /login, so this action will not work as expected.

I think this should either clear the current session before navigating to the login page, or use a dedicated account-switch flow that bypasses the guest-only redirect logic.

2. Navbar auth state is only refreshed on pathname changes

isAuthenticated is derived from getAccessToken() inside an effect that depends only on pathname. That means auth state changes that happen without a route change (token cleared elsewhere, token updated, cross-tab logout, session expiry handling, etc.) will leave the navbar in a stale state until navigation happens.

I think the navbar should read from a shared auth state/store/context instead of keeping its own route-triggered copy of authentication state.

Thanks for the detailed review. Addressed all points in the latest update.

AuthRouteGate stale ready state

  • Reworked the gate to use explicit auth status (checking | authenticated | guest) instead of a sticky ready=true flag.
  • This prevents stale authorization state from leaking across transitions.

Token presence vs token validity

  • Gate no longer treats any stored token as authenticated by default.
  • It now validates auth using fetchCurrentUser (/auth/me) before granting access.
  • Invalid/expired tokens are cleared and treated as guest.

Welcome page removal

  • Restored the Welcome page at / for consistency with the Android flow.

Login coupled to fetchMyProfile

  • Decoupled session establishment from profile fetch.
  • Access token is persisted immediately after successful login.
  • Profile fetch now only affects post-login routing (/complete-profile vs default route) and no longer blocks an authenticated session.

returnTo exact-string blocking

  • returnTo checks now normalize to pathname before blocked-route comparison (so values like /login?x=1 are also blocked).

Switch Account behavior

  • Updated Switch Account to clear session before navigating to /login, so it now works under guest-only login constraints.

Navbar auth freshness

  • Navbar auth state is now synced beyond pathname changes via auth change signaling and browser events (storage, focus, visibility updates), reducing stale UI state.

@erinc00
erinc00 requested a review from kckagancan March 30, 2026 07:41

@kckagancan kckagancan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fine now. Great work.

@gulcetahtasiz gulcetahtasiz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything looks fine, thanks for the effort. Merging

@gulcetahtasiz
gulcetahtasiz merged commit 42e19e5 into development Apr 1, 2026
@erinc00
erinc00 deleted the feature/web-auth-state-flow branch April 7, 2026 14:50
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.

3 participants