mirror of
https://github.com/documenso/documenso.git
synced 2026-08-24 07:12:23 +10:00
feat(remix): support serving app under a sub-path via NEXT_PUBLIC_BASE_PATH (#2824)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { authClient } from '@documenso/auth/client';
|
||||
import { AuthenticationErrorCode } from '@documenso/auth/server/lib/errors/error-codes';
|
||||
import { formatPath } from '@documenso/lib/constants/app';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { env } from '@documenso/lib/utils/env';
|
||||
import { zEmail } from '@documenso/lib/utils/zod';
|
||||
@@ -44,7 +45,7 @@ const handleFallbackErrorMessages = (code: string) => {
|
||||
return message;
|
||||
};
|
||||
|
||||
const LOGIN_REDIRECT_PATH = '/';
|
||||
const LOGIN_REDIRECT_PATH = formatPath('/');
|
||||
|
||||
export const ZSignInFormSchema = z.object({
|
||||
email: zEmail().min(1),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { formatPath } from '@documenso/lib/constants/app';
|
||||
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
|
||||
import {
|
||||
DOCUMENTS_PAGE_SHORTCUT,
|
||||
@@ -862,7 +863,7 @@ const PromptLanguageCommands = ({
|
||||
|
||||
formData.append('lang', lang);
|
||||
|
||||
const response = await fetch('/api/locale', {
|
||||
const response = await fetch(formatPath('/api/locale'), {
|
||||
method: 'post',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
+4
-1
@@ -1,4 +1,5 @@
|
||||
import { authClient } from '@documenso/auth/client';
|
||||
import { formatPath } from '@documenso/lib/constants/app';
|
||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { DialogFooter } from '@documenso/ui/primitives/dialog';
|
||||
@@ -34,7 +35,9 @@ export const DocumentSigningAuthAccount = ({
|
||||
const currentPath = `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
||||
|
||||
await authClient.signOut({
|
||||
redirectPath: `/signin?returnTo=${encodeURIComponent(currentPath)}#embedded=true&email=${isDirectTemplate ? '' : email}`,
|
||||
redirectPath: formatPath(
|
||||
`/signin?returnTo=${encodeURIComponent(currentPath)}#embedded=true&email=${isDirectTemplate ? '' : email}`,
|
||||
),
|
||||
});
|
||||
} catch {
|
||||
setIsSigningOut(false);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { authClient } from '@documenso/auth/client';
|
||||
import { formatPath } from '@documenso/lib/constants/app';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
@@ -21,10 +22,10 @@ export const DocumentSigningAuthPageView = ({ email, emailHasAccount }: Document
|
||||
try {
|
||||
setIsSigningOut(true);
|
||||
|
||||
let redirectPath = '/signin';
|
||||
let redirectPath = formatPath('/signin');
|
||||
|
||||
if (email) {
|
||||
redirectPath = emailHasAccount ? `/signin#email=${email}` : `/signup#email=${email}`;
|
||||
redirectPath = emailHasAccount ? formatPath(`/signin#email=${email}`) : formatPath(`/signup#email=${email}`);
|
||||
}
|
||||
|
||||
await authClient.signOut({
|
||||
|
||||
+10
-7
@@ -1,5 +1,6 @@
|
||||
import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session';
|
||||
import { SessionProvider } from '@documenso/lib/client-only/providers/session';
|
||||
import { getBasePath } from '@documenso/lib/constants/app';
|
||||
import { APP_I18N_OPTIONS, type SupportedLanguageCodes } from '@documenso/lib/constants/i18n';
|
||||
import { createPublicEnv } from '@documenso/lib/utils/env';
|
||||
import { extractLocaleData } from '@documenso/lib/utils/i18n';
|
||||
@@ -20,7 +21,6 @@ import {
|
||||
useMatches,
|
||||
} from 'react-router';
|
||||
import { PreventFlashOnWrongTheme, ThemeProvider, useTheme } from 'remix-themes';
|
||||
|
||||
import type { Route } from './+types/root';
|
||||
import stylesheet from './app.css?url';
|
||||
import { GenericErrorLayout } from './components/general/generic-error-layout';
|
||||
@@ -68,6 +68,7 @@ export async function loader({ context, request }: Route.LoaderArgs) {
|
||||
lang,
|
||||
theme: getTheme(),
|
||||
disableAnimations,
|
||||
basePath: getBasePath(),
|
||||
// Surface the per-request CSP nonce produced by `securityHeadersMiddleware` so all
|
||||
// SSR-rendered <script>/<style> elements in this layout (and child
|
||||
// routes that need it) can carry the matching nonce attribute.
|
||||
@@ -90,10 +91,10 @@ export async function loader({ context, request }: Route.LoaderArgs) {
|
||||
}
|
||||
|
||||
export function Layout({ children }: { children: React.ReactNode }) {
|
||||
const { theme } = useLoaderData<typeof loader>() || {};
|
||||
const { theme, basePath } = useLoaderData<typeof loader>() || {};
|
||||
|
||||
return (
|
||||
<ThemeProvider specifiedTheme={theme} themeAction="/api/theme">
|
||||
<ThemeProvider specifiedTheme={theme} themeAction={`${basePath ?? ''}/api/theme`}>
|
||||
<LayoutContent>{children}</LayoutContent>
|
||||
</ThemeProvider>
|
||||
);
|
||||
@@ -111,6 +112,8 @@ export function LayoutContent({ children }: { children: React.ReactNode }) {
|
||||
|
||||
const [theme] = useTheme();
|
||||
|
||||
const basePath = data.basePath ?? '';
|
||||
|
||||
// Recipient routes (signing pages) put `documenso-branded` on <body> so the
|
||||
// <style> block from `RecipientBranding` applies to BOTH the main tree and
|
||||
// any portaled content (Radix dialogs/popovers/dropdowns mount outside the
|
||||
@@ -126,11 +129,11 @@ export function LayoutContent({ children }: { children: React.ReactNode }) {
|
||||
<html translate="no" lang={lang} data-theme={theme} className={theme ?? ''} suppressHydrationWarning>
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<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" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href={`${basePath}/apple-touch-icon.png`} />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href={`${basePath}/favicon-32x32.png`} />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href={`${basePath}/favicon-16x16.png`} />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link rel="manifest" href={`${basePath}/site.webmanifest`} />
|
||||
<meta name="google" content="notranslate" />
|
||||
<Meta />
|
||||
<Links nonce={nonce(cspNonce)} />
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
*
|
||||
* No translations required.
|
||||
*/
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { formatPath } from '@documenso/lib/constants/app';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router';
|
||||
|
||||
export const loader = () => {
|
||||
@@ -147,7 +148,7 @@ export default function EmbedPlaygroundPage() {
|
||||
return inputToken;
|
||||
}
|
||||
|
||||
const response = await fetch('/api/v2/embedding/create-presign-token', {
|
||||
const response = await fetch(formatPath('/api/v2/embedding/create-presign-token'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${inputToken}`,
|
||||
|
||||
Reference in New Issue
Block a user