1import type { QueryClient } from '@tanstack/react-query'
2import { Toaster } from '@conar/ui/components/sonner'
3import appCss from '@conar/ui/globals.css?url'
4import { useMountedEffect } from '@conar/ui/hookas/use-mounted-effect'
5import { ThemeProvider } from '@conar/ui/theme-provider'
6import { QueryClientProvider } from '@tanstack/react-query'
7import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
8import { createRootRouteWithContext, HeadContent, Outlet, Scripts, useRouterState } from '@tanstack/react-router'
9import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
10import { ErrorPage } from '~/error-page'
11import { getLatestReleaseOptions, getRepoOptions, getUsersCountOptions } from '~/queries'
12import { seo } from '~/utils/seo'
13
14if (import.meta.env.DEV) {
15 import('react-scan').then(({ scan }) => {
16 scan()
17 })
18}
19
20export const Route = createRootRouteWithContext<{
21 queryClient: QueryClient
22}>()({
23 beforeLoad: async ({ context }) => {
24 if (typeof window !== 'undefined') {
25 context.queryClient.prefetchQuery(getRepoOptions)
26 context.queryClient.prefetchQuery(getLatestReleaseOptions)
27 context.queryClient.prefetchQuery(getUsersCountOptions)
28 }
29 },
30 head: () => ({
31 meta: [
32 {
33 charSet: 'utf-8',
34 },
35 {
36 name: 'viewport',
37 content: 'width=device-width, initial-scale=1',
38 },
39 ...seo({
40 title: 'Conar.app - AI-powered connections management tool',
41 description: 'AI-powered tool that makes database operations easier. Built for PostgreSQL. Modern alternative to traditional database management tools.',
42 image: '/og-image.png',
43 }),
44 { name: 'apple-mobile-web-app-title', content: 'Conar' },
45 ],
46 links: [
47 { rel: 'stylesheet', href: appCss },
48 { rel: 'icon', type: 'image/png', href: '/favicon-96x96.png', sizes: '96x96' },
49 { rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' },
50 { rel: 'shortcut icon', href: '/favicon.ico' },
51 { rel: 'apple-touch-icon', href: '/apple-touch-icon.png', sizes: '180x180' },
52 { rel: 'manifest', href: '/site.webmanifest' },
53 ],
54 scripts: [
55 {
56 defer: true,
57 src: 'https://assets.onedollarstats.com/stonks.js',
58 ...(import.meta.env.DEV ? { 'data-debug': 'conar.app' } : {}),
59 },
60 ],
61 }),
62 component: RootComponent,
63 errorComponent: props => <ErrorPage {...props} />,
64})
65
66function RootComponent() {
67 const { queryClient } = Route.useRouteContext()
68 const pathname = useRouterState({ select: state => state.location.pathname })
69
70 useMountedEffect(() => {
71 window.scrollTo({
72 top: 0,
73 })
74 }, [pathname])
75
76 return (
77 <html suppressHydrationWarning>
78 <head>
79 <HeadContent />
80 </head>
81 <body className="bg-gray-100 dark:bg-neutral-950">
82 <QueryClientProvider client={queryClient}>
83 <ThemeProvider>
84 <Outlet />
85 <ReactQueryDevtools buttonPosition="bottom-left" />
86 </ThemeProvider>
87 </QueryClientProvider>
88 <Toaster />
89 <TanStackRouterDevtools position="bottom-right" />
90 <Scripts />
91 </body>
92 </html>
93 )
94}