User — Frontend Engineering

Planned — not yet live. The entire Block / Unblock feature, including the Blocked Users List screen, the profile block/unblock action, the controllers, and the API calls described here, has not been built yet.

Frontend engineering notes for the user domain, focused on the Block / Unblock User feature. Existing user screens (login, settings, profile) are referenced only for navigation context.

Code lives under frontend/lib/modules/user/. The module follows the workspace GetX conventions:

  • Services (GetxService) hold session-scoped reactive state with .obs and survive across screens.
  • Page controllers (GetxController) hold plain fields and call update(['tag']) to rebuild GetBuilder widgets.
  • Repos are static classes that wrap MyApi.to, return raw maps/booleans, and log failures as MyException with an ExceptionCodes value.
  • Errors surface to the user through MySnackBar.show. Navigation goes through MyNavigator and MyRouteNames.

Screens

Screen Entry trigger Exit paths
Blocked Users List Account settings → "Blocked Users" row Back to settings; tap a row → confirm Unblock, stay on list
Rider Profile Tap a rider anywhere a profile is linked (search, ride participants, buddy list) Back to caller; Block opens the confirmation dialog; Unblock acts in place
Block Confirmation Dialog Tap Block on Rider Profile Confirm → runs block, dismisses; Cancel → dismisses, no change
  • Blocked Users List shows each blocked rider (name, photo) with an Unblock affordance per row. It is reached from account settings, not from the main navigation.
  • Rider Profile shows a single action that toggles by state: Block when the rider is not blocked, Unblock when they are. The block path is guarded by a confirmation dialog; unblock acts directly (matching the lighter-weight, reversible nature of unblock).
  • Block Confirmation Dialog is a simple confirm/cancel dialog. It states that blocking removes any existing buddy connection and closes intercom.

Controller & State

BlockedUsersController (GetxController)

Bound to the Blocked Users List screen. Loads the list once on init and holds it for the screen's lifetime.

Field Type Purpose
blocked List<BlockedRider> Riders the current user has blocked
isLoading bool True while the initial list fetch is in flight
hasError bool True if the list fetch failed
  • onInit() calls loadBlocked().
  • loadBlocked() sets isLoading = true, update(['blockedUsersPage']), fetches the list, then clears loading and rebuilds.
  • unblock(String targetId) removes the rider from blocked and rebuilds before awaiting the API (optimistic). On failure it re-inserts the rider at its prior position and shows a MySnackBar error.
  • BlockedRider is a small view model holding id, display name, photo URL, and createdAt (when the block was created).

RiderProfileController (GetxController) — additions

The block feature adds one reactive field and two actions to the existing profile controller.

Field Type Purpose
isBlocked bool Whether the viewed rider is blocked by the current user
  • isBlocked is populated when the profile loads (see Local Caching) and drives the Block / Unblock action label.
  • block() sets isBlocked = true and rebuilds before awaiting the API (optimistic). On success it also invalidates the Blocked Users List cache so the new entry appears next time that screen opens. On failure it reverts isBlocked to false and shows a MySnackBar error.
  • unblock() mirrors block() with the opposite transition.
  • A self-block guard short-circuits both actions if the target id equals the signed-in user's id (MyUserService.get.user.id). The UI never offers Block on the user's own profile, so this is defensive only.

Optimistic update pattern

Block and unblock apply the state change locally and rebuild first, then await the repo call:

  1. Apply the change to the reactive field/list and update([...]).
  2. Await the repo call.
  3. On success, do nothing further (state already reflects the change); if the action mutates the blocked list, invalidate that cache.
  4. On failure, revert to the captured prior state and surface a MySnackBar error.

This keeps the toggle instant. Offline is handled separately (see Edge Cases) — the controllers do not optimistically update when there is no connectivity.

Routes use the existing MyRouteNames enum and MyNavigator.

  • Settings → Blocked Users: a new blockedUsers entry in MyRouteNames (path /settings/blocked) reached from a row in the account settings screen.
  • Back navigation: standard back from Blocked Users returns to settings; back from Rider Profile returns to whatever screen linked the profile.
  • Block action: triggered in place on the Rider Profile screen via the confirmation dialog; it does not navigate. Unblock on the Blocked Users List acts in place and stays on the list.
  • Guards: both screens require an authenticated session (MyUserService.get.isSignedIn), consistent with other signed-in routes.
  • Deep links: none for the Blocked Users List — it is a private, settings-nested screen with no shareable URL. Rider Profile keeps whatever linking it already has; the block feature adds no new deep link.

API Dependencies

All calls require a Firebase ID token (added by MyApi.to), and the caller's id must match :id. The repo (MyUserRepo, or a dedicated block repo) wraps these and logs failures as MyException.

Action Method & path Notes
Block POST user/:id/block/:targetId Idempotent — blocking an already-blocked rider succeeds with no extra effect
Unblock DELETE user/:id/block/:targetId Returns 404 if the rider is not on the blocked list
List blocked GET user/:id/blocked Returns the current user's blocked riders
  • :id — the signed-in user's id (MyUserService.get.user.id).
  • :targetId — the rider being blocked or unblocked.

Block / Unblock request: no body; both ids are path parameters.

List blocked response:

{
  "blocked": [
    { "id": "rider_123", "createdAt": "2026-05-24T10:00:00.000Z" }
  ]
}

The list response carries id and createdAt only. Display name and photo for each row are resolved from already-cached user data where available; otherwise the row falls back to a minimal identity until a profile is opened.

Local Caching

  • Blocked list is held in memory by BlockedUsersController for the screen's session. It is not persisted to local storage — it is private, small, and cheap to refetch.
  • The list is invalidated whenever a block or unblock succeeds:
  • Unblock from the list removes the row locally (optimistic).
  • Block from a Rider Profile marks the cached list stale so the next open of the Blocked Users List refetches and shows the new entry.
  • isBlocked flag on RiderProfileController is fetched when a profile loads. It is derived from the blocked-state the backend returns with the profile (or by checking against the cached blocked list when present), and kept in the controller for that profile view only.
  • Session-wide user state continues to live in MyUserService (a GetxService); the block feature adds no new persistent service state.

Edge Cases

  • Loading: Blocked Users List shows a loading indicator while isLoading is true on first fetch.
  • Empty list: when blocked is empty after load, show an empty state ("You haven't blocked anyone") rather than a bare list.
  • Offline: block and unblock are rejected when offline — do not optimistically update. Show a MySnackBar error and leave state unchanged so the UI never claims a block that did not reach the server.
  • API failure (online): revert the optimistic change to the captured prior state and show a MySnackBar error.
  • Unblock 404 (not blocked): treat as already-unblocked — remove the row if still present and do not surface an error; the end state matches intent.
  • Self-block guard: the UI never offers Block on the user's own profile; the controller also short-circuits if targetId equals the signed-in user's id, as a defensive check.
  • Stale row identity: if a blocked row has no cached name/photo, render a minimal placeholder identity rather than blocking the whole list on a lookup.
  • Blocked user invisibility: because a block hides the rider from search, suggestions, and participant previews, the Rider Profile is normally only reachable for a blocked rider via the Blocked Users List; the profile's Unblock action and the list's Unblock action share the same controller logic.