feat: add themes

This commit is contained in:
David Nguyen
2025-02-14 17:50:23 +11:00
parent 28f5177064
commit 180656978b
4 changed files with 38 additions and 36 deletions

View File

@@ -12,7 +12,7 @@ import {
useLoaderData, useLoaderData,
useLocation, useLocation,
} from 'react-router'; } from 'react-router';
import { ThemeProvider } from 'remix-themes'; import { PreventFlashOnWrongTheme, ThemeProvider, useTheme } from 'remix-themes';
import { getOptionalLoaderSession } from 'server/utils/get-loader-session'; import { getOptionalLoaderSession } from 'server/utils/get-loader-session';
import { SessionProvider } from '@documenso/lib/client-only/providers/session'; import { SessionProvider } from '@documenso/lib/client-only/providers/session';
@@ -84,13 +84,13 @@ export async function loader({ request }: Route.LoaderArgs) {
); );
} }
export function Layout({ children }: { children: React.ReactNode }) { export function App() {
const { publicEnv, theme, lang } = useLoaderData<typeof loader>() || {}; const { publicEnv, lang, session, ...data } = useLoaderData<typeof loader>() || {};
// const [theme] = useTheme(); const [theme] = useTheme();
return ( return (
<html translate="no" lang={lang} data-theme={theme ?? ''}> <html translate="no" lang={lang} className={theme ?? ''}>
<head> <head>
<meta charSet="utf-8" /> <meta charSet="utf-8" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
@@ -102,18 +102,25 @@ export function Layout({ children }: { children: React.ReactNode }) {
<Meta /> <Meta />
<Links /> <Links />
<meta name="google" content="notranslate" /> <meta name="google" content="notranslate" />
{/* <PreventFlashOnWrongTheme ssrTheme={Boolean(theme)} /> */} <PreventFlashOnWrongTheme ssrTheme={Boolean(data.theme)} />
<Suspense> <Suspense>
<PostHogPageview /> <PostHogPageview />
</Suspense> </Suspense>
</head> </head>
<body> <body>
{children} <SessionProvider session={session}>
<TooltipProvider>
<TrpcProvider>
<Outlet />
<Toaster />
</TrpcProvider>
</TooltipProvider>
</SessionProvider>
<ScrollRestoration /> <ScrollRestoration />
<Scripts /> <Scripts />
{/* Todo: Do we want this here? */}
<RefreshOnFocus /> <RefreshOnFocus />
<script <script
@@ -126,7 +133,14 @@ export function Layout({ children }: { children: React.ReactNode }) {
); );
} }
export default function App({ loaderData }: Route.ComponentProps) { /**
* We have this weird setup with:
* - No root layout
* - AppWithTheme
*
* To handle remix-themes.
*/
export default function AppWithTheme({ loaderData }: Route.ComponentProps) {
const location = useLocation(); const location = useLocation();
useEffect(() => { useEffect(() => {
@@ -134,18 +148,9 @@ export default function App({ loaderData }: Route.ComponentProps) {
}, [location.pathname]); }, [location.pathname]);
return ( return (
<SessionProvider session={loaderData.session}>
{/* Todo: Themes (this won't work for now) */}
<ThemeProvider specifiedTheme={loaderData.theme} themeAction="/api/theme"> <ThemeProvider specifiedTheme={loaderData.theme} themeAction="/api/theme">
<TooltipProvider> <App />
<TrpcProvider>
<Outlet />
<Toaster />
</TrpcProvider>
</TooltipProvider>
</ThemeProvider> </ThemeProvider>
</SessionProvider>
); );
} }

View File

@@ -1,8 +1,7 @@
import { createCookieSessionStorage } from 'react-router'; import { createCookieSessionStorage } from 'react-router';
import { createThemeSessionResolver } from 'remix-themes'; import { createThemeSessionResolver } from 'remix-themes';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; import { getCookieDomain, useSecureCookies } from '@documenso/lib/constants/auth';
import { env } from '@documenso/lib/utils/env';
const themeSessionStorage = createCookieSessionStorage({ const themeSessionStorage = createCookieSessionStorage({
cookie: { cookie: {
@@ -10,10 +9,9 @@ const themeSessionStorage = createCookieSessionStorage({
path: '/', path: '/',
httpOnly: true, httpOnly: true,
sameSite: 'lax', sameSite: 'lax',
secrets: ['insecure-secret'], // Todo: Don't need secret secrets: ['insecure-secret-do-not-care'],
// Todo: Check this works on production. secure: useSecureCookies,
// Set domain and secure only if in production domain: getCookieDomain(),
...(env('NODE_ENV') === 'production' ? { domain: NEXT_PUBLIC_WEBAPP_URL(), secure: true } : {}),
}, },
}); });

View File

@@ -1,8 +1,7 @@
import type { Context } from 'hono'; import type { Context } from 'hono';
import { deleteCookie, getSignedCookie, setSignedCookie } from 'hono/cookie'; import { deleteCookie, getSignedCookie, setSignedCookie } from 'hono/cookie';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; import { getCookieDomain, useSecureCookies } from '@documenso/lib/constants/auth';
import { useSecureCookies } from '@documenso/lib/constants/auth';
import { appLog } from '@documenso/lib/utils/debugger'; import { appLog } from '@documenso/lib/utils/debugger';
import { env } from '@documenso/lib/utils/env'; import { env } from '@documenso/lib/utils/env';
@@ -20,12 +19,6 @@ const getAuthSecret = () => {
return authSecret; return authSecret;
}; };
const getAuthDomain = () => {
const url = new URL(NEXT_PUBLIC_WEBAPP_URL());
return url.hostname;
};
/** /**
* Generic auth session cookie options. * Generic auth session cookie options.
*/ */
@@ -34,7 +27,7 @@ export const sessionCookieOptions = {
path: '/', path: '/',
sameSite: useSecureCookies ? 'none' : 'lax', // Todo: This feels wrong? sameSite: useSecureCookies ? 'none' : 'lax', // Todo: This feels wrong?
secure: useSecureCookies, secure: useSecureCookies,
domain: getAuthDomain(), domain: getCookieDomain(),
// Todo: Max age for specific auth cookies. // Todo: Max age for specific auth cookies.
} as const; } as const;

View File

@@ -56,3 +56,9 @@ export const useSecureCookies =
const secureCookiePrefix = useSecureCookies ? '__Secure-' : ''; const secureCookiePrefix = useSecureCookies ? '__Secure-' : '';
export const formatSecureCookieName = (name: string) => `${secureCookiePrefix}${name}`; export const formatSecureCookieName = (name: string) => `${secureCookiePrefix}${name}`;
export const getCookieDomain = () => {
const url = new URL(NEXT_PUBLIC_WEBAPP_URL());
return url.hostname;
};