import { HeadContent, Navigate, Outlet, Scripts, createRootRouteWithContext, isRedirect, } from "@tanstack/react-router"; import * as React from "react"; import { DefaultCatchBoundary } from "@/components/DefaultCatchBoundary"; import { type QueryClient } from "@tanstack/react-query"; import { ensureSuperTokensFrontend } from "@/lib/supertokens/client"; import { AuthContextType } from "@/contexts/auth-context"; import Providers from "@/features/core/components/providers"; import { SessionMonitor } from "@/components/session-monitor"; import { IOSInstallPrompt } from "@/components/ios-install-prompt"; import { ColorSchemeScript, mantineHtmlProps } from "@mantine/core"; import { HeaderConfig } from "@/features/core/types/header-config"; import { playerQueries } from "@/features/players/queries"; import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure"; import { DEFAULT_LOCALE, ensureMessages, resolveLocale } from "@/lib/i18n"; import { getRootMeta } from "@/lib/i18n/meta"; import FullScreenLoader from "@/components/full-screen-loader"; import { CHROME_COLORS } from "@/lib/mantine/theme-colors"; import mantineCssUrl from '@mantine/core/styles.css?url' import mantineDatesCssUrl from '@mantine/dates/styles.css?url' import mantineCarouselCssUrl from '@mantine/carousel/styles.css?url' import mantineTiptapCssUrl from '@mantine/tiptap/styles.css?url' export const Route = createRootRouteWithContext<{ queryClient: QueryClient; auth: AuthContextType; header: HeaderConfig; refresh: string[]; withPadding: boolean; fullWidth: boolean; }>()({ head: () => { // Meta stays DEFAULT_LOCALE: crawlers/unfurlers have no session, and // reading match.context here creates circular route-type inference that // breaks beforeLoad typing in child routes. const rootMeta = getRootMeta(DEFAULT_LOCALE); return { title: "FLXN IX", meta: [ { charSet: "utf-8", }, { name: "viewport", content: "width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, interactive-widget=resizes-content, viewport-fit=cover", }, { name: 'description', content: rootMeta.description }, { name: 'keywords', content: 'FLXN, beer pong, tournament, sports, statistics, pong' }, { property: 'og:title', content: 'FLXN' }, { property: 'og:description', content: rootMeta.description }, { property: 'og:url', content: 'https://flexxon.app' }, { property: 'og:type', content: 'website' }, { property: 'og:site_name', content: 'FLXN' }, { property: 'og:image', content: 'https://flexxon.app/favicon.png' }, { property: 'og:image:width', content: '512' }, { property: 'og:image:height', content: '512' }, { property: 'og:image:alt', content: rootMeta.ogImageAlt }, { property: 'og:locale', content: rootMeta.ogLocale }, { name: 'twitter:card', content: 'summary' }, { name: 'twitter:title', content: 'FLXN' }, { name: 'twitter:description', content: rootMeta.description }, { name: 'twitter:image', content: 'https://flexxon.app/favicon.png' }, { name: 'mobile-web-app-capable', content: 'yes' }, { name: 'apple-mobile-web-app-capable', content: 'yes' }, { name: 'apple-mobile-web-app-status-bar-style', content: 'default' }, { name: 'apple-mobile-web-app-title', content: 'FLXN' }, ], links: [ { rel: "apple-touch-icon", sizes: "180x180", href: "/apple-touch-icon.png", }, { rel: "icon", type: "image/png", sizes: "32x32", href: "/favicon-32x32.png", }, { rel: "icon", type: "image/png", sizes: "16x16", href: "/favicon-16x16.png", }, { rel: "manifest", href: "/site.webmanifest" }, { rel: "icon", href: "/favicon.ico" }, { rel: 'stylesheet', href: mantineCssUrl }, { rel: 'stylesheet', href: mantineCarouselCssUrl }, { rel: 'stylesheet', href: mantineDatesCssUrl }, { rel: 'stylesheet', href: mantineTiptapCssUrl }, { rel: "preconnect", href: "https://fonts.googleapis.com" }, { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous", }, { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=League+Spartan:wght@100..900&display=swap", } ], }; }, errorComponent: (props) => { return ( ); }, component: RootComponent, notFoundComponent: () => , beforeLoad: async ({ context, location }) => { const publicRoutes = ['/login', '/logout', '/refresh-session']; if (publicRoutes.some(route => location.pathname.startsWith(route))) { return {}; } try { const auth = await ensureServerQueryData( context.queryClient, playerQueries.auth() ); await ensureMessages(resolveLocale(auth?.metadata?.locale)); return { auth }; } catch (error: any) { if (isRedirect(error) || error instanceof Response) throw error; if (typeof window !== 'undefined') { const { doesSessionExist, attemptRefreshingSession } = await import('supertokens-web-js/recipe/session'); const sessionExists = await doesSessionExist(); if (sessionExists) { try { await attemptRefreshingSession(); const auth = await ensureServerQueryData( context.queryClient, playerQueries.auth() ); await ensureMessages(resolveLocale(auth?.metadata?.locale)); return { auth }; } catch { return {}; } } } return {}; } }, pendingComponent: () => , }); function RootComponent() { React.useEffect(() => { ensureSuperTokensFrontend(); if (import.meta.env.PROD && 'serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js').catch(() => {}); } }, []); return ( ); } // todo: analytics -> process.env data-website-id function RootDocument({ children }: { children: React.ReactNode }) { return (
{children}
); }