Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/root.tsx
T

98 lines
3.9 KiB
TypeScript

import { useEffect } from "react";
import {
type ActionFunction,
type HeadersFunction,
type LoaderFunctionArgs,
type MiddlewareFunction,
useLoaderData,
useRouteError,
useRouteLoaderData,
} from "react-router";
import "./globals.css";
import "./themes.css";
import "./styles/sentry-masking.css";
import { RootSurfaceApp, RootSurfaceErrorBoundary, rootI18nNamespaces } from "./root-client-surface";
import { serverTimingHeaders } from "./modules/metrics/utils/defaultHeaders.server";
import type { PortalRootData } from "./services/customer-portal/portal-root";
import type { AppRootData } from "./utils/data/useRootData";
import { type v2MetaFunction } from "./utils/compat/v2MetaFunction";
import { CSP_NONCE_REQUEST_HEADER } from "./utils/security/contentSecurityPolicy";
// Production and development builds receive this value from vite.config.ts.
// The test-only runtime branch keeps the route module independently testable
// without weakening the explicit surface selection used for real builds.
const portalBuildSelected =
import.meta.env.MODE === "test" ? process.env.WEB_RUNTIME_SURFACE === "portal" : import.meta.env.VITE_HIVEGRID_BUILD_SURFACE === "portal";
export const middleware: MiddlewareFunction<Response>[] = [
async ({ request }, next) => {
// VA-P26a: the portal surface owns a separate identity class and must not
// parse, validate, or bind the core User/TenantUser session at the root.
if (portalBuildSelected) return next();
const { runCoreRootMiddleware } = await import("./root-core.server");
return runCoreRootMiddleware(request, next);
},
];
export const headers: HeadersFunction = (args) => {
const responseHeaders = new Headers(serverTimingHeaders(args));
if (!portalBuildSelected) {
for (const headerName of [CSP_NONCE_REQUEST_HEADER, "Content-Security-Policy"] as const) {
const value = args.loaderHeaders.get(headerName) ?? args.parentHeaders.get(headerName);
if (value) responseHeaders.set(headerName, value);
}
return responseHeaders;
}
for (const headerName of [CSP_NONCE_REQUEST_HEADER, "Content-Security-Policy", "Cache-Control", "Referrer-Policy"] as const) {
const value = args.errorHeaders?.get(headerName) ?? args.actionHeaders.get(headerName) ?? args.loaderHeaders.get(headerName) ?? args.parentHeaders.get(headerName);
if (value) responseHeaders.set(headerName, value);
}
return responseHeaders;
};
export const handle = { i18n: [...rootI18nNamespaces] };
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
if (portalBuildSelected) {
const { loadPortalRootData } = await import("./services/customer-portal/portal-root.server");
return loadPortalRootData(request);
}
const { loadCoreRootData } = await import("./root-core.server");
return loadCoreRootData({ request, params });
};
export const meta: v2MetaFunction<AppRootData | PortalRootData> = ({ data }) => data?.metatags ?? [];
export const action: ActionFunction = async ({ request }) => {
if (portalBuildSelected) {
const { handlePortalRootAction } = await import("./services/customer-portal/portal-root.server");
return handlePortalRootAction(request);
}
const { handleCoreRootAction } = await import("./root-core.server");
return handleCoreRootAction(request);
};
export default function App() {
const data = useLoaderData<AppRootData | PortalRootData>();
return <RootSurfaceApp data={data} />;
}
export function ErrorBoundary() {
const error = useRouteError();
const rootData = useRouteLoaderData<AppRootData | PortalRootData>("root");
// Report route-level errors that React handled before they reached the SSR
// integration. Portal and Core share this transport, not their UI shells.
useEffect(() => {
if (error) {
void import("@sentry/react").then((Sentry) => {
Sentry.captureException(error);
});
}
}, [error]);
return <RootSurfaceErrorBoundary rootData={rootData} />;
}