2025-02-04 16:24:26 +11:00
import { Suspense } from 'react' ;
2025-01-02 15:33:37 +11:00
import {
Links ,
Meta ,
Outlet ,
Scripts ,
ScrollRestoration ,
2025-02-03 19:52:23 +11:00
data ,
2025-01-02 15:33:37 +11:00
isRouteErrorResponse ,
2025-01-31 14:09:02 +11:00
useLoaderData ,
2025-01-02 15:33:37 +11:00
} from 'react-router' ;
2025-02-03 14:10:28 +11:00
import { ThemeProvider } from 'remix-themes' ;
2025-01-02 15:33:37 +11:00
2025-02-03 14:10:28 +11:00
import { SessionProvider } from '@documenso/lib/client-only/providers/session' ;
2025-02-03 19:52:23 +11:00
import { APP_I18N_OPTIONS } from '@documenso/lib/constants/i18n' ;
import { extractLocaleData } from '@documenso/lib/utils/i18n' ;
2025-01-02 15:33:37 +11:00
import { TrpcProvider } from '@documenso/trpc/react' ;
import { Toaster } from '@documenso/ui/primitives/toaster' ;
import { TooltipProvider } from '@documenso/ui/primitives/tooltip' ;
import type { Route } from './+types/root' ;
import stylesheet from './app.css?url' ;
2025-02-03 23:56:27 +11:00
import { GenericErrorLayout } from './components/general/generic-error-layout' ;
2025-02-04 16:24:26 +11:00
import { PostHogPageview } from './providers/posthog' ;
2025-02-03 19:52:23 +11:00
import { langCookie } from './storage/lang-cookie.server' ;
2025-02-03 14:10:28 +11:00
import { themeSessionResolver } from './storage/theme-session.server' ;
2025-01-02 15:33:37 +11:00
export const links : Route.LinksFunction = ( ) = > [
{ 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=Caveat:wght@400..600&display=swap' ,
} ,
{
rel : 'stylesheet' ,
href : 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap' ,
} ,
{ rel : 'stylesheet' , href : stylesheet } ,
] ;
2025-02-04 16:24:26 +11:00
// Todo: Meta data.
// export function generateMetadata() {
// return {
// title: {
// template: '%s - Documenso',
// default: 'Documenso',
// },
// description:
// 'Join Documenso, the open signing infrastructure, and get a 10x better signing experience. Pricing starts at $30/mo. forever! Sign in now and enjoy a faster, smarter, and more beautiful document signing process. Integrates with your favorite tools, customizable, and expandable. Support our mission and become a part of our open-source community.',
// keywords:
// 'Documenso, open source, DocuSign alternative, document signing, open signing infrastructure, open-source community, fast signing, beautiful signing, smart templates',
// authors: { name: 'Documenso, Inc.' },
// robots: 'index, follow',
// metadataBase: new URL(NEXT_PUBLIC_WEBAPP_URL() ?? 'http://localhost:3000'),
// openGraph: {
// title: 'Documenso - The Open Source DocuSign Alternative',
// description:
// 'Join Documenso, the open signing infrastructure, and get a 10x better signing experience. Pricing starts at $30/mo. forever! Sign in now and enjoy a faster, smarter, and more beautiful document signing process. Integrates with your favorite tools, customizable, and expandable. Support our mission and become a part of our open-source community.',
// type: 'website',
// images: ['/opengraph-image.jpg'],
// },
// twitter: {
// site: '@documenso',
// card: 'summary_large_image',
// images: ['/opengraph-image.jpg'],
// description:
// 'Join Documenso, the open signing infrastructure, and get a 10x better signing experience. Pricing starts at $30/mo. forever! Sign in now and enjoy a faster, smarter, and more beautiful document signing process. Integrates with your favorite tools, customizable, and expandable. Support our mission and become a part of our open-source community.',
// },
// };
// }
2025-02-03 14:10:28 +11:00
export async function loader ( { request , context } : Route . LoaderArgs ) {
const { getTheme } = await themeSessionResolver ( request ) ;
2025-02-03 19:52:23 +11:00
let lang = await langCookie . parse ( request . headers . get ( 'cookie' ) ? ? '' ) ;
if ( ! APP_I18N_OPTIONS . supportedLangs . includes ( lang ) ) {
lang = extractLocaleData ( { headers : request.headers } ) ;
}
return data (
{
lang ,
theme : getTheme ( ) ,
session : context.session ,
__ENV__ : Object.fromEntries (
Object . entries ( process . env ) . filter ( ( [ key ] ) = > key . startsWith ( 'NEXT_' ) ) , // Todo: I'm pretty sure this will leak?
) ,
} ,
{
headers : {
'Set-Cookie' : await langCookie . serialize ( lang ) ,
} ,
} ,
) ;
2025-01-31 14:09:02 +11:00
}
2025-01-02 15:33:37 +11:00
export function Layout ( { children } : { children : React.ReactNode } ) {
2025-02-03 19:52:23 +11:00
const { __ENV__ , theme , lang } = useLoaderData < typeof loader > ( ) || { } ;
2025-02-03 14:10:28 +11:00
// const [theme] = useTheme();
2025-01-31 14:09:02 +11:00
2025-01-02 15:33:37 +11:00
return (
2025-02-03 19:52:23 +11:00
< html translate = "no" lang = { lang } data-theme = { theme ? ? '' } >
2025-01-02 15:33:37 +11:00
< head >
< meta charSet = "utf-8" / >
2025-02-03 20:09:35 +11:00
< link rel = "apple-touch-icon" sizes = "180x180" href = "/apple-touch-icon.png" / >
< link rel = "icon" type = "image/png" sizes = "32x32" href = "/favicon-32x32.png" / >
< link rel = "icon" type = "image/png" sizes = "16x16" href = "/favicon-16x16.png" / >
2025-01-02 15:33:37 +11:00
< meta name = "viewport" content = "width=device-width, initial-scale=1" / >
2025-02-03 23:56:27 +11:00
< link rel = "manifest" href = "/site.webmanifest" / >
2025-02-04 16:24:26 +11:00
< meta name = "google" content = "notranslate" / >
2025-01-02 15:33:37 +11:00
< Meta / >
< Links / >
2025-02-03 19:52:23 +11:00
< meta name = "google" content = "notranslate" / >
2025-02-03 14:10:28 +11:00
{ /* <PreventFlashOnWrongTheme ssrTheme={Boolean(theme)} /> */ }
2025-02-04 16:24:26 +11:00
< Suspense >
< PostHogPageview / >
< / Suspense >
2025-01-02 15:33:37 +11:00
< / head >
< body >
{ children }
< ScrollRestoration / >
< Scripts / >
2025-01-31 14:09:02 +11:00
< script
dangerouslySetInnerHTML = { {
__html : ` window.__ENV__ = ${ JSON . stringify ( __ENV__ ) } ` ,
} }
/ >
2025-01-02 15:33:37 +11:00
< / body >
< / html >
) ;
}
2025-02-03 14:10:28 +11:00
export default function App ( { loaderData } : Route . ComponentProps ) {
2025-01-02 15:33:37 +11:00
return (
2025-02-03 14:10:28 +11:00
< SessionProvider session = { loaderData . session } >
{ /* Todo: Themes (this won't work for now) */ }
< ThemeProvider specifiedTheme = { loaderData . theme } themeAction = "/api/theme" >
< TooltipProvider >
< TrpcProvider >
< Outlet / >
2025-01-02 15:33:37 +11:00
2025-02-03 14:10:28 +11:00
< Toaster / >
< / TrpcProvider >
< / TooltipProvider >
< / ThemeProvider >
< / SessionProvider >
2025-01-02 15:33:37 +11:00
) ;
}
export function ErrorBoundary ( { error } : Route . ErrorBoundaryProps ) {
2025-02-03 23:56:27 +11:00
const errorCode = isRouteErrorResponse ( error ) ? error.status : 500 ;
2025-01-02 15:33:37 +11:00
2025-02-03 23:56:27 +11:00
return < GenericErrorLayout errorCode = { errorCode } / > ;
2025-01-02 15:33:37 +11:00
}