< Summary

Information
Class: root.tsx
Assembly: app
File(s): /home/runner/work/ClutterStock/ClutterStock/frontend/app/root.tsx
Tag: 58_25416222083
Line coverage
0%
Covered lines: 0
Uncovered lines: 29
Coverable lines: 29
Total lines: 148
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 8
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/ClutterStock/ClutterStock/frontend/app/root.tsx

#LineLine coverage
 1import {
 2  Links,
 3  Meta,
 4  Outlet,
 5  Scripts,
 6  ScrollRestoration,
 7  useRouteLoaderData,
 8} from "react-router";
 9
 10import type { Route } from "./+types/root";
 11import { ProblemBoundary } from "~/components/problem-boundary";
 12import { SiteFooter } from "~/components/site-footer";
 13import { SiteHeader } from "~/components/site-header";
 14import { FlashToasts, Toaster, ToastProvider, type ToastInput } from "~/lib/toasts";
 15import type { PublicRuntimeConfig } from "~/public-runtime-config";
 16import type { SessionUser } from "~/lib/session.server";
 17import "./app.css";
 18
 19interface RootLoaderData {
 20  publicRuntime: PublicRuntimeConfig;
 21  user: SessionUser | null;
 22  flashes: readonly ToastInput[];
 23}
 24
 025export async function loader({ request }: Route.LoaderArgs): Promise<RootLoaderData> {
 026  if (globalThis.window !== undefined) {
 027    const w = globalThis.window as Window & {
 28      __CLUTTERSTOCK_PUBLIC__?: PublicRuntimeConfig;
 29    };
 030    return {
 31      publicRuntime: w.__CLUTTERSTOCK_PUBLIC__ ?? {
 32        otelTracesEndpoint: "",
 33        otelServiceName: "",
 34      },
 35      user: null,
 36      flashes: [],
 37    };
 38  }
 39
 040  const { getSession } = await import("~/lib/session.server");
 041  const { drainFlashes } = await import("~/lib/toasts.server");
 042  let user: SessionUser | null = null;
 043  let flashes: readonly ToastInput[] = [];
 044  try {
 045    const sess = await getSession(request);
 046    user = sess?.data.user ?? null;
 047    flashes = await drainFlashes(request);
 48  } catch (err) {
 49    // Redis unavailable — serve page unauthenticated rather than crashing
 050    console.error("[root] session lookup failed:", err);
 51  }
 52
 053  return {
 54    publicRuntime: {
 55      otelTracesEndpoint:
 56        process.env.PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT?.trim() ||
 57        process.env.VITE_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT?.trim() ||
 58        "",
 59      otelServiceName:
 60        process.env.PUBLIC_OTEL_SERVICE_NAME?.trim() ||
 61        process.env.VITE_OTEL_SERVICE_NAME?.trim() ||
 62        "",
 63    },
 64    user,
 65    flashes,
 66  };
 67}
 68
 069function PublicRuntimeConfigScript() {
 070  const data = useRouteLoaderData("root");
 071  const cfg = data?.publicRuntime;
 072  if (!cfg) return null;
 073  const json = JSON.stringify(cfg);
 074  return (
 75    <script
 76      // Runs before module scripts; hydrates browser OTEL + other public runtime config.
 77      dangerouslySetInnerHTML={{
 78        __html: `window.__CLUTTERSTOCK_PUBLIC__=${json};`,
 79      }}
 80    />
 81  );
 82}
 83
 084export const links: Route.LinksFunction = () => [
 85  { rel: "icon", type: "image/svg+xml", href: "/brand/icon.svg" },
 86  { rel: "icon", type: "image/png", sizes: "32x32", href: "/brand/icon-32.png" },
 87  { rel: "icon", type: "image/png", sizes: "16x16", href: "/brand/icon-16.png" },
 88  { rel: "apple-touch-icon", sizes: "180x180", href: "/brand/apple-touch-icon.png" },
 89  { rel: "manifest", href: "/site.webmanifest" },
 90  { rel: "preconnect", href: "https://fonts.googleapis.com" },
 91  {
 92    rel: "preconnect",
 93    href: "https://fonts.gstatic.com",
 94    crossOrigin: "anonymous",
 95  },
 96  {
 97    rel: "stylesheet",
 98    href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swa
 99  },
 100];
 101
 0102export function Layout({
 103  children,
 104}: {
 105  readonly children: React.ReactNode;
 106}) {
 0107  const data = useRouteLoaderData("root") as RootLoaderData | undefined;
 0108  const initialFlashes = data?.flashes ?? [];
 109
 0110  return (
 111    <html lang="en" suppressHydrationWarning>
 112      <head>
 113        <meta charSet="utf-8" />
 114        <meta name="viewport" content="width=device-width, initial-scale=1" />
 115        {/* Set theme before first paint to avoid flash */}
 116        <script dangerouslySetInnerHTML={{ __html:
 117          `(function(){try{var t=localStorage.getItem('cs-theme');if(t&&['tui','win98','cde'].includes(t))document.docum
 118        }} />
 119        <Meta />
 120        <Links />
 121      </head>
 122      <body className="min-h-screen flex flex-col">
 123        <ToastProvider>
 124          <FlashToasts flashes={initialFlashes} />
 125          <SiteHeader />
 126          <div className="flex-1 flex flex-col">{children}</div>
 127          <SiteFooter />
 128          <Toaster />
 129        </ToastProvider>
 130        <PublicRuntimeConfigScript />
 131        <ScrollRestoration />
 132        <Scripts />
 133      </body>
 134    </html>
 135  );
 136}
 137
 0138export default function App() {
 0139  return <Outlet />;
 140}
 141
 0142export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
 0143  return (
 144    <main className="pt-16 p-4 container mx-auto">
 145      <ProblemBoundary error={error} scope="page" />
 146    </main>
 147  );
 148}