# Visit Syria: API integration plan

Connects the Next.js frontend to the Laravel backend (`../backend`, base URL `/api/v1`), one website section at a time. Each section goes through the same order: **API module, zustand store, UI, link, test**. The next section starts only after the current section's test gate passes.

## Verified backend facts (checked against a running instance)

- Backend runs locally on `http://127.0.0.1:8000` (SQLite, seeded: 74 places, 7 events, 9 restaurants). Production DB is MySQL.
- Envelope: `{ success, message, data, meta? }`. Paginated lists add `meta { current_page, last_page, per_page, total, from, to }`. Errors: `{ success: false, message, errors }`.
- Locale: `Accept-Language: en` returns English content; the default is Arabic. The frontend sends the site language on every request.
- CORS is open (`allowed_origins: *`), so the browser can call the API directly.
- Auth: Sanctum bearer token (`Authorization: Bearer <token>`). Login and register return `{ user, token }`.
- Required params: `map/pois` needs `north, south, east, west`; `reviews` needs `reviewable_id`; `nearby` needs `lat, lng`.
- Public GET endpoints all return 200 in the probe: home, cities, restaurants, cafes, events, climate/seasons, cms/sections, faqs, travel-deals, entertainment-activities, tour-guides, experiences, destinations, embassies, important-numbers, medical/centers, investment/sectors, cultural-venues, neighborhoods, airports, transport-providers, accommodations, videos, event-organizers, celebration-venues.

## Conventions

- `src/lib/api/client.ts`: one `fetch` wrapper. Reads `NEXT_PUBLIC_API_URL`, adds `Accept-Language` and the bearer token, unwraps `data`, returns `meta`, and throws a typed `ApiError` with `message` and `errors`.
- `src/lib/api/<section>.ts`: one module per section, with typed functions and the response types next to them.
- `src/store/<section>.ts`: one zustand store per section (list, detail, filters, loading, error). Stores hold server data and UI filters. Components read from the store and never call `fetch` directly.
- `src/components/<page>/`: UI components. They render store state, with the current static content from `src/data/*` as the fallback when the API is unreachable.
- `NEXT_PUBLIC_API_URL` is baked in at build time (static export). Local default: `http://127.0.0.1:8000/api/v1`.
- Tokens live in `localStorage` (per-viewer convenience, wrapped in try/catch), not in the store.

## Phase 0: Foundation

**Build**
1. `npm i zustand`.
2. `src/lib/api/client.ts`, `src/lib/api/types.ts` (envelope, pagination, `Localized` name type).
3. `src/lib/api/config.ts` for `NEXT_PUBLIC_API_URL`, plus `.env.example`.
4. `src/store/createListStore.ts`: a shared factory for list + detail + filters + status, so each section store stays small.

**Test gate 0**
- `npx tsc --noEmit` passes.
- A smoke script (`scripts/api-smoke.mjs`, run with `node`) calls `/home`, `/restaurants` and `/events` and checks the envelope shape.
- A bad base URL produces a typed `ApiError`, not an unhandled rejection.
- Gate passes when all three checks pass.

## Section 1: Life and Fun (food, events, exhibitions, entertainment)

**Endpoints:** `restaurants`, `restaurants/{placeId}`, `cafes`, `cafes/{placeId}`, `events` (filters `city_id`, `category_id`, `from`, `to`, `is_free`, `price_max`, `q`, `per_page`), `events/{event}`, `entertainment-activities`, `entertainment-activities/{placeId}`.

**Build**
- Food and Beverage page: restaurants and cafés from the API. The toggle and city filter map to `city_id`. The price and open-now filters need backend support (see open questions), until then they filter the loaded list on the client.
- Festivals & Events and Exhibitions pages: `events` with `from`, `to`, `is_free`, `q`. Exhibitions depends on an event category; if none exists, the page keeps its empty state.
- Entertainment: a new Experiences section on the Life and Fun hub.
- Detail views for a single restaurant or event (optional for this section).

**Test gate 1**
- Smoke: each endpoint returns 200 with the expected `data` shape.
- Browser (headless Chrome, 1440 and 390 widths): restaurant and café toggle shows live results; each filter changes the list; `is_free` and date range filter events; empty state shows when a filter matches nothing.
- Fallback: with the API URL pointing at a dead port, the page still renders the static list and shows no unhandled error.
- No console errors; no horizontal scroll at 390px.
- Gate passes when all checks pass. Stop and report before Section 2 if any fail.

## Section 2: Home

**Endpoints:** `home` (stats), `cities`, `destinations`, `experiences`, `tour-guides`, `travel-deals`, `events` (preview).

**Build:** hero stats from `home`, destinations and experiences cards, guides and deals rows, events preview wired to the Phase-1 store.

**Test gate 2:** smoke on all six endpoints; browser check of the home sections at 1440, 1024, 768 and 390; dead-API fallback still renders the static home content; no console errors.

## Section 3: Explore Syria

**Endpoints:** `cities`, `cities/{city}`, `cultural-venues`, `neighborhoods`, `tour-guides`, `cms/sections`, `cms/historical-events`, `map/pois` (bounds), `nearby` (`lat`, `lng`, `radius_km`).

**Build:** About Syria content from `cms/sections` and `cms/historical-events`; History & Heritage and Arts & Culture from `cultural-venues`; Neighborhoods from `neighborhoods`; the map page from `map/pois` with bounds from the viewport.

**Test gate 3:** smoke with valid bounds (`map/pois` needs all four); browser check of the map (markers render, bounds update on pan); About Syria shows CMS content; fallback check; no console errors.

## Section 4: Services and Information

**Endpoints:** `embassies`, `airports`, `transport-providers`, `important-numbers`, `faqs`, `medical/destinations`, `medical/centers`, `medical/specialties`, `climate/seasons`, `climate/weather-tips`, `climate/forecast`, `investment/sectors`, `POST investment/requests`.

**Build:** directory lists for embassies, airports, transport and important numbers; FAQ accordion; medical centers with specialty filter; climate seasons and forecast; investment sector list and an inquiry form that POSTs to `investment/requests`.

**Test gate 4:** smoke on every GET; the investment form submits a test request in a local environment and gets a success envelope (form tests use the local dev host only); form validation errors show inline; fallback check; no console errors.

## Section 5: Deals

**Endpoints:** `travel-deals`, `travel-deals/{travelDeal}`.

**Build:** deal list and detail from the store.

**Test gate 5:** smoke; list and detail render; fallback; no console errors.

## Section 6: Accounts (auth, favorites, reviews)

**Endpoints:** `auth/register`, `auth/login`, `auth/verify-email`, `auth/resend-verification`, `auth/forgot-password`, `auth/verify-reset-code`, `auth/reset-password`, `auth/google`, `auth/me`, `auth/profile`, `auth/logout`, `favorites`, `favorites/toggle`, `reviews` (GET with `reviewable_id`, POST).

**Build:** auth store (token, user, status), login and register forms, forgot and reset flow, favorites toggle on cards, review form on detail pages (pending approval state shown).

**Test gate 6:** smoke with the demo account (`demo@hellosyria.test`, local only); register, login, logout and profile work; favorites toggle persists after reload; a new review shows as pending; logged-out users are sent to login for favorites; no console errors. Tokens are never printed to logs.

## Section 7: Assistant and search

**Endpoints:** `assistant/chat` (POST).

**Build:** chat panel using the assistant store; search uses `q` on the list endpoints.

**Test gate 7:** smoke POST returns an answer envelope; chat renders messages and an error state; search results update; no console errors.

## Definition of done (every section)

1. `npx tsc --noEmit` passes.
2. `npm run build` passes (static export).
3. The section's smoke script passes against the running backend.
4. Headless browser checks at 1440, 1024, 768 and 390 pass: data renders, filters work, no horizontal scroll.
5. The dead-API fallback renders without errors.
6. No console errors.
7. Results are reported before the next section starts.

## Open questions and risks

- **Restaurant price and open-now filters:** the restaurants endpoint has `city_id` and `q` only. Options: client-side filtering (current plan) or a backend change adding `price` and `open_now`.
- **Exhibitions:** no exhibition category is confirmed in the backend. The page stays empty until one exists.
- **`type` field on lists:** list items return `type: null` (the subtype relation is not loaded in the list). The frontend uses the endpoint, not `type`, to know the kind.
- **Localisation:** the default is Arabic. The site is English-first, so every request sends `Accept-Language`. The Arabic site is out of scope for these phases.
- **Local database:** the backend is verified on SQLite. MySQL is the configured production driver; run the same gates on MySQL before release.
- **Static export and API:** the API must be reachable from the production origin. CORS is open today; tighten it to the site origin before launch.
- **Demo content:** seeded images may come from remote URLs; the `media` slots fall back to placeholders when a path is missing.
