Extract schedule \$.tsx body to a readable module

Modern.js routes catch-all paths via a file literally named \`\$.tsx\`.
The name is framework-mandated but cryptic at a glance. Move the real
component to src/features/schedule/ScheduleDetailsCatchAllRoute.tsx
and make \$.tsx a 12-line re-export, so every grep/import/stack-frame
shows the readable name and the \$.tsx file stays just a framework
shim.
This commit is contained in:
2026-04-18 12:52:13 +03:00
parent b01fc2f0c9
commit 96adf785aa
2 changed files with 92 additions and 76 deletions
@@ -0,0 +1,84 @@
/**
* Schedule flight-details catch-all route.
*
* This module backs the framework-mandated `src/routes/[lang]/schedule/$.tsx`
* splat file. The `$.tsx` filename is how Modern.js (via react-router) marks
* a route that captures every trailing segment, so we keep it as a thin
* re-export and put the real code under a readable path.
*
* Captures variable-length paths for multi-flight chains, e.g.
* /{lang}/schedule/{flight1-date}[/{flight2-date}]...
* Also handles the airport-code detail variant:
* /{lang}/schedule/{depCode}/{flight-date}/{arrCode}/...
*/
import { lazy, Suspense } from "react";
import { useParams } from "@modern-js/runtime/router";
import { useTranslation } from "@/i18n/provider.js";
import { parseFlightUrlParams } from "@/features/online-board/url.js";
import { FlightListSkeleton } from "@/ui/flights/FlightListSkeleton.js";
import { getEnv } from "@/env/index.js";
import type { IScheduleFlightId } from "./types.js";
const ScheduleDetailsPage = lazy(() =>
import("./components/ScheduleDetailsPage.js").then(
(m) => ({ default: m.ScheduleDetailsPage }),
),
);
/**
* Parse the catch-all segments into flight IDs.
* Segments that are 3 chars or less are treated as airport codes and skipped.
*/
function parseFlightSegments(segments: string[]): IScheduleFlightId[] {
const flights: IScheduleFlightId[] = [];
for (const segment of segments) {
if (segment.length <= 3) continue;
const parsed = parseFlightUrlParams(segment);
if (parsed) {
const flight: IScheduleFlightId = {
carrier: parsed.carrier,
flightNumber: parsed.flightNumber,
date: parsed.date,
};
if (parsed.suffix !== undefined) {
flight.suffix = parsed.suffix;
}
flights.push(flight);
}
}
return flights;
}
export default function ScheduleDetailsCatchAllRoute(): JSX.Element {
const { t } = useTranslation();
const routeParams = useParams<{ "*": string; lang: string }>();
const locale = routeParams.lang ?? "ru";
const canonicalOrigin = getEnv().PROD_ORIGIN;
// Modern.js splat route ($.tsx) provides the remaining path via "*" param.
const rawFlights = routeParams["*"] ?? "";
const segments = rawFlights.split("/").filter(Boolean);
const flights = parseFlightSegments(segments);
if (flights.length === 0) {
return (
<div data-testid="invalid-params">
<p>{t("SHARED.INVALID-PARAMS")}</p>
</div>
);
}
return (
<Suspense fallback={<FlightListSkeleton count={flights.length} />}>
<ScheduleDetailsPage
flights={flights}
locale={locale}
canonicalOrigin={canonicalOrigin}
/>
</Suspense>
);
}
+8 -76
View File
@@ -1,80 +1,12 @@
/**
* Schedule flight details catch-all route.
* Modern.js/react-router splat file.
*
* Captures variable-length paths for multi-flight chains.
* URL: /{lang}/schedule/{flight1-date}[/{flight2-date}]...
* Also handles: /{lang}/schedule/{depCode}/{flight-date}/{arrCode}/...
*
* Modern.js catch-all route: [...flights] captures all remaining segments.
* The `$.tsx` filename is a framework convention that marks a catch-all
* route — it captures every trailing path segment. The name is mandated
* by the router, but cryptic at a glance; the real component lives at
* src/features/schedule/ScheduleDetailsCatchAllRoute.tsx and this file
* is intentionally a thin re-export so the readable name appears in
* imports, search results and stack traces.
*/
import { lazy, Suspense } from "react";
import { useParams } from "@modern-js/runtime/router";
import { useTranslation } from "@/i18n/provider.js";
import { parseFlightUrlParams } from "@/features/online-board/url.js";
import { FlightListSkeleton } from "@/ui/flights/FlightListSkeleton.js";
import { getEnv } from "@/env/index.js";
import type { IScheduleFlightId } from "@/features/schedule/types.js";
const ScheduleDetailsPage = lazy(() =>
import("@/features/schedule/components/ScheduleDetailsPage.js").then(
(m) => ({ default: m.ScheduleDetailsPage }),
),
);
/**
* Parse the catch-all segments into flight IDs.
* Segments that are 3 chars or less are treated as airport codes and skipped.
*/
function parseFlightSegments(segments: string[]): IScheduleFlightId[] {
const flights: IScheduleFlightId[] = [];
for (const segment of segments) {
if (segment.length <= 3) continue;
const parsed = parseFlightUrlParams(segment);
if (parsed) {
const flight: IScheduleFlightId = {
carrier: parsed.carrier,
flightNumber: parsed.flightNumber,
date: parsed.date,
};
if (parsed.suffix !== undefined) {
flight.suffix = parsed.suffix;
}
flights.push(flight);
}
}
return flights;
}
export default function ScheduleDetailsRoute(): JSX.Element {
const { t } = useTranslation();
const routeParams = useParams<{ "*": string; lang: string }>();
const locale = routeParams.lang ?? "ru";
const canonicalOrigin = getEnv().PROD_ORIGIN;
// Modern.js $.tsx splat route provides the remaining path via "*" param.
const rawFlights = routeParams["*"] ?? "";
const segments = rawFlights.split("/").filter(Boolean);
const flights = parseFlightSegments(segments);
if (flights.length === 0) {
return (
<div data-testid="invalid-params">
<p>{t("SHARED.INVALID-PARAMS")}</p>
</div>
);
}
return (
<Suspense fallback={<FlightListSkeleton count={flights.length} />}>
<ScheduleDetailsPage
flights={flights}
locale={locale}
canonicalOrigin={canonicalOrigin}
/>
</Suspense>
);
}
export { default } from "@/features/schedule/ScheduleDetailsCatchAllRoute.js";