mirror of
https://github.com/documenso/documenso.git
synced 2026-07-25 09:25:08 +10:00
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:
@@ -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';
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>>
|
||||
|
||||
Reference in New Issue
Block a user