Skip to content

Guided walkthroughs

A guided walkthrough is an in-app product tour — a sequence of spotlight tooltips that introduces a first-time agent to the portal. It exists in the broker and sales-man apps only (never the shopper or analytics apps). The tour is built on react-joyride and is driven by a shared component in @orascom/common-components.

Whether the tour runs is decided by two gates that work together:

  • A client-side flag in the browser — localStorage key WALKTHROUGH_LOCALSTORAGE_KEY (literal value 'walkthroughStep', frontend/libs/common-components/src/lib/walkThrough/Constants.tsx:1). This flag doubles as a resume pointer: it stores the current step number ('1', '2', …), not just a boolean.
  • A durable per-user status on the backend — the is_walkthrough_done boolean column. Once it is true, the flag is never re-seeded on future logins, so the tour never shows again.

The broker dashboard — the tour’s first step (“Welcome to Orascom Broker Portal”) opens here, anchored to the page body, before walking the agent through destinations, units, deals, and analytics.

sequenceDiagram
actor U as Agent (Broker / Sales-Man)
participant SPA as Agent SPA
participant LS as localStorage
participant JR as Joyride tour
participant API as Backend (Laravel)
participant DB as users / sales_team
U->>SPA: First login
SPA->>API: GET /user (userDataLoader)
API-->>SPA: user.is_walkthrough_done = false
SPA->>LS: setItem walkthroughStep = 1
Note over SPA,JR: tour runs only while the flag is truthy
JR->>U: Step 1 Welcome (anchored to page body)
loop each step
  JR->>LS: setItem walkthroughStep = stepIndex + 1
  JR->>SPA: navigate to the step route
end
U->>JR: Finish or Skip
JR->>LS: removeItem walkthroughStep
JR->>SPA: set is_walkthrough_done true in user context
JR->>API: PUT user/complete-walkthrough (fire-and-forget)
API->>DB: is_walkthrough_done = true
JR->>SPA: navigate home (replace)
Note over API,DB: next login returns done = true so the flag is never re-seeded
Guided walkthrough — first-login seed, tour run, completion write-back (verified)
  1. First login seeds the flag — both apps run a userDataLoader on the authenticated route. It fetches GET /user, then: if user.is_walkthrough_done is already true it removes the flag; otherwise, if no flag exists yet, it sets WALKTHROUGH_LOCALSTORAGE_KEY to '1' (broker frontend/apps/orascom-broker-app/src/api/user-data-loader.ts:24-28; sales-man frontend/apps/orascom-sales-man-app/src/api/user-data-loader.ts:33-37). So the backend status is what decides whether a fresh browser gets seeded at all.

  2. The tour only runs while the flag is truthy — the shared WalkthroughComponent initialises its currentStep from Number(localStorage.getItem(WALKTHROUGH_LOCALSTORAGE_KEY)) || undefined (frontend/libs/common-components/src/lib/walkThrough/walkthrough-component/walkthrough-component.tsx:60-62) and gates the Joyride with run={isJoyrideRunning && run && !!currentStep} (:144). No flag ⇒ currentStep is undefined ⇒ the tour does not start. It is mounted via createPortal from each app’s main-layout (broker .../layouts/main-layout/main-layout.tsx:87-97; sales-man :102-112).

  3. Each step persists the pointer and navigates — while status === 'running', the callback writes the new step number back to localStorage (walkthrough-component.tsx:91-102) and navigates to that step’s data.route (:104-120). Because the pointer is stored, a mid-tour reload resumes at the saved step rather than restarting. The step list lives in each app’s utils/walkthroughSteps.ts / utils/walkThroughSteps.ts (e.g. broker brokerSteps, frontend/apps/orascom-broker-app/src/utils/walkthroughSteps.ts:9), and each spotlight targets a WALKTHROUGH_TARGETS_IDS anchor (Constants.tsx:3).

  4. Direct-URL access to a tour page is blocked when the flag is unset — the dedicated walkthrough routes redirect home unless the flag is present. Broker: hitting WALKTHROUGH_DEAL_DETAILS or WALKTHROUGH_UNIT without the flag ⇒ Navigate to the dashboard (frontend/apps/orascom-broker-app/src/components/protected-routes/protected-routes.tsx:76-82). Sales-man: WALKTHROUGH_UNIT or WALKTHROUGH_PIPELINE without the flag ⇒ Navigate to the overview (frontend/apps/orascom-sales-man-app/src/components/protected-routes/protected-routes.tsx:104-112). You cannot deep-link into a tour page.

  5. Finish or Skip clears the flag and writes the backend status — on status === 'finished' or 'skipped' the callback runs, in order: removeItem(WALKTHROUGH_LOCALSTORAGE_KEY)updateUserStateOnFinish?.() (sets is_walkthrough_done: true on the in-memory user context) → updateWalkthroughStatus?.() (the backend call) → navigate('/', { replace: true }) (walkthrough-component.tsx:72-78). The backend call is not awaited — it is fire-and-forget (see Gotchas).

  6. The backend call marks the user doneupdateWalkthroughStatus() issues PUT /user/complete-walkthrough (endpoint updateUserWalkthroughStatus, broker frontend/apps/orascom-broker-app/src/api/endpoints.ts:219-222; sales-man frontend/apps/orascom-sales-man-app/src/api/endpoints.ts:121-124; broker call frontend/apps/orascom-broker-app/src/api/user.ts:42-47). Server-side the route sits inside the auth:sanctum group (backend/routes/api.php:26-29, route at :28) and hits UserController@completeWalkThrough, which flips the column only if it is still false and returns respondSuccess() (backend/app/Http/Controllers/Api/Auth/UserController.php:52-61). There is no dedicated Action — the one-line update is inline in the thin controller. The column is is_walkthrough_done (boolean, default false), added by two migrations: users for brokers (backend/database/migrations/2025_03_03_113729_add_column_is_walkthrough_done_to_users_table.php:14) and sales_team for sales-men (backend/database/migrations/2025_03_03_114302_add_column_is_walkthrough_done_to_sales_team_table.php:14). It is $fillable on both models (backend/app/Models/User.php:39, backend/app/Models/SalesMan.php:25) and returned by UserResource (backend/app/Http/Resources/UserResource.php:30) so GET /user can report it back on the next login.

Each dedicated tour page renders hardcoded static dummy data (staticUnit / staticLead / staticPipeline) rather than fetching, so it loads instantly and is safe to render without live records. The ids embedded in the paths (1/0/3, pipelines/1) are placeholders, not real records.

App Route (ROUTES_MAPPER) Component Renders
Broker /unit-details/walkthrough (WALKTHROUGH_UNIT, routesMapper.ts:52) pages/walkthroughUnit/WalkthroughUnit.tsx (route routes.tsx:161) Static unit-details layout — gallery, info, floor plan, payment plan, specs
Broker /deal-details/walkthrough/1/0/3 (WALKTHROUGH_DEAL_DETAILS, routesMapper.ts:51) pages/walkthroughDealDetails/WalkthroughDealDetails.tsx (route routes.tsx:283) Static deal-details with breadcrumbs, meetings, invoice, lead-info card
Sales-Man /unit-details/walkthrough (WALKTHROUGH_UNIT, routesMapper.ts:43) pages/walkthroughUnit/WalkthroughUnit.tsx (route routes.tsx:267) Static unit-details layout (no payment-plan block)
Sales-Man /leads/walkthrough/pipelines/1 (WALKTHROUGH_PIPELINE, routesMapper.ts:44) pages/walkthroughPipeline/WalkthroughPipeline.tsx (route routes.tsx:275) Static pipeline-details via PipelineDetailsStatic

The rest of the tour steps point at real pages (dashboard, destination details, primary units, new-deal, analytics) — the dedicated static pages above exist only for the steps where showing a live, fetched record would be slow or require data the new agent does not have yet. Those tour-driving routes plus NEW_DEAL are also listed in each layout’s WALKTHROUGH_ROUTES array (broker main-layout.tsx:24-28; sales-man :30-34) so the tour is allowed to run over them without waiting for their queries to settle.