| import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; |
| import { |
| Outlet, |
| Link, |
| createRootRouteWithContext, |
| useRouter, |
| HeadContent, |
| Scripts, |
| } from "@tanstack/react-router"; |
| import { useEffect, type ReactNode } from "react"; |
|
|
| import appCss from "../styles.css?url"; |
| import { reportLovableError } from "../lib/lovable-error-reporting"; |
|
|
| function NotFoundComponent() { |
| return ( |
| <div className="flex min-h-screen items-center justify-center bg-background px-4"> |
| <div className="max-w-md text-center"> |
| <h1 className="text-7xl font-bold text-foreground">404</h1> |
| <h2 className="mt-4 text-xl font-semibold text-foreground">Page not found</h2> |
| <p className="mt-2 text-sm text-muted-foreground"> |
| The page you're looking for doesn't exist or has been moved. |
| </p> |
| <div className="mt-6"> |
| <Link |
| to="/" |
| className="inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90" |
| > |
| Go home |
| </Link> |
| </div> |
| </div> |
| </div> |
| ); |
| } |
|
|
| function ErrorComponent({ error, reset }: { error: Error; reset: () => void }) { |
| console.error(error); |
| const router = useRouter(); |
| useEffect(() => { |
| reportLovableError(error, { boundary: "tanstack_root_error_component" }); |
| }, [error]); |
|
|
| return ( |
| <div className="flex min-h-screen items-center justify-center bg-background px-4"> |
| <div className="max-w-md text-center"> |
| <h1 className="text-xl font-semibold tracking-tight text-foreground"> |
| This page didn't load |
| </h1> |
| <p className="mt-2 text-sm text-muted-foreground"> |
| Something went wrong on our end. You can try refreshing or head back home. |
| </p> |
| <div className="mt-6 flex flex-wrap justify-center gap-2"> |
| <button |
| onClick={() => { |
| router.invalidate(); |
| reset(); |
| }} |
| className="inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90" |
| > |
| Try again |
| </button> |
| <a |
| href="/" |
| className="inline-flex items-center justify-center rounded-md border border-input bg-background px-4 py-2 text-sm font-medium text-foreground transition-colors hover:bg-accent" |
| > |
| Go home |
| </a> |
| </div> |
| </div> |
| </div> |
| ); |
| } |
|
|
| export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({ |
| head: () => ({ |
| meta: [ |
| { charSet: "utf-8" }, |
| { name: "viewport", content: "width=device-width, initial-scale=1" }, |
| { title: "Imageboard Grabber" }, |
| { |
| name: "description", |
| content: |
| "Search booru-style imageboards, browse thumbnails, and batch-download posts as ZIP.", |
| }, |
| { property: "og:title", content: "Imageboard Grabber" }, |
| { |
| property: "og:description", |
| content: |
| "Search booru-style imageboards, browse thumbnails, and batch-download posts as ZIP.", |
| }, |
| { property: "og:type", content: "website" }, |
| { name: "twitter:card", content: "summary_large_image" }, |
| ], |
| links: [ |
| { |
| rel: "stylesheet", |
| href: appCss, |
| }, |
| { rel: "icon", href: "/favicon.ico", type: "image/x-icon" }, |
| ], |
| }), |
| shellComponent: RootShell, |
| component: RootComponent, |
| notFoundComponent: NotFoundComponent, |
| errorComponent: ErrorComponent, |
| }); |
|
|
| function RootShell({ children }: { children: ReactNode }) { |
| return ( |
| <html lang="en"> |
| <head> |
| <HeadContent /> |
| </head> |
| <body> |
| {children} |
| <Scripts /> |
| </body> |
| </html> |
| ); |
| } |
|
|
| function RootComponent() { |
| const { queryClient } = Route.useRouteContext(); |
|
|
| return ( |
| <QueryClientProvider client={queryClient}> |
| {/* Required: nested routes render here. Removing <Outlet /> breaks all child routes. */} |
| <Outlet /> |
| </QueryClientProvider> |
| ); |
| } |
|
|