Files
sign/apps/remix/app/root.tsx

165 lines
5.9 KiB
TypeScript
Raw Normal View History

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
}