The Android map never drew because mapType was "mutedStandard", an Apple
Maps value. Android's MapManager looks the name up in a fixed table and
unboxes the result into an int, so an unrecognised value threw a
NullPointerException in the native view manager before any tile
rendered. Android now gets "standard" plus a customMapStyle that mutes
POI and transit labels, since showsPointsOfInterest is iOS-only too.
Location hung indefinitely: getCurrentPositionAsync was called with no
accuracy and no timeout, so it waited for a GPS fix that never arrives
indoors or on an emulator with no mock location. useUserLocation now
takes a cached fix first for an immediate render, caps the precise
reading at 15 seconds, and checks device location services separately
from app permission. Reverse geocoding moved off the critical path so a
failed lookup costs the address label rather than the coordinates. The
single boolean became five states, each with its own notice and either a
retry or a settings shortcut, since retrying a hard denial does nothing.
Map also no longer deletes itself when the driver fetch fails or the
location is still pending: drivers are an overlay, and calculateRegion
already falls back to Beirut.
Adds a four-tile service selector above Recent Rides - Car, Moto,
Courier, My Car - with the choice held in useServiceStore for the
booking flow to read. English only for now; the intended Arabic names
are recorded in constants/services.ts for the language pass. Selection
styling matches the role picker on sign-up so the two read as one
control.
app.config.js layers the Google Maps key and expo-location permission
strings onto app.json. No effect under Expo Go, which ignores native
config, but required for the first EAS build.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign-in gains a "Keep me signed in" checkbox: checked issues a 30-day
token and prefills the address next launch, unchecked drops the session
to 12 hours and forgets the address. The TTL is chosen server-side in
the login route.
Emailed codes are now reachable without retyping. OtpField opts into the
iOS one-time-code keyboard suggestion and raises a paste chip when the
user returns from Gmail with a code on the clipboard. The mails put the
code first in the subject and body, which is what makes Gmail render its
"Copy code" notification action at all.
Fixes found along the way:
- Session was wiped on every launch. decodeJwtExp used atob, which
neither RN 0.74 nor Expo SDK 51 defines, so it threw, returned null,
and the caller read that as "expired" and deleted the token. Replaced
with a dependency-free base64url decoder, and restore now only
discards a session it can prove is expired.
- Verification and reset codes counted attempts but never enforced them,
leaving a 6-digit code open to unlimited guessing. Both routes now
charge the attempt before comparing so concurrent guesses can't race
past the cap of five, and compare in constant time.
- A wrong verification code showed the "Verified" success screen:
onModalHide fired unconditionally, so the failure state advanced the
flow. Only an explicit "verified" state does that now.
- fetchAPI discarded the server's error body, so the UI substring-matched
synthetic status strings and showed "Could not sign in" for everything.
It now throws ApiError carrying status and the server's message.
- Login answered a missing account faster than a wrong password; it now
runs the same scrypt work either way.
- Blank email or password is caught client-side instead of surfacing as
an opaque 400, and a failed attempt only clears the password on a 401.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SMTP:
- Add connection/greeting/socket timeouts so a stalled Gmail
connection no longer hangs sign-up
- Wrap sendMail in try/catch and fall back to logging the code
- Derive secure from port (465 implicit TLS vs 587 STARTTLS)
- Strip whitespace from the Gmail app password
- Document SMTP_HOST/SMTP_PORT in .env.example and environment.d.ts
Password reset (new):
- POST /(api)/auth/forgot-password emails a 6-digit code and does
not reveal whether the address is registered
- POST /(api)/auth/reset-password validates the code, sets the new
password, verifies the email, and signs the user in
- password_reset_codes table added to seed-db.mjs
- "Forgot password?" flow on the mobile sign-in screen
User deletion (new):
- DELETE /(api)/admin/users/[id], owner-only, blocks self-deletion
- Delete button with confirmation on the dashboard Users page
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>