Skip to content

feat(pwa): closed-resto sheet at load (next-opening + stale-tab) + reusable delivery-address sheet on disabled Livraison#490

Merged
alexpmichelet merged 3 commits into
mainfrom
agent/availability-and-delivery-ux
Jun 11, 2026
Merged

feat(pwa): closed-resto sheet at load (next-opening + stale-tab) + reusable delivery-address sheet on disabled Livraison#490
alexpmichelet merged 3 commits into
mainfrom
agent/availability-and-delivery-ux

Conversation

@alexpmichelet

Copy link
Copy Markdown
Contributor

Two related UX overhauls in the PWA Client (apps/web), one PR.

Feature A — Closed-restaurant UX, detected at LOAD (Uber-Eats parity)

Previously a customer had to enter an address before discovering the resto was closed (the hors_horaire verdict only came after the quote chain). The opening hours are known at page load, so we surface a « Resto fermé » bottom sheet immediately and bypass the address-first form when closed — a delivery quote is pointless when closed.

  • Backend readServiceStatus (new publicTenantQuery in lib/delivery/quote.ts) returns { isOpen, windows }, reading only through the sanctioned listTenantServiceWindows seam (ADR 0010). readServiceOpen left intact.
  • Pure core lib/availability/decide-next-opening.ts: clientIsOpenNow, decideNextOpening (next window-start strictly after now — later today / tomorrow / later this week / wrap to next week / no windows → null, DST-correct Europe/Paris) and the FR label (Réouverture aujourd'hui à 18h00 / demain à 11h30 / lundi à 11h30 / Horaires non communiqués). TDD'd (18 cases).
  • Bypass-address-when-closed: on /, <AddressFirstHome> overlays the dismissable closed sheet; « Voir la carte » routes to /menu for read-only browse. Ordering/checkout stays blocked by the existing isOpenNow/hors_horaire gate (safety net untouched).
  • Stale-tab / closing-while-browsing: <ServiceStatusProvider> combines the Convex reactive subscription on readServiceStatus (an admin editing hours propagates live) with a ~30s client interval re-evaluating clientIsOpenNow against the wall clock — because a natural 22:00 closing crosses the clock with no data change, so reactivity alone would never push it. On flip open→closed the sheet appears mid-browse; on closed→open it dismisses.

Feature B — Reusable delivery-address sheet on a disabled « Livraison » tap

A disabled Livraison button is no longer a dead tap.

  • decideDisabledDeliveryTap (pure predicate): closed → defer to the closed sheet; verdict null → address sheet PROMPT mode (« Renseigne ton adresse »); hors_zone/surge → address sheet EDIT mode, pre-filled (« Modifie ton adresse »); hors_horaire verdict → defer to closed UX.
  • <DeliveryAddressSheet> replays the address→quote chain. On a deliverable verdict it dispatches the new ADOPT_VERDICT reducer action (enable + select Livraison, persist to localStorage) and closes — no page reload. On a refusal it shows the reason inside the sheet and stays open to try another address.
  • DRY: the address→quote chain and the Places widget are extracted into useAddressQuoteChain + usePlacesAutocomplete, consumed by BOTH <AddressFirstForm> and the sheet. The Google Places loader is still brought in via a DYNAMIC import() inside a useEffect (guardrail googlemaps-loader-ssr-bug — never a static top-level import). <AddressFirstForm> refactor is behaviour-preserving: the validated A.1 flow (address → deliverable → palier 1 Wallet card → /menu) is unchanged.

Tests

TDD: test:feat:refactor:. Pure functions, the ADOPT_VERDICT transition, the disabled-tap predicate, and the backend query contract + tenant isolation are all unit-pinned. Full suites green locally: web 373, backend 1665; typecheck + lint clean (0 errors). No new npm deps; no shared barrel edits.

Manual checks

  1. Seed service hours closed → ouvrir / → bottom sheet « Resto fermé · réouverture à X » SANS saisir d'adresse ; « Voir la carte » → menu parcourable.
  2. Onglet ouvert quand l'heure de fermeture est franchie → le sheet apparaît tout seul (~30s).
  3. Lien direct /menu onglet frais → taper « Livraison » → sheet adresse (prompt) → adresse livrable → « Livraison » s'active sans reload.
  4. Adresse hors_zone → taper « Livraison » → sheet pré-rempli (edit) pour modifier l'adresse.

🤖 Generated with Claude Code

alexpmichelet and others added 3 commits June 11, 2026 23:08
…_VERDICT + readServiceStatus

Red specs (TDD) for both features:
- decideNextOpening / formatNextOpeningLabel / clientIsOpenNow (Feature A pure core)
- decideDisabledDeliveryTap predicate (Feature B routing)
- delivery-mode reducer ADOPT_VERDICT transition (Feature B)
- backend readServiceStatus public query contract + tenant isolation (Feature A.1)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Feature A — closed-restaurant UX detected at LOAD (Uber-Eats parity):
- backend readServiceStatus public query exposes { isOpen, windows }
- lib/availability pure core: clientIsOpenNow + decideNextOpening (next
  window-start in Europe/Paris: today/tomorrow/this-week/wrap) + FR label
- <ServiceStatusProvider>: Convex reactive sub + ~30s wall-clock tick so a
  natural closing (no data change) flips the UI mid-browse (stale-tab)
- <ClosedRestoSheet>: dismissable bottom sheet, bypasses the address form
  when closed, browse menu read-only (checkout gate left as safety net)

Feature B — reusable delivery-address sheet on a disabled Livraison tap:
- decideDisabledDeliveryTap predicate (closed-sheet / prompt / edit / none)
- ADOPT_VERDICT reducer transition (enable + select Livraison, persist)
- shared usePlacesAutocomplete + useAddressQuoteChain hooks (DRY)
- <DeliveryAddressSheet>: replays the chain, adopts a deliverable verdict
  without reload, keeps open with a message on a refusal

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… surfaces

- <AddressFirstForm> now consumes usePlacesAutocomplete + useAddressQuoteChain
  (behaviour-preserving: A.1 address → deliverable → Wallet card → /menu intact)
- <DeliveryModeProvider> consumes the extracted reducer + exposes adoptVerdict
  (persist + dispatch ADOPT_VERDICT)
- <DeliveryModeToggle> takes tenantId, routes a disabled Livraison tap via
  decideDisabledDeliveryTap to the closed sheet or the address sheet
- menu-view / panier-body / app/page wrap the toggle in <ServiceStatusProvider>
  and mount <ClosedRestoSheet>; home uses <AddressFirstHome> wrapper

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
kitchen-boost-repo-admin Ready Ready Preview, Comment Jun 11, 2026 9:14pm
kitchen-boost-repo-web Ready Ready Preview, Comment Jun 11, 2026 9:14pm

@alexpmichelet alexpmichelet merged commit 613f658 into main Jun 11, 2026
4 checks passed
@alexpmichelet alexpmichelet deleted the agent/availability-and-delivery-ux branch June 11, 2026 21:15
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.

1 participant