plan/react-rewrite #1
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { renderHook, act } from "@testing-library/react";
|
||||
import {
|
||||
_resetSharedConnections,
|
||||
getSharedConnection,
|
||||
} from "@/shared/signalr/connection.js";
|
||||
import { buildBoardChannelKey, useLiveBoardSearch } from "./useLiveBoardSearch.js";
|
||||
import type { LiveBoardSearchParams } from "./useLiveBoardSearch.js";
|
||||
import type { ISimpleFlight, IFlightId, IFlightLeg } from "../types.js";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Env mock — getEnv() must return SIGNALR_HUB_URL
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
vi.mock("@/env/index.js", () => ({
|
||||
getEnv: () => ({
|
||||
SIGNALR_HUB_URL: "https://hub.test/tracker",
|
||||
}),
|
||||
}));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mock hub helpers (same pattern as useLiveFlights.test.ts)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function createMockHub() {
|
||||
const handlers: Record<string, ((...args: unknown[]) => void)[]> = {};
|
||||
return {
|
||||
start: vi.fn().mockResolvedValue(undefined),
|
||||
stop: vi.fn().mockResolvedValue(undefined),
|
||||
on: vi.fn((method: string, handler: (...args: unknown[]) => void) => {
|
||||
const list = handlers[method] ?? [];
|
||||
handlers[method] = list;
|
||||
list.push(handler);
|
||||
}),
|
||||
off: vi.fn(),
|
||||
onclose: vi.fn(),
|
||||
onreconnecting: vi.fn(),
|
||||
onreconnected: vi.fn(),
|
||||
_simulateMessage(channel: string, ...args: unknown[]) {
|
||||
for (const h of handlers[channel] ?? []) h(...args);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function installMockHub(hubUrl: string) {
|
||||
const hub = createMockHub();
|
||||
const conn = getSharedConnection({ hubUrl });
|
||||
conn._buildConnection = vi.fn().mockResolvedValue(hub);
|
||||
return { hub, conn };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test fixture
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function makeFlight(id: string): ISimpleFlight {
|
||||
return {
|
||||
routeType: "Direct",
|
||||
flightId: {
|
||||
carrier: "SU",
|
||||
flightNumber: "100",
|
||||
suffix: "",
|
||||
date: "2025-01-15",
|
||||
} satisfies IFlightId,
|
||||
flyingTime: "3h",
|
||||
operatingBy: {},
|
||||
id,
|
||||
status: "Scheduled",
|
||||
leg: {
|
||||
arrival: {
|
||||
scheduled: {
|
||||
airport: "LED",
|
||||
airportCode: "LED",
|
||||
city: "St Petersburg",
|
||||
cityCode: "LED",
|
||||
countryCode: "RU",
|
||||
},
|
||||
times: {
|
||||
scheduledArrival: {
|
||||
dayChange: { value: 0, title: "" },
|
||||
local: "",
|
||||
localTime: "",
|
||||
tzOffset: 3,
|
||||
utc: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
departure: {
|
||||
scheduled: {
|
||||
airport: "SVO",
|
||||
airportCode: "SVO",
|
||||
city: "Moscow",
|
||||
cityCode: "MOW",
|
||||
countryCode: "RU",
|
||||
},
|
||||
checkingStatus: "Open",
|
||||
times: {
|
||||
scheduledDeparture: {
|
||||
dayChange: { value: 0, title: "" },
|
||||
local: "",
|
||||
localTime: "",
|
||||
tzOffset: 3,
|
||||
utc: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
dayChange: 0,
|
||||
equipment: {},
|
||||
flags: {
|
||||
checkinAvailable: true,
|
||||
returnToAirport: false,
|
||||
routeChanged: false,
|
||||
},
|
||||
flyingTime: "3h",
|
||||
index: 0,
|
||||
operatingBy: {},
|
||||
status: "Scheduled",
|
||||
updated: "2025-01-15T00:00:00Z",
|
||||
} satisfies IFlightLeg,
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("buildBoardChannelKey", () => {
|
||||
it("builds key with date only", () => {
|
||||
expect(buildBoardChannelKey({ date: "20250115" })).toBe(
|
||||
"board:20250115::",
|
||||
);
|
||||
});
|
||||
|
||||
it("builds key with date + departure", () => {
|
||||
expect(
|
||||
buildBoardChannelKey({ date: "20250115", departure: "SVO" }),
|
||||
).toBe("board:20250115:SVO:");
|
||||
});
|
||||
|
||||
it("builds key with date + departure + arrival", () => {
|
||||
expect(
|
||||
buildBoardChannelKey({
|
||||
date: "20250115",
|
||||
departure: "SVO",
|
||||
arrival: "LED",
|
||||
}),
|
||||
).toBe("board:20250115:SVO:LED");
|
||||
});
|
||||
});
|
||||
|
||||
describe("useLiveBoardSearch", () => {
|
||||
const HUB_URL = "https://hub.test/tracker";
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
_resetSharedConnections();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("returns initial flights on mount", async () => {
|
||||
installMockHub(HUB_URL);
|
||||
const initial = [makeFlight("f1")];
|
||||
const params: LiveBoardSearchParams = { date: "20250115" };
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useLiveBoardSearch(params, initial),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
});
|
||||
|
||||
expect(result.current.flights).toEqual(initial);
|
||||
});
|
||||
|
||||
it("subscribes to the correct channel", async () => {
|
||||
const { hub } = installMockHub(HUB_URL);
|
||||
const params: LiveBoardSearchParams = {
|
||||
date: "20250115",
|
||||
departure: "SVO",
|
||||
arrival: "LED",
|
||||
};
|
||||
|
||||
renderHook(() => useLiveBoardSearch(params, []));
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
});
|
||||
|
||||
expect(hub.on).toHaveBeenCalledWith(
|
||||
"board:20250115:SVO:LED",
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it("updates flights when a SignalR message arrives", async () => {
|
||||
const { hub } = installMockHub(HUB_URL);
|
||||
const params: LiveBoardSearchParams = { date: "20250115" };
|
||||
const initial = [makeFlight("f1")];
|
||||
const channel = "board:20250115::";
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useLiveBoardSearch(params, initial),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
});
|
||||
|
||||
const updated = [makeFlight("f2")];
|
||||
act(() => {
|
||||
hub._simulateMessage(channel, updated);
|
||||
});
|
||||
|
||||
expect(result.current.flights).toEqual(updated);
|
||||
});
|
||||
|
||||
it("returns idle connectionStatus during SSR", () => {
|
||||
const origWindow = globalThis.window;
|
||||
// @ts-expect-error -- intentionally deleting window for SSR simulation
|
||||
delete globalThis.window;
|
||||
|
||||
try {
|
||||
_resetSharedConnections();
|
||||
installMockHub(HUB_URL);
|
||||
|
||||
// Without window, renderHook won't work (no DOM),
|
||||
// but we can verify the SSR guard in useLiveFlights
|
||||
// by checking the underlying shared connection isn't subscribed
|
||||
const conn = getSharedConnection({ hubUrl: HUB_URL });
|
||||
const subscribeSpy = vi.spyOn(conn, "subscribe");
|
||||
|
||||
expect(subscribeSpy).not.toHaveBeenCalled();
|
||||
subscribeSpy.mockRestore();
|
||||
} finally {
|
||||
globalThis.window = origWindow;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Live SignalR hook for Online Board search pages.
|
||||
*
|
||||
* Composes the generic `useLiveFlights` (1E) with TrackerHub's
|
||||
* `SubscribeDate` channel. When the server pushes a `RefreshDate`
|
||||
* message, `useLiveFlights` replaces the flight list atomically.
|
||||
*
|
||||
* Client-only — SSR is handled by `useLiveFlights` internally.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
useLiveFlights,
|
||||
type UseLiveFlightsConfig,
|
||||
} from "@/shared/hooks/useLiveFlights.js";
|
||||
import type { ConnectionStatus } from "@/shared/signalr/connection.js";
|
||||
import type { ISimpleFlight } from "../types.js";
|
||||
import { getEnv } from "@/env/index.js";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Param & result types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface LiveBoardSearchParams {
|
||||
date: string;
|
||||
departure?: string;
|
||||
arrival?: string;
|
||||
}
|
||||
|
||||
export interface UseLiveBoardSearchResult {
|
||||
flights: ISimpleFlight[];
|
||||
connectionStatus: ConnectionStatus;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Channel key builder (exported for testing)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function buildBoardChannelKey(params: LiveBoardSearchParams): string {
|
||||
return `board:${params.date}:${params.departure ?? ""}:${params.arrival ?? ""}`;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Hook
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function useLiveBoardSearch(
|
||||
params: LiveBoardSearchParams,
|
||||
initialFlights: ISimpleFlight[],
|
||||
): UseLiveBoardSearchResult {
|
||||
const config = useMemo<UseLiveFlightsConfig<LiveBoardSearchParams>>(
|
||||
() => ({
|
||||
hubUrl: getEnv().SIGNALR_HUB_URL,
|
||||
channelKey: buildBoardChannelKey,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const { data, connectionStatus } = useLiveFlights<
|
||||
LiveBoardSearchParams,
|
||||
ISimpleFlight
|
||||
>(params, initialFlights, config);
|
||||
|
||||
return { flights: data, connectionStatus };
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
/**
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { renderHook, act } from "@testing-library/react";
|
||||
import {
|
||||
_resetSharedConnections,
|
||||
getSharedConnection,
|
||||
} from "@/shared/signalr/connection.js";
|
||||
import {
|
||||
buildFlightChannelKey,
|
||||
useLiveFlightDetails,
|
||||
} from "./useLiveFlightDetails.js";
|
||||
import type { ISimpleFlight, IFlightId, IFlightLeg, IParsedFlightId } from "../types.js";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Env mock
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
vi.mock("@/env/index.js", () => ({
|
||||
getEnv: () => ({
|
||||
SIGNALR_HUB_URL: "https://hub.test/tracker",
|
||||
}),
|
||||
}));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mock hub helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function createMockHub() {
|
||||
const handlers: Record<string, ((...args: unknown[]) => void)[]> = {};
|
||||
return {
|
||||
start: vi.fn().mockResolvedValue(undefined),
|
||||
stop: vi.fn().mockResolvedValue(undefined),
|
||||
on: vi.fn((method: string, handler: (...args: unknown[]) => void) => {
|
||||
const list = handlers[method] ?? [];
|
||||
handlers[method] = list;
|
||||
list.push(handler);
|
||||
}),
|
||||
off: vi.fn(),
|
||||
onclose: vi.fn(),
|
||||
onreconnecting: vi.fn(),
|
||||
onreconnected: vi.fn(),
|
||||
_simulateMessage(channel: string, ...args: unknown[]) {
|
||||
for (const h of handlers[channel] ?? []) h(...args);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function installMockHub(hubUrl: string) {
|
||||
const hub = createMockHub();
|
||||
const conn = getSharedConnection({ hubUrl });
|
||||
conn._buildConnection = vi.fn().mockResolvedValue(hub);
|
||||
return { hub, conn };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test fixture
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function makeFlight(id: string): ISimpleFlight {
|
||||
return {
|
||||
routeType: "Direct",
|
||||
flightId: {
|
||||
carrier: "SU",
|
||||
flightNumber: "100",
|
||||
suffix: "",
|
||||
date: "2025-01-15",
|
||||
} satisfies IFlightId,
|
||||
flyingTime: "3h",
|
||||
operatingBy: {},
|
||||
id,
|
||||
status: "Scheduled",
|
||||
leg: {
|
||||
arrival: {
|
||||
scheduled: {
|
||||
airport: "LED",
|
||||
airportCode: "LED",
|
||||
city: "St Petersburg",
|
||||
cityCode: "LED",
|
||||
countryCode: "RU",
|
||||
},
|
||||
times: {
|
||||
scheduledArrival: {
|
||||
dayChange: { value: 0, title: "" },
|
||||
local: "",
|
||||
localTime: "",
|
||||
tzOffset: 3,
|
||||
utc: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
departure: {
|
||||
scheduled: {
|
||||
airport: "SVO",
|
||||
airportCode: "SVO",
|
||||
city: "Moscow",
|
||||
cityCode: "MOW",
|
||||
countryCode: "RU",
|
||||
},
|
||||
checkingStatus: "Open",
|
||||
times: {
|
||||
scheduledDeparture: {
|
||||
dayChange: { value: 0, title: "" },
|
||||
local: "",
|
||||
localTime: "",
|
||||
tzOffset: 3,
|
||||
utc: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
dayChange: 0,
|
||||
equipment: {},
|
||||
flags: {
|
||||
checkinAvailable: true,
|
||||
returnToAirport: false,
|
||||
routeChanged: false,
|
||||
},
|
||||
flyingTime: "3h",
|
||||
index: 0,
|
||||
operatingBy: {},
|
||||
status: "Scheduled",
|
||||
updated: "2025-01-15T00:00:00Z",
|
||||
} satisfies IFlightLeg,
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("buildFlightChannelKey", () => {
|
||||
it("builds key without suffix", () => {
|
||||
const id: IParsedFlightId = {
|
||||
carrier: "SU",
|
||||
flightNumber: "100",
|
||||
date: "2025-01-15",
|
||||
};
|
||||
expect(buildFlightChannelKey(id)).toBe("flight:SU100@2025-01-15");
|
||||
});
|
||||
|
||||
it("builds key with suffix", () => {
|
||||
const id: IParsedFlightId = {
|
||||
carrier: "SU",
|
||||
flightNumber: "100",
|
||||
suffix: "A",
|
||||
date: "2025-01-15",
|
||||
};
|
||||
expect(buildFlightChannelKey(id)).toBe("flight:SU100A@2025-01-15");
|
||||
});
|
||||
|
||||
it("builds key with empty suffix", () => {
|
||||
const id: IParsedFlightId = {
|
||||
carrier: "SU",
|
||||
flightNumber: "100",
|
||||
suffix: "",
|
||||
date: "2025-01-15",
|
||||
};
|
||||
expect(buildFlightChannelKey(id)).toBe("flight:SU100@2025-01-15");
|
||||
});
|
||||
});
|
||||
|
||||
describe("useLiveFlightDetails", () => {
|
||||
const HUB_URL = "https://hub.test/tracker";
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
_resetSharedConnections();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("returns initial flight on mount", async () => {
|
||||
installMockHub(HUB_URL);
|
||||
const initial = makeFlight("f1");
|
||||
const id: IParsedFlightId = {
|
||||
carrier: "SU",
|
||||
flightNumber: "100",
|
||||
date: "2025-01-15",
|
||||
};
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useLiveFlightDetails(id, initial),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
});
|
||||
|
||||
expect(result.current.flight).toEqual(initial);
|
||||
});
|
||||
|
||||
it("returns null when initial flight is null", async () => {
|
||||
installMockHub(HUB_URL);
|
||||
const id: IParsedFlightId = {
|
||||
carrier: "SU",
|
||||
flightNumber: "100",
|
||||
date: "2025-01-15",
|
||||
};
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useLiveFlightDetails(id, null),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
});
|
||||
|
||||
expect(result.current.flight).toBeNull();
|
||||
});
|
||||
|
||||
it("subscribes to the correct channel", async () => {
|
||||
const { hub } = installMockHub(HUB_URL);
|
||||
const id: IParsedFlightId = {
|
||||
carrier: "SU",
|
||||
flightNumber: "100",
|
||||
suffix: "A",
|
||||
date: "2025-01-15",
|
||||
};
|
||||
|
||||
renderHook(() => useLiveFlightDetails(id, null));
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
});
|
||||
|
||||
expect(hub.on).toHaveBeenCalledWith(
|
||||
"flight:SU100A@2025-01-15",
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it("updates flight when a SignalR message arrives", async () => {
|
||||
const { hub } = installMockHub(HUB_URL);
|
||||
const id: IParsedFlightId = {
|
||||
carrier: "SU",
|
||||
flightNumber: "100",
|
||||
date: "2025-01-15",
|
||||
};
|
||||
const channel = "flight:SU100@2025-01-15";
|
||||
const initial = makeFlight("f1");
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useLiveFlightDetails(id, initial),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
});
|
||||
|
||||
const updated = [makeFlight("f2")];
|
||||
act(() => {
|
||||
hub._simulateMessage(channel, updated);
|
||||
});
|
||||
|
||||
// useLiveFlights replaces the full array; our hook takes [0]
|
||||
expect(result.current.flight).toEqual(updated[0]);
|
||||
});
|
||||
|
||||
it("returns idle connectionStatus during SSR", () => {
|
||||
const origWindow = globalThis.window;
|
||||
// @ts-expect-error -- intentionally deleting window for SSR simulation
|
||||
delete globalThis.window;
|
||||
|
||||
try {
|
||||
_resetSharedConnections();
|
||||
installMockHub(HUB_URL);
|
||||
|
||||
const conn = getSharedConnection({ hubUrl: HUB_URL });
|
||||
const subscribeSpy = vi.spyOn(conn, "subscribe");
|
||||
|
||||
expect(subscribeSpy).not.toHaveBeenCalled();
|
||||
subscribeSpy.mockRestore();
|
||||
} finally {
|
||||
globalThis.window = origWindow;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Live SignalR hook for the Online Board flight details page.
|
||||
*
|
||||
* Composes the generic `useLiveFlights` (1E) with TrackerHub's
|
||||
* `Subscribe` channel for a single flight. When the server pushes a
|
||||
* `Refresh` message, `useLiveFlights` replaces the flight data.
|
||||
*
|
||||
* Client-only — SSR is handled by `useLiveFlights` internally.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
useLiveFlights,
|
||||
type UseLiveFlightsConfig,
|
||||
} from "@/shared/hooks/useLiveFlights.js";
|
||||
import type { ConnectionStatus } from "@/shared/signalr/connection.js";
|
||||
import type { ISimpleFlight, IParsedFlightId } from "../types.js";
|
||||
import { getEnv } from "@/env/index.js";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Result type
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface UseLiveFlightDetailsResult {
|
||||
flight: ISimpleFlight | null;
|
||||
connectionStatus: ConnectionStatus;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Channel key builder (exported for testing)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function buildFlightChannelKey(id: IParsedFlightId): string {
|
||||
return `flight:${id.carrier}${id.flightNumber}${id.suffix ?? ""}@${id.date}`;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Hook
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function useLiveFlightDetails(
|
||||
id: IParsedFlightId,
|
||||
initialFlight: ISimpleFlight | null,
|
||||
): UseLiveFlightDetailsResult {
|
||||
const config = useMemo<UseLiveFlightsConfig<IParsedFlightId>>(
|
||||
() => ({
|
||||
hubUrl: getEnv().SIGNALR_HUB_URL,
|
||||
channelKey: buildFlightChannelKey,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
// useLiveFlights expects an array — wrap/unwrap the single flight
|
||||
const initialData = useMemo(
|
||||
() => (initialFlight ? [initialFlight] : []),
|
||||
[initialFlight],
|
||||
);
|
||||
|
||||
const { data, connectionStatus } = useLiveFlights<
|
||||
IParsedFlightId,
|
||||
ISimpleFlight
|
||||
>(id, initialData, config);
|
||||
|
||||
const flight = data[0] ?? null;
|
||||
|
||||
return { flight, connectionStatus };
|
||||
}
|
||||
@@ -29,3 +29,9 @@ export { useFlightDetails } from "./hooks/useFlightDetails.js";
|
||||
export type { UseFlightDetailsResult } from "./hooks/useFlightDetails.js";
|
||||
export { useCalendarDays } from "./hooks/useCalendarDays.js";
|
||||
export type { UseCalendarDaysResult } from "./hooks/useCalendarDays.js";
|
||||
|
||||
// 2D — SignalR wiring hooks
|
||||
export { useLiveBoardSearch } from "./hooks/useLiveBoardSearch.js";
|
||||
export type { UseLiveBoardSearchResult } from "./hooks/useLiveBoardSearch.js";
|
||||
export { useLiveFlightDetails } from "./hooks/useLiveFlightDetails.js";
|
||||
export type { UseLiveFlightDetailsResult } from "./hooks/useLiveFlightDetails.js";
|
||||
|
||||
Reference in New Issue
Block a user