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.obsand survive across screens. - Page controllers (
GetxController) hold plain fields and callupdate(['tag'])to rebuildGetBuilderwidgets. - Repos are static classes that wrap
MyApi.to, return raw maps/booleans, and log failures asMyExceptionwith anExceptionCodesvalue. - Errors surface to the user through
MySnackBar.show. Navigation goes throughMyNavigatorandMyRouteNames.
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()callsloadBlocked().loadBlocked()setsisLoading = true,update(['blockedUsersPage']), fetches the list, then clears loading and rebuilds.unblock(String targetId)removes the rider fromblockedand rebuilds before awaiting the API (optimistic). On failure it re-inserts the rider at its prior position and shows aMySnackBarerror.BlockedRideris a small view model holdingid, display name, photo URL, andcreatedAt(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 |
isBlockedis populated when the profile loads (see Local Caching) and drives the Block / Unblock action label.block()setsisBlocked = trueand 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 revertsisBlockedtofalseand shows aMySnackBarerror.unblock()mirrorsblock()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:
- Apply the change to the reactive field/list and
update([...]). - Await the repo call.
- On success, do nothing further (state already reflects the change); if the action mutates the blocked list, invalidate that cache.
- On failure, revert to the captured prior state and surface a
MySnackBarerror.
This keeps the toggle instant. Offline is handled separately (see Edge Cases) — the controllers do not optimistically update when there is no connectivity.
Navigation
Routes use the existing MyRouteNames enum and MyNavigator.
- Settings → Blocked Users: a new
blockedUsersentry inMyRouteNames(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
BlockedUsersControllerfor 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.
isBlockedflag onRiderProfileControlleris 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(aGetxService); the block feature adds no new persistent service state.
Edge Cases
- Loading: Blocked Users List shows a loading indicator while
isLoadingis true on first fetch. - Empty list: when
blockedis 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
MySnackBarerror 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
MySnackBarerror. - 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
targetIdequals 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.