chore: merge main, resolve biome formatting conflicts

Merge origin/main into feat/external-2fa-codes. Resolve formatting
conflicts caused by biome rollout; preserve both feature streams:
PR's external 2FA token + signing-session 2FA proof additions plus
main's RateLimit/RecipientExpired/signingReminders/date-auto-insert.

In complete-document-with-token.ts, drop the duplicate early
field-fetching block introduced when main moved that logic later
with date auto-insert support; keep the EXTERNAL_TWO_FACTOR_AUTH
check using derivedRecipientActionAuth.
This commit is contained in:
ephraimduncan
2026-05-12 11:46:11 +00:00
parent 9194884fbe
commit 138d663c25
1959 changed files with 93488 additions and 47038 deletions
+32 -40
View File
@@ -1,41 +1,6 @@
import { CSS_LENGTH_REGEX, type TCssVarsSchema } from '@documenso/lib/types/css-vars';
import { colord } from 'colord';
import { toKebabCase } from 'remeda';
import { z } from 'zod';
export const ZCssVarsSchema = z
.object({
background: z.string().optional().describe('Base background color'),
foreground: z.string().optional().describe('Base text color'),
muted: z.string().optional().describe('Muted/subtle background color'),
mutedForeground: z.string().optional().describe('Muted/subtle text color'),
popover: z.string().optional().describe('Popover/dropdown background color'),
popoverForeground: z.string().optional().describe('Popover/dropdown text color'),
card: z.string().optional().describe('Card background color'),
cardBorder: z.string().optional().describe('Card border color'),
cardBorderTint: z.string().optional().describe('Card border tint/highlight color'),
cardForeground: z.string().optional().describe('Card text color'),
fieldCard: z.string().optional().describe('Field card background color'),
fieldCardBorder: z.string().optional().describe('Field card border color'),
fieldCardForeground: z.string().optional().describe('Field card text color'),
widget: z.string().optional().describe('Widget background color'),
widgetForeground: z.string().optional().describe('Widget text color'),
border: z.string().optional().describe('Default border color'),
input: z.string().optional().describe('Input field border color'),
primary: z.string().optional().describe('Primary action/button color'),
primaryForeground: z.string().optional().describe('Primary action/button text color'),
secondary: z.string().optional().describe('Secondary action/button color'),
secondaryForeground: z.string().optional().describe('Secondary action/button text color'),
accent: z.string().optional().describe('Accent/highlight color'),
accentForeground: z.string().optional().describe('Accent/highlight text color'),
destructive: z.string().optional().describe('Destructive/danger action color'),
destructiveForeground: z.string().optional().describe('Destructive/danger text color'),
ring: z.string().optional().describe('Focus ring color'),
radius: z.string().optional().describe('Border radius size in REM units'),
warning: z.string().optional().describe('Warning/alert color'),
})
.describe('Custom CSS variables for theming');
export type TCssVarsSchema = z.infer<typeof ZCssVarsSchema>;
export const toNativeCssVars = (vars: TCssVarsSchema) => {
const cssVars: Record<string, string> = {};
@@ -47,23 +12,50 @@ export const toNativeCssVars = (vars: TCssVarsSchema) => {
const color = colord(value);
const { h, s, l } = color.toHsl();
cssVars[`--${toKebabCase(key)}`] = `${h} ${s} ${l}`;
// Tailwind's theme.css consumes these via `hsl(var(--token))`. CSS
// Color 4 space-separated `hsl()` requires `%` on saturation and
// lightness — without it, the function is invalid and the property
// falls back to its initial value (which is why bare numeric output
// here used to silently break customer colours).
cssVars[`--${toKebabCase(key)}`] = `${h} ${s}% ${l}%`;
}
}
if (radius) {
cssVars[`--radius`] = `${radius}`;
// Defence in depth: radius is interpolated raw into the rendered <style>
// block, so anything outside the length pattern is a CSS-injection vector.
// The Zod schema rejects bad values at the API boundary; this re-check
// protects against schema drift and any path that bypasses validation.
if (radius && CSS_LENGTH_REGEX.test(radius)) {
cssVars[`--radius`] = radius;
}
return cssVars;
};
/**
* Pure-string sibling of `toNativeCssVars` — returns the same set of CSS custom
* property declarations as a single string suitable for SSR inlining inside a
* rule block. Does not touch the DOM.
*
* Example: { background: '#111', radius: '0.5rem' }
* -> "--background: 0 0% 6.7%; --radius: 0.5rem;"
*
* Saturation and lightness include the `%` suffix that
* `hsl(var(--token))` requires under CSS Color 4 space-separated syntax.
*/
export const toNativeCssVarsString = (vars: TCssVarsSchema): string => {
const map = toNativeCssVars(vars);
return Object.entries(map)
.map(([k, v]) => `${k}: ${v};`)
.join(' ');
};
export const injectCss = (options: { css?: string; cssVars?: TCssVarsSchema }) => {
const { css, cssVars } = options;
if (css) {
const style = document.createElement('style');
style.innerHTML = css;
style.textContent = css;
document.head.appendChild(style);
}
@@ -1,11 +1,10 @@
import { FieldType } from '@prisma/client';
import { validateCheckboxLength } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import type { TFieldCheckbox } from '@documenso/lib/types/field';
import { parseCheckboxCustomText } from '@documenso/lib/utils/fields';
import type { TSignEnvelopeFieldValue } from '@documenso/trpc/server/envelope-router/sign-envelope-field.types';
import { checkboxValidationSigns } from '@documenso/ui/primitives/document-flow/field-items-advanced-settings/constants';
import { FieldType } from '@prisma/client';
import { SignFieldCheckboxDialog } from '~/components/dialogs/sign-field-checkbox-dialog';
@@ -53,9 +52,7 @@ export const handleCheckboxFieldClick = async (
}
if (validationRule && validationLength) {
const checkboxValidationRule = checkboxValidationSigns.find(
(sign) => sign.label === validationRule,
);
const checkboxValidationRule = checkboxValidationSigns.find((sign) => sign.label === validationRule);
if (!checkboxValidationRule) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
@@ -65,21 +62,14 @@ export const handleCheckboxFieldClick = async (
// Custom logic to make it flow better.
// If "at most" OR "exactly" 1 value then just return the new selected value if exists.
if (
(checkboxValidationRule.value === '=' || checkboxValidationRule.value === '<=') &&
validationLength === 1
) {
if ((checkboxValidationRule.value === '=' || checkboxValidationRule.value === '<=') && validationLength === 1) {
return {
type: FieldType.CHECKBOX,
value: [clickedCheckboxIndex],
};
}
const isValid = validateCheckboxLength(
checkedValues.length,
checkboxValidationRule.value,
validationLength,
);
const isValid = validateCheckboxLength(checkedValues.length, checkboxValidationRule.value, validationLength);
// Only render validation dialog if validation is invalid.
if (!isValid) {
@@ -1,8 +1,7 @@
import { FieldType } from '@prisma/client';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import type { TFieldDropdown } from '@documenso/lib/types/field';
import type { TSignEnvelopeFieldValue } from '@documenso/trpc/server/envelope-router/sign-envelope-field.types';
import { FieldType } from '@prisma/client';
import { SignFieldDropdownDialog } from '~/components/dialogs/sign-field-dropdown-dialog';
@@ -1,8 +1,7 @@
import { FieldType } from '@prisma/client';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import type { TFieldEmail } from '@documenso/lib/types/field';
import type { TSignEnvelopeFieldValue } from '@documenso/trpc/server/envelope-router/sign-envelope-field.types';
import { FieldType } from '@prisma/client';
import { SignFieldEmailDialog } from '~/components/dialogs/sign-field-email-dialog';
@@ -1,8 +1,7 @@
import { FieldType } from '@prisma/client';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import type { TFieldInitials } from '@documenso/lib/types/field';
import type { TSignEnvelopeFieldValue } from '@documenso/trpc/server/envelope-router/sign-envelope-field.types';
import { FieldType } from '@prisma/client';
import { SignFieldInitialsDialog } from '~/components/dialogs/sign-field-initials-dialog';
@@ -1,8 +1,7 @@
import { FieldType } from '@prisma/client';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import type { TFieldName } from '@documenso/lib/types/field';
import type { TSignEnvelopeFieldValue } from '@documenso/trpc/server/envelope-router/sign-envelope-field.types';
import { FieldType } from '@prisma/client';
import { SignFieldNameDialog } from '~/components/dialogs/sign-field-name-dialog';
@@ -1,8 +1,7 @@
import { FieldType } from '@prisma/client';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import type { TFieldNumber } from '@documenso/lib/types/field';
import type { TSignEnvelopeFieldValue } from '@documenso/trpc/server/envelope-router/sign-envelope-field.types';
import { FieldType } from '@prisma/client';
import { SignFieldNumberDialog } from '~/components/dialogs/sign-field-number-dialog';
@@ -1,8 +1,7 @@
import { FieldType } from '@prisma/client';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import type { TFieldSignature } from '@documenso/lib/types/field';
import type { TSignEnvelopeFieldValue } from '@documenso/trpc/server/envelope-router/sign-envelope-field.types';
import { FieldType } from '@prisma/client';
import { SignFieldSignatureDialog } from '~/components/dialogs/sign-field-signature-dialog';
@@ -18,14 +17,7 @@ type HandleSignatureFieldClickOptions = {
export const handleSignatureFieldClick = async (
options: HandleSignatureFieldClickOptions,
): Promise<Extract<TSignEnvelopeFieldValue, { type: typeof FieldType.SIGNATURE }> | null> => {
const {
field,
fullName,
signature,
typedSignatureEnabled,
uploadSignatureEnabled,
drawSignatureEnabled,
} = options;
const { field, fullName, signature, typedSignatureEnabled, uploadSignatureEnabled, drawSignatureEnabled } = options;
if (field.type !== FieldType.SIGNATURE) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
@@ -1,8 +1,7 @@
import { FieldType } from '@prisma/client';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import type { TFieldText } from '@documenso/lib/types/field';
import type { TSignEnvelopeFieldValue } from '@documenso/trpc/server/envelope-router/sign-envelope-field.types';
import { FieldType } from '@prisma/client';
import { SignFieldTextDialog } from '~/components/dialogs/sign-field-text-dialog';
+3 -2
View File
@@ -1,12 +1,13 @@
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { i18n, type MessageDescriptor } from '@lingui/core';
export const appMetaTags = (title?: string) => {
export const appMetaTags = (title?: MessageDescriptor) => {
const 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.';
return [
{
title: title ? `${title} - Documenso` : 'Documenso',
title: title ? `${i18n._(title)} - Documenso` : 'Documenso',
},
{
name: 'description',
+38
View File
@@ -0,0 +1,38 @@
import { useRouteLoaderData } from 'react-router';
/**
* Returns the supplied CSP nonce only when rendering on the server.
*
* Browsers strip the `nonce` attribute from `getAttribute()` after CSP
* processing for security (so reflected XSS can't read the nonce back out
* of the DOM), but React 18's hydration reads via `getAttribute` and warns
* about a mismatch when the JSX prop is non-empty:
*
* Prop `nonce` did not match. Server: "" Client: "abc..."
*
* Returning `undefined` on the client makes React treat the prop as
* "no attribute" — `shouldRemoveAttribute` short-circuits for nullish
* values (see `react-dom/cjs/react-dom.development.js` `shouldRemoveAttribute`),
* and the hydration prop-diff branch is skipped entirely.
*
* The nonce only matters at the moment the script/style is parsed by the
* browser. After that it's an inert attribute, so dropping it on the
* client has no functional impact. Subsequent dynamically-injected
* scripts inherit trust via `'strict-dynamic'`.
*/
export const nonce = (value: string | undefined): string | undefined => (typeof window === 'undefined' ? value : '');
/**
* Reads the per-request CSP nonce surfaced by the root loader. Use this
* inside any non-root route component that needs to render a `<style>`,
* `<script>`, or other element that the CSP gates by nonce.
*
* Centralised here so the cast is in one place — if the root loader's
* `nonce` field is ever renamed/removed, only this function needs updating
* (and TypeScript will catch it at the cast site).
*/
export const useCspNonce = (): string | undefined => {
const rootData = useRouteLoaderData('root') as { nonce?: string } | undefined;
return rootData?.nonce;
};
@@ -14,7 +14,7 @@ type PromiseWithResolvers<T> = {
const GlobalPromise = globalThis.Promise as any;
if (typeof GlobalPromise.withResolvers !== 'function') {
GlobalPromise.withResolvers = function <T>(): PromiseWithResolvers<T> {
GlobalPromise.withResolvers = <T>(): PromiseWithResolvers<T> => {
let resolve!: (value: T | PromiseLike<T>) => void;
let reject!: (reason?: unknown) => void;
+3 -8
View File
@@ -10,12 +10,9 @@
import { useActionData, useLoaderData } from 'react-router';
import * as _superjson from 'superjson';
export type SuperJsonFunction = <Data extends unknown>(
data: Data,
init?: number | ResponseInit,
) => SuperTypedResponse<Data>;
export type SuperJsonFunction = <Data>(data: Data, init?: number | ResponseInit) => SuperTypedResponse<Data>;
export declare type SuperTypedResponse<T extends unknown = unknown> = Response & {
export declare type SuperTypedResponse<T = unknown> = Response & {
superjson(): Promise<T>;
};
@@ -23,9 +20,7 @@ type AppData = any;
type DataFunction = (...args: any[]) => unknown; // matches any function
type DataOrFunction = AppData | DataFunction;
export type UseDataFunctionReturn<T extends DataOrFunction> = T extends (
...args: any[]
) => infer Output
export type UseDataFunctionReturn<T extends DataOrFunction> = T extends (...args: any[]) => infer Output
? Awaited<Output> extends SuperTypedResponse<infer U>
? U
: Awaited<ReturnType<T>>