Merge branch 'main' into chore/add-manual-translations

This commit is contained in:
David Nguyen
2026-08-14 10:58:56 +10:00
committed by GitHub
44 changed files with 1276 additions and 229 deletions
+46
View File
@@ -6,6 +6,42 @@ export const APP_DOCUMENT_UPLOAD_SIZE_LIMIT = Number(env('NEXT_PUBLIC_DOCUMENT_S
export const NEXT_PUBLIC_WEBAPP_URL = () => env('NEXT_PUBLIC_WEBAPP_URL') ?? 'http://localhost:3000';
/**
* The sub-path the app is served under (no trailing slash), e.g. "/ESign".
* Returns an empty string when served at root.
*
* Prefers the explicit NEXT_PUBLIC_BASE_PATH (which is the same value baked
* into the Vite/React Router build). Falls back to the pathname of
* NEXT_PUBLIC_WEBAPP_URL so the function still works in dev when the env
* variable is unset.
*
* Avoid using this to build URLs, use {@link formatPath} instead. Reserve this
* for cases where the raw prefix itself is needed, such as path comparisons.
*/
export const getBasePath = (): string => {
const explicit = env('NEXT_PUBLIC_BASE_PATH');
if (explicit) {
return explicit.replace(/\/$/, '');
}
try {
return new URL(NEXT_PUBLIC_WEBAPP_URL()).pathname.replace(/\/$/, '');
} catch {
return '';
}
};
/**
* Prefix a root-relative path with the app's base path.
*
* `formatPath('/api/trpc')` -> `/ESign/api/trpc` under sub-path hosting,
* `/api/trpc` otherwise.
*/
export const formatPath = (path: string): string => {
return `${getBasePath()}${path}`;
};
export const NEXT_PUBLIC_SIGNING_CONTACT_INFO = () =>
env('NEXT_PUBLIC_SIGNING_CONTACT_INFO') ?? NEXT_PUBLIC_WEBAPP_URL();
@@ -17,6 +53,14 @@ export const NEXT_PRIVATE_INTERNAL_WEBAPP_URL = () =>
export const IS_BILLING_ENABLED = () => env('NEXT_PUBLIC_FEATURE_BILLING_ENABLED') === 'true';
/**
* Whether this instance is Documenso Cloud (managed SaaS).
*
* Used so we can show a different UI for Documenso Cloud and self-hosted instances since
* there are things like billing, upsells, documenso links, etc that don't make sense for self-hosted instances.
*/
export const IS_DOCUMENSO_CLOUD = () => env('NEXT_PUBLIC_IS_DOCUMENSO_CLOUD') === 'true';
export const API_V2_BETA_URL = '/api/v2-beta';
export const API_V2_URL = '/api/v2';
@@ -99,3 +143,5 @@ export const CSC_INSTANCE_SIGNATURE_LEVEL = (): TSignatureLevel => {
return value;
};
export const DOCUMENSO_CLOUD_ENTERPRISE_CTA_URL = 'https://documen.so/enterprise-cta';
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,10 +1,10 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import { NEXT_PUBLIC_WEBAPP_URL } from '../constants/app';
import { NEXT_PUBLIC_WEBAPP_URL, getBasePath } from '../constants/app';
import { env } from '../utils/env';
export const getBaseUrl = () => {
if (typeof window !== 'undefined') {
return '';
return getBasePath();
}
const webAppUrl = NEXT_PUBLIC_WEBAPP_URL();
+3 -1
View File
@@ -2,6 +2,8 @@ import { DocumentDataType } from '@prisma/client';
import { base64 } from '@scure/base';
import { match } from 'ts-pattern';
import { formatPath } from '../../constants/app';
export type GetFileOptions = {
type: DocumentDataType;
data: string;
@@ -36,7 +38,7 @@ const getFileFromBytes64 = (data: string) => {
};
const getFileFromS3 = async (key: string) => {
const getPresignedUrlResponse = await fetch(`/api/files/presigned-get-url`, {
const getPresignedUrlResponse = await fetch(formatPath('/api/files/presigned-get-url'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
+2 -1
View File
@@ -1,5 +1,6 @@
import type { TUploadPdfResponse } from '@documenso/remix/server/api/files/files.types';
import { formatPath } from '../../constants/app';
import { AppError } from '../../errors/app-error';
type File = {
@@ -38,7 +39,7 @@ export const putPdfFile = async (file: File, options?: PutFileOptions) => {
formData.append('file', properFile);
const response = await fetch('/api/files/upload-pdf', {
const response = await fetch(formatPath('/api/files/upload-pdf'), {
method: 'POST',
headers: buildUploadAuthHeaders(options),
body: formData,
+27 -3
View File
@@ -1,4 +1,15 @@
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { getBasePath, NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
/**
* The origin of the web app, ignoring any sub-path NEXT_PUBLIC_WEBAPP_URL carries.
*/
const getWebAppOrigin = () => {
try {
return new URL(NEXT_PUBLIC_WEBAPP_URL()).origin;
} catch {
return NEXT_PUBLIC_WEBAPP_URL();
}
};
export const isValidReturnTo = (returnTo?: string) => {
if (!returnTo) {
@@ -10,7 +21,10 @@ export const isValidReturnTo = (returnTo?: string) => {
const decodedReturnTo = decodeURIComponent(returnTo);
const returnToUrl = new URL(decodedReturnTo, NEXT_PUBLIC_WEBAPP_URL());
if (returnToUrl.origin !== NEXT_PUBLIC_WEBAPP_URL()) {
// Compare against the origin, not the raw env value: when the app is served
// under a sub-path NEXT_PUBLIC_WEBAPP_URL is e.g. "https://host/ESign", which
// never equals a URL's origin ("https://host").
if (returnToUrl.origin !== getWebAppOrigin()) {
return false;
}
@@ -30,7 +44,17 @@ export const normalizeReturnTo = (returnTo?: string) => {
const decodedReturnTo = decodeURIComponent(returnTo);
const returnToUrl = new URL(decodedReturnTo, NEXT_PUBLIC_WEBAPP_URL());
return `${returnToUrl.pathname}${returnToUrl.search}${returnToUrl.hash}`;
const basePath = getBasePath();
let pathname = returnToUrl.pathname;
// A root-relative returnTo ("/inbox") resolves to a pathname without the
// sub-path, so re-apply it when it is missing.
if (basePath && pathname !== basePath && !pathname.startsWith(`${basePath}/`)) {
pathname = `${basePath}${pathname}`;
}
return `${pathname}${returnToUrl.search}${returnToUrl.hash}`;
} catch {
return undefined;
}
+4 -3
View File
@@ -18,7 +18,7 @@ import {
} from 'lucide-react';
import type { ComponentType } from 'react';
import { FaUsers } from 'react-icons/fa6';
import { IS_BILLING_ENABLED } from '../constants/app';
import { IS_BILLING_ENABLED, IS_DOCUMENSO_CLOUD } from '../constants/app';
import { canExecuteOrganisationAction } from './organisations';
import { canExecuteTeamAction } from './teams';
@@ -73,6 +73,7 @@ export const getSettingsNavGroups = ({
hasManageableBillingOrgs,
}: GetSettingsNavGroupsArgs): SettingsNavGroups => {
const isBillingEnabled = IS_BILLING_ENABLED();
const isDocumensoCloud = IS_DOCUMENSO_CLOUD();
const canManageOrg =
organisation !== null && canExecuteOrganisationAction('MANAGE_ORGANISATION', organisation.currentOrganisationRole);
@@ -126,7 +127,7 @@ export const getSettingsNavGroups = ({
label: msg`Certificates`,
isSubNav: true,
},
...(isBillingEnabled && organisation.organisationClaim.flags.emailDomains
...((isBillingEnabled && organisation.organisationClaim.flags.emailDomains) || isDocumensoCloud
? [
{
key: 'email-domains',
@@ -154,7 +155,7 @@ export const getSettingsNavGroups = ({
label: msg`Groups`,
icon: GroupIcon,
},
...(isBillingEnabled && organisation.organisationClaim.flags.authenticationPortal
...((isBillingEnabled && organisation.organisationClaim.flags.authenticationPortal) || isDocumensoCloud
? [
{
key: 'sso',