diff --git a/src/features/popular-requests/components/PopularRequestItem.tsx b/src/features/popular-requests/components/PopularRequestItem.tsx
new file mode 100644
index 00000000..14fa86df
--- /dev/null
+++ b/src/features/popular-requests/components/PopularRequestItem.tsx
@@ -0,0 +1,152 @@
+/**
+ * Renders a single popular request item, switching display by request mode.
+ *
+ * Angular equivalent: `PopularRequestComponent` + the per-mode components
+ * (ArrivalRequestComponent, DepartureRequestComponent,
+ * FlightNumberRequestComponent, RouteRequestComponent).
+ *
+ * In React, these are inlined as a single component with a switch,
+ * since each mode branch is 2-3 lines of JSX.
+ */
+
+import { useTranslation } from "@/i18n/provider.js";
+import { useCityName } from "@/shared/hooks/useDictionaries.js";
+import { RequestInfo } from "./RequestInfo.js";
+import type { PopularRequest } from "../types.js";
+
+export interface PopularRequestItemProps {
+ request: PopularRequest;
+ onClick: (request: PopularRequest) => void;
+}
+
+export function PopularRequestItem({
+ request,
+ onClick,
+}: PopularRequestItemProps): JSX.Element {
+ const { t } = useTranslation();
+
+ const handleClick = () => {
+ onClick(request);
+ };
+
+ switch (request.mode) {
+ case "Arrival": {
+ return (
+
+ );
+ }
+ case "Departure": {
+ return (
+
+ );
+ }
+ case "FlightNumber": {
+ const flightInfo = `${request.carrier}\u00a0${request.flightNumber}`;
+ return (
+
+ {t("BOARD.FLIGHT_NUMBER")}:{" "}
+ {flightInfo}
+
+ );
+ }
+ case "Route":
+ case "RouteWithBack": {
+ const label = getRouteLabel(request.mode, request.type, t);
+ return (
+
+ );
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Internal sub-components
+// ---------------------------------------------------------------------------
+
+function ArrivalDisplay({
+ label,
+ cityCode,
+ onClick,
+}: {
+ label: string;
+ cityCode: string;
+ onClick: () => void;
+}): JSX.Element {
+ const cityName = useCityName(cityCode);
+ return (
+
+ {label}: {cityName}
+
+ );
+}
+
+function DepartureDisplay({
+ label,
+ cityCode,
+ onClick,
+}: {
+ label: string;
+ cityCode: string;
+ onClick: () => void;
+}): JSX.Element {
+ const cityName = useCityName(cityCode);
+ return (
+
+ {label}: {cityName}
+
+ );
+}
+
+function RouteDisplay({
+ label,
+ departureCode,
+ arrivalCode,
+ onClick,
+}: {
+ label: string;
+ departureCode: string;
+ arrivalCode: string;
+ onClick: () => void;
+}): JSX.Element {
+ const departureName = useCityName(departureCode);
+ const arrivalName = useCityName(arrivalCode);
+ return (
+
+ {label}:{" "}
+
+ {departureName} - {arrivalName}
+
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Helpers
+// ---------------------------------------------------------------------------
+
+function getRouteLabel(
+ mode: "Route" | "RouteWithBack",
+ type: string,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ t: (key: string) => any,
+): string {
+ if (mode === "RouteWithBack") {
+ return t("SCHEDULE.SCHEDULE-FULL-ROUTE") as string;
+ }
+ return type === "Onlineboard"
+ ? (t("BOARD.ROUTE") as string)
+ : (t("SCHEDULE.SCHEDULE-OUTBOUND") as string);
+}
diff --git a/src/features/popular-requests/components/PopularRequestsPanel.tsx b/src/features/popular-requests/components/PopularRequestsPanel.tsx
new file mode 100644
index 00000000..d87c5306
--- /dev/null
+++ b/src/features/popular-requests/components/PopularRequestsPanel.tsx
@@ -0,0 +1,56 @@
+/**
+ * Container component for the Popular Requests panel.
+ *
+ * Angular equivalent: `PopularRequestsComponent` (popular-requests.component.ts)
+ * — fetches popular requests on mount, renders up to 4 items in a 2-column
+ * grid, and handles click navigation.
+ *
+ * This component is embedded in start pages (OnlineBoard, Schedule),
+ * not rendered as a standalone route.
+ */
+
+import { useTranslation } from "@/i18n/provider.js";
+import { usePopularRequests } from "../hooks/usePopularRequests.js";
+import { PopularRequestItem } from "./PopularRequestItem.js";
+import type { PopularRequest } from "../types.js";
+
+export interface PopularRequestsPanelProps {
+ /** Callback invoked when a user clicks a popular request. The host page
+ * handles navigation based on the request mode and type. */
+ onRequestClick: (request: PopularRequest) => void;
+}
+
+/**
+ * Renders the "Popular sections" panel with up to 4 popular request items.
+ * Shows nothing while loading, nothing on error (graceful degradation).
+ */
+export function PopularRequestsPanel({
+ onRequestClick,
+}: PopularRequestsPanelProps): JSX.Element | null {
+ const { t } = useTranslation();
+ const { requests, loading, error } = usePopularRequests();
+
+ // Gracefully degrade: don't render anything on loading or error
+ if (loading || error || requests.length === 0) {
+ return null;
+ }
+
+ // Angular renders exactly 4 items (requests[0]..requests[3])
+ const visibleRequests = requests.slice(0, 4);
+
+ return (
+
+
+ {t("BOARD.POPULAR-CHAPTERS")}
+
+ {visibleRequests.map((request, index) => (
+
+ ))}
+
+ );
+}
diff --git a/src/features/popular-requests/components/RequestInfo.tsx b/src/features/popular-requests/components/RequestInfo.tsx
new file mode 100644
index 00000000..5235b683
--- /dev/null
+++ b/src/features/popular-requests/components/RequestInfo.tsx
@@ -0,0 +1,36 @@
+/**
+ * Styled clickable text wrapper for popular request items.
+ *
+ * Angular equivalent: `RequestInfoComponent` (request-info.component.ts)
+ * — a simple styled `` with blue link color and pointer cursor.
+ */
+
+import type { ReactNode, MouseEvent } from "react";
+
+export interface RequestInfoProps {
+ children: ReactNode;
+ onClick: (e: MouseEvent) => void;
+}
+
+export function RequestInfo({ children, onClick }: RequestInfoProps): JSX.Element {
+ return (
+ {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ onClick(e as unknown as MouseEvent);
+ }
+ }}
+ style={{
+ color: "var(--color-blue-link, #0645ad)",
+ cursor: "pointer",
+ }}
+ >
+ {children}
+
+ );
+}
diff --git a/src/mf/expose/PopularRequests.tsx b/src/mf/expose/PopularRequests.tsx
index 3d1cf9b1..3dd8f42a 100644
--- a/src/mf/expose/PopularRequests.tsx
+++ b/src/mf/expose/PopularRequests.tsx
@@ -1,17 +1,59 @@
+import { useCallback } from "react";
+import { useNavigate, useParams } from "@modern-js/runtime/router";
import type { HostContract } from "@/host-contract";
+import { PopularRequestsPanel } from "@/features/popular-requests/components/PopularRequestsPanel.js";
+import type { PopularRequest } from "@/features/popular-requests/types.js";
/**
* MF expose wrapper for the Popular Requests feature.
- * Phase 5 (popular-requests port) replaces the body with the real root.
+ *
+ * Renders the PopularRequestsPanel and handles navigation on click
+ * by routing to the appropriate onlineboard/schedule page within the
+ * host application.
*/
export interface PopularRequestsRemoteProps {
hostContract: HostContract;
}
-export default function PopularRequestsRemote(_props: PopularRequestsRemoteProps): JSX.Element {
+export default function PopularRequestsRemote({
+ hostContract,
+}: PopularRequestsRemoteProps): JSX.Element {
+ const navigate = useNavigate();
+ const params = useParams<{ lang: string }>();
+ const lang = params.lang ?? hostContract.locale;
+
+ const handleRequestClick = useCallback(
+ (request: PopularRequest) => {
+ const nav = hostContract.navigate ?? ((path: string) => void navigate(path));
+
+ switch (request.mode) {
+ case "FlightNumber":
+ nav(`/${lang}/onlineboard`);
+ return;
+ case "Arrival":
+ nav(`/${lang}/onlineboard`);
+ return;
+ case "Departure":
+ nav(`/${lang}/onlineboard`);
+ return;
+ case "Route":
+ if (request.type === "Onlineboard") {
+ nav(`/${lang}/onlineboard`);
+ } else {
+ nav(`/${lang}/schedule`);
+ }
+ return;
+ case "RouteWithBack":
+ nav(`/${lang}/schedule`);
+ return;
+ }
+ },
+ [hostContract.navigate, navigate, lang],
+ );
+
return (
-
Popular Requests remote — stub. Populated in Phase 5.
+
);
}
diff --git a/src/routes/[lang]/popular/page.tsx b/src/routes/[lang]/popular/page.tsx
new file mode 100644
index 00000000..1b8e3ad6
--- /dev/null
+++ b/src/routes/[lang]/popular/page.tsx
@@ -0,0 +1,55 @@
+/**
+ * Popular Requests standalone page route.
+ *
+ * Provides a standalone view of the PopularRequestsPanel. In Angular,
+ * popular requests were embedded in OnlineBoard and Schedule start pages.
+ * This route provides a direct-access path for the MF remote and allows
+ * independent rendering/testing.
+ *
+ * URL: /{lang}/popular
+ */
+
+import { lazy, Suspense, useCallback } from "react";
+import { useParams, useNavigate } from "@modern-js/runtime/router";
+import type { PopularRequest } from "@/features/popular-requests/types.js";
+
+const PopularRequestsPanel = lazy(() =>
+ import("@/features/popular-requests/components/PopularRequestsPanel.js").then(
+ (m) => ({ default: m.PopularRequestsPanel }),
+ ),
+);
+
+export default function PopularPage(): JSX.Element {
+ const routeParams = useParams<{ lang: string }>();
+ const lang = routeParams.lang ?? "ru";
+ const navigate = useNavigate();
+
+ const handleRequestClick = useCallback(
+ (request: PopularRequest) => {
+ switch (request.mode) {
+ case "FlightNumber":
+ case "Arrival":
+ case "Departure":
+ void navigate(`/${lang}/onlineboard`);
+ return;
+ case "Route":
+ if (request.type === "Onlineboard") {
+ void navigate(`/${lang}/onlineboard`);
+ } else {
+ void navigate(`/${lang}/schedule`);
+ }
+ return;
+ case "RouteWithBack":
+ void navigate(`/${lang}/schedule`);
+ return;
+ }
+ },
+ [lang, navigate],
+ );
+
+ return (
+ Loading...}>
+
+
+ );
+}