The frontend monorepo
What it is
Section titled “What it is”The frontend/ repo is an Nx 21 monorepo (nx.json, nx@21.0.3 in package.json) that houses
four separate React 18 single-page apps — one per persona — plus five shared libraries they
all import through @orascom/* TypeScript path aliases. Every app is its own deployable and every app
talks to the same Laravel backend repo; what differs per app is an env-driven endpoint prefix and
its own base-host env var, so one API host can serve all four personas without the browser ever calling
a CRM or middleware directly.
Stack (from package.json): React 18.3.1, Vite 6.3.5, TypeScript 5.7.3, Yarn 1
(yarn@1.22.22, classic — not pnpm/npm), TanStack React Query 5.84.1, react-router-dom 6.11.2
(createBrowserRouter), @sentry/react 7.112.2, i18next 21.9.2 + react-i18next 11.18.6,
and Formik 2.2.9 + Yup 0.32.11 for forms. The build graph is driven by the @nx/vite,
@nx/eslint, and @nx/cypress plugins (nx.json plugins).
The four apps
Section titled “The four apps”Each app lives under apps/, has its own router basename env var (the “path” var below), its own
API base-host env var, its own localStorage token key, and its own Sentry DSN. Follow the link for
the per-app deep-dive.
| App (deep-dive) | One-line role | Router basename env | API base-host env |
|---|---|---|---|
Shopper — apps/orascom-shopper-app |
Customer-facing portal: browse, reserve & pay online, EOI payment, post-purchase Pre-Delivery owner area | NX_SHOPPER_PATH |
NX__API_ENDPOINT |
Broker — apps/orascom-broker-app |
External brokers selling ODH units on commission; the same build also serves sales-managers by role | NX_BROKER_PATH |
NX__BROKER_API_ENDPOINT |
Sales-man — apps/orascom-sales-man-app |
Internal Orascom sales-staff CRM: leads → pipelines → customers → sales, plus EOI and Taskeen | NX_SALES_MAN_PATH |
NX__SALESMAN_API_ENDPOINT |
Shopper Analytics — apps/orascom-shopper-analytics-app |
Internal BI dashboards over shopper-portal data (sales, leads, engagement, demographics, funnel) | NX_SHOPPER_ANALYTICS_PATH |
NX__SHOPPER_ANALYTICS_API_ENDPOINT |
All four share one architecture: src/api/routes.tsx (route tree) → src/utils/routes-mapper.ts
(path constants) → src/pages/* (screens), with a thin Network.fetch HTTP layer (src/api/network.ts)
over a flat ENDPOINTS map (src/api/endpoints.ts).
Shared libraries
Section titled “Shared libraries”Five Nx libraries live under libs/, imported by the apps via the @orascom/* aliases configured in
tsconfig.base.json. Always import through the alias and the lib’s src/index.ts barrel — never deep-import
../../../libs/... (frontend/CLAUDE.md § 4).
@orascom/api-interfaces(libs/api-interfaces) — Shared TypeScript types/interfaces for the whole platform: the HTTPPayload/Routeshapes (api-interfaces.ts,route.interface.ts) pluscommon,unit,destination,country,reservation,user,eoi, andoman-registrationinterfaces. Pure types, no runtime code. When a change represents an API shape, the type goes here.@orascom/common-components(libs/common-components) — The cross-app React UI kit used by all four apps: primitives (button,card,select/filter-select,modal/generic-modal,loader/spinner,tooltip,accordion,pagination,range-slider/price-range-slider,cards-slider,thumbs-gallery,video,language-dropdown); auth UI (login-component,set-password,forget-password,auth-wrapper); analytics UI (analytics-calendar/analytics-filter/analytics-period-filter+ theuse-analytics-*hooks); the sales-offer calculators (sales-offer-payment-calculator,-installment-plan,-floor-plan, plus Montenegro variants); thewalkThroughguided-tour system; and shared contexts (user-context,reservation.context).@orascom/broker-sales-man-common-components(libs/broker-sales-man-common-components) — Components shared specifically between the Broker and Sales-man CRM portals: the full EOI UI (eoi-listing,eoi-details,eoi-card,eoi-add-units,eoi-add-preference,create-eoi-component); destination detail UI (destination-details-uiand its-facilities/-location/-projects/-masterplan/-videoparts); unit UI (unit-details,unit-payment-calculator/-plan,unit-specs,similar-units,property-card); compare (compare-button,compare-properties); Taskeen (taskeen-card,taskeen-table,taskeen-details-modal); pluslaunch-details,header,notifications-wrapper,change-logo-modal(whitelabel), and thecompare-units/country-selectedcontexts.@orascom/common-styles(libs/common-styles) — Shared SCSS:styles.scssplus astyles/folder of design tokens, variables, mixins, and breakpoints, imported by each app’s ownstyles.scss. Consumed as an SCSS import rather than a JS symbol.@orascom/utils(libs/utils) — Shared runtime utilities, hooks and contexts: the React Query wiring (query-client→queryClient/mutationCache, used in each app’smain.tsx);constructURL/errorMessagesHandlerandtype-guards(utils.ts); the currency system (currency.context+CurrencyContextProvider,use-currency-context/-converter,exchange.rates.utils,use-restrict-currency-to-gouna); i18nchangeLanguage; filter helpers (get-filters-query-string,use-unit-filters,use-price-slider); Taskeen helpers; and generic hooks (use-window-dimensions,use-media-query,use-disclosure,use-download,payment-plan-calculator).
Cross-app conventions
Section titled “Cross-app conventions”The four apps deliberately look almost identical inside; the differences are all pushed to environment variables so nothing persona-specific is hard-coded.
- Env-driven, per-persona endpoint prefixes. Every request is built as
base-host + endpoint-prefix + path. The base host is the app’s own*_API_ENDPOINT; the prefix is its own*_ENDPOINT_PREFIX. Shopper usesNX__SHOPPER_ENDPOINT_PREFIX(plusNX__PRE_DELIVERY_ENDPOINT_PREFIXfor the owner area); Broker uses two prefixes in one build —NX__BROKER_ENDPOINT_PREFIXandNX__SALES_MANAGER_ENDPOINT_PREFIX; Sales-man usesNX__SALESMAN_ENDPOINT_PREFIX; Analytics usesNX__SHOPPER_ANALYTICS_ENDPOINT_PREFIX. A few endpoints (currency,/user, some cross-portal/shopper/*lookups) are deliberately prefix-less. Persona endpoints are mutually exclusive — don’t copy a call between apps; lift the shared shape into@orascom/api-interfaces(frontend/CLAUDE.md§ 2.6). - No refresh tokens anywhere. No app implements refresh-token rotation. Each app’s
userDataLoaderfetches/me(or/user); on a missing or expired token it removes the token fromlocalStorageandredirects to that app’s/login. Token keys are persona-scoped (NX_SHOPPER_ACCESS_TOKEN_KEY,NX_BROKER_ACCESS_TOKEN_KEY,NX_SALESMAN_ACCESS_TOKEN_KEY,NX_SHOPPER_ANALYTICS_ACCESS_TOKEN_KEY) so two personas open in the same browser profile can’t read each other’s tokens — don’t collapse them. See the auth model for the four distinct login flows (phone+OTP, email+password, Microsoft SSO, email+password). - React Query in three of four apps — not analytics. Shopper, Broker, and Sales-man each wrap their
tree in
QueryClientProvider(thequeryClientcomes from@orascom/utils). Shopper Analytics does NOT use React Query — its filter state lives in a plainFILTER_CONTEXTand data is fetched through service classes directly. ProtectedRoutes+userDataLoadergating. Every non-auth route group is wrapped in<ProtectedRoutes>(re-checks the token per navigation) and runs a route-loaderuserDataLoader(loads the current user and redirects to/loginwhen unauthenticated).NotFoundPageis theerrorElementfor each group.- Per-app Sentry DSN. Each
src/main.tsxinits@sentry/react(reactRouterV6 tracing + replay) with its own DSN env var —NX_SENTRY_SHOPPER_DSN,NX_SENTRY_BROKER_DSN,NX_SENTRY_SALESMAN_DSN,NX_SENTRY_ANALYTICS_DSN. Source-map upload at build time is handled bytools/orascom-sentry-vite-plugin.ts.
Running / building
Section titled “Running / building”Nx targets are exposed both as package.json shortcuts and directly via yarn nx <target> <project>.
Use the Nx graph (don’t invoke Vite directly) so caching and the affected-graph behave (frontend/CLAUDE.md § 2).
# install (Yarn 1 classic only — never npm/pnpm)yarn install
# per-app dev serversyarn start # shopper (port 4200)yarn start:broker # broker (port 4201)yarn start:sales-manyarn start:shopper-analytics# equivalently:yarn nx serve orascom-shopper-app
# per-app production buildsyarn build # shopperyarn build:brokeryarn build:sales-manyarn build:shopper-analytics# equivalently:yarn nx build orascom-broker-app
# lint / test / CI-equivalent local checkyarn nx lint orascom-shopper-appyarn nx test orascom-shopper-appyarn nx affected -t lint test build # what changed vs. main
# inspect the workspaceyarn nx graphyarn nx show project orascom-shopper-app --jsonGotchas
Section titled “Gotchas”README.mdis wrong about the app count (says 2, there are 4). Trustapps/andpackage.json.- Yarn 1 only. The lockfile is
yarn.lock; do not runnpm installor migrate to pnpm, and don’t add a non-Nx-aware bundler (Bun, standalone Turbopack) — Nx caching depends on its own runners (frontend/CLAUDE.md§ 2, § 12). - Vite inlines every
NX_*/VITE_*var into the JS bundle. A secret placed in a populated.envships to every browser — never commit one, and never make a direct browser call to a CRM or middleware. - The
NX_CURRENY_LAYER_*misspelling is intentional.CURRENY(missing the secondC) is baked into every populated env; “fixing” the spelling silently breaks currency config everywhere. Rename only as one coordinated change across.env.example, all envs, and code. @types/reactis19.xwhile the runtime React is18.3.1. This mismatch is intentional and fragile (frontend/CLAUDE.md§ 12) — don’t “align” the types to 19 or bump React to 19 without a coordinated check.tsconfig.base.jsonhas a dangling@orascom/apialias pointing at a non-existentlibs/api. Ignore it.- The Nx Cloud
accessTokeninnx.jsonis a committed read-write token (frontend/CLAUDE.md§ 12, andPROJECT-OVERVIEW.md§ 9). Treat it as leaked — it needs rotating out of the repo into a CI-only env var. - No SSR / Next.js. The apps are SPA + Vite by design; moving to SSR would require coordinated backend caching work.
