plan/react-rewrite #1
@@ -0,0 +1,77 @@
|
||||
# Phase 4 -- Flights Map (Master Plan)
|
||||
|
||||
## Goal
|
||||
|
||||
Port the Angular `flights-map` feature (single `/flights-map` route) to the React/ModernJS codebase as a Module Federation remote component. The map uses Leaflet for interactive marker/polyline rendering and is behind the `flightsMap` feature flag.
|
||||
|
||||
## Angular analysis summary
|
||||
|
||||
- **Route:** `/flights-map` (start page with embedded Leaflet map)
|
||||
- **Feature flag:** `flightsMap` (env-based)
|
||||
- **APIs:** `destinations/v1` (search routes by departure/arrival), `days/.../flights-map/v1` (calendar availability)
|
||||
- **Leaflet map:** markers (blue default, blue-small zoom-dependent, orange selected), polylines (solid direct, dashed connecting), popups, tooltips, zoom-aware layer visibility
|
||||
- **Components:** start page container, map body (sole Leaflet consumer), filter panel, meta tags, loader/empty overlays
|
||||
- **No SignalR** -- data is static, fetched on filter change
|
||||
- **SEO:** meta tags only in Angular; design spec requires JSON-LD `WebPage` schema
|
||||
|
||||
## Sub-plans
|
||||
|
||||
### 4A -- Types + API + Hooks
|
||||
|
||||
**Files:** `src/features/flights-map/types.ts`, `api.ts`, `api.test.ts`, `hooks/useFlightsMapSearch.ts`, `hooks/useFlightsMapCalendar.ts`
|
||||
|
||||
- Define `IFlightRoute`, `IMapMarker`, `IMapPolyline`, `FlightsMapSearchParams`, `FlightsMapCalendarParams`
|
||||
- Define `IDestinationsResponse`, `IFlightsMapDaysResponse`
|
||||
- API: `searchDestinations(client, params)` -> `GET destinations/1`, `getFlightsMapCalendar(client, params)` -> `GET days/{date}/200/{route}/flights-map/v1`
|
||||
- Hooks: `useFlightsMapSearch(params)`, `useFlightsMapCalendar(params)`
|
||||
- Feature flag: `useFeatureFlag("flightsMap")` via env config
|
||||
- TDD: ~10 tests for API functions
|
||||
|
||||
### 4B -- Leaflet Map Wrapper
|
||||
|
||||
**File:** `src/features/flights-map/components/MapCanvas.tsx`
|
||||
|
||||
- **Only file that imports `leaflet`** (design spec constraint)
|
||||
- Wrapped in `React.lazy()` + `<ClientOnly>` (SSR-safe)
|
||||
- Accepts `markers`, `polylines`, `onMarkerClick` etc. as props -- does not own state
|
||||
- Renders markers (blue/orange icons), polylines (solid/dashed), popups, tooltips
|
||||
- Great-circle arc calculation for polyline rendering
|
||||
- Install `leaflet` + `@types/leaflet`
|
||||
- No TDD (Leaflet requires real DOM)
|
||||
|
||||
### 4C -- Route Pages + Feature Components
|
||||
|
||||
**Files:**
|
||||
- `src/routes/[lang]/flights-map/page.tsx` -- route page with SEO + feature flag guard
|
||||
- `src/features/flights-map/components/FlightsMapStartPage.tsx` -- container (filter + map + loading/empty states)
|
||||
- `src/features/flights-map/components/FlightsMapFilter.tsx` -- search filter (departure, arrival, connections, domestic/international toggles, date picker)
|
||||
- `src/features/flights-map/components/ClientOnly.tsx` -- SSR-safe wrapper
|
||||
|
||||
### 4D -- SEO + JSON-LD + Parity
|
||||
|
||||
**Files:**
|
||||
- `src/features/flights-map/seo.ts` -- `buildFlightsMapSeo(t, locale, canonicalOrigin)`
|
||||
- `src/features/flights-map/json-ld.ts` -- JSON-LD `WebPage` schema builder
|
||||
- `src/features/flights-map/seo.test.ts` -- ~5 integration tests
|
||||
- Update barrel `src/features/flights-map/index.ts`
|
||||
- Update `src/mf/expose/FlightsMap.tsx` from stub to real
|
||||
|
||||
## Dependency graph
|
||||
|
||||
```
|
||||
4A (types/api/hooks) --> 4B (map canvas, needs types)
|
||||
--> 4C (pages/components, needs hooks)
|
||||
4A + 4B + 4C --------> 4D (SEO + barrel + MF expose update)
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
pnpm typecheck && pnpm lint && pnpm test && pnpm build:standalone
|
||||
```
|
||||
|
||||
## Constraints
|
||||
|
||||
- Do NOT touch `ClientApp/`, ASP.NET, `wwwroot/`
|
||||
- No `Co-Authored-By` lines in commits
|
||||
- ~8-12 commits total
|
||||
@@ -35,6 +35,7 @@
|
||||
"i18next-icu": "^2.0.0",
|
||||
"i18next-resources-to-backend": "^1.0.0",
|
||||
"intl-messageformat": "^10.0.0",
|
||||
"leaflet": "^1.9.4",
|
||||
"lru-cache": "^10.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
@@ -48,6 +49,7 @@
|
||||
"@testing-library/jest-dom": "^6",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@types/leaflet": "^1.9.21",
|
||||
"@types/node": "^24.0.0",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
|
||||
Generated
+23
@@ -50,6 +50,9 @@ importers:
|
||||
intl-messageformat:
|
||||
specifier: ^10.0.0
|
||||
version: 10.7.18
|
||||
leaflet:
|
||||
specifier: ^1.9.4
|
||||
version: 1.9.4
|
||||
lru-cache:
|
||||
specifier: ^10.0.0
|
||||
version: 10.4.3
|
||||
@@ -84,6 +87,9 @@ importers:
|
||||
'@testing-library/react-hooks':
|
||||
specifier: ^8.0.1
|
||||
version: 8.0.1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react-test-renderer@19.2.5(react@18.3.1))(react@18.3.1)
|
||||
'@types/leaflet':
|
||||
specifier: ^1.9.21
|
||||
version: 1.9.21
|
||||
'@types/node':
|
||||
specifier: ^24.0.0
|
||||
version: 24.12.2
|
||||
@@ -2711,6 +2717,9 @@ packages:
|
||||
'@types/estree@1.0.8':
|
||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||
|
||||
'@types/geojson@7946.0.16':
|
||||
resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==}
|
||||
|
||||
'@types/html-minifier-terser@6.1.0':
|
||||
resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==}
|
||||
|
||||
@@ -2726,6 +2735,9 @@ packages:
|
||||
'@types/json-schema@7.0.15':
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
|
||||
'@types/leaflet@1.9.21':
|
||||
resolution: {integrity: sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==}
|
||||
|
||||
'@types/loadable__component@5.13.10':
|
||||
resolution: {integrity: sha512-2/LjmgG1JcGPj7T3NViq7BB5cvOA0s63gL3Gv+FPULj2L+3ExWfsNQcsFPUIOoGsVUJeZxgNPf320JZDyxjtCQ==}
|
||||
|
||||
@@ -4435,6 +4447,9 @@ packages:
|
||||
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
leaflet@1.9.4:
|
||||
resolution: {integrity: sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==}
|
||||
|
||||
levn@0.4.1:
|
||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -9701,6 +9716,8 @@ snapshots:
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
|
||||
'@types/geojson@7946.0.16': {}
|
||||
|
||||
'@types/html-minifier-terser@6.1.0': {}
|
||||
|
||||
'@types/istanbul-lib-coverage@2.0.6': {}
|
||||
@@ -9715,6 +9732,10 @@ snapshots:
|
||||
|
||||
'@types/json-schema@7.0.15': {}
|
||||
|
||||
'@types/leaflet@1.9.21':
|
||||
dependencies:
|
||||
'@types/geojson': 7946.0.16
|
||||
|
||||
'@types/loadable__component@5.13.10':
|
||||
dependencies:
|
||||
'@types/react': 18.3.28
|
||||
@@ -11708,6 +11729,8 @@ snapshots:
|
||||
|
||||
kind-of@6.0.3: {}
|
||||
|
||||
leaflet@1.9.4: {}
|
||||
|
||||
levn@0.4.1:
|
||||
dependencies:
|
||||
prelude-ls: 1.2.1
|
||||
|
||||
Reference in New Issue
Block a user