mirror of
https://github.com/documenso/documenso.git
synced 2026-07-27 10:25:00 +10:00
chore: merge main, resolve biome formatting conflicts
Merge origin/main into feat/document-file-conversion. Conflicts were format-only (Tailwind class ordering, single-line vs multi-line) plus two semantic merges: - files.helpers.ts: combine main's pending-PDF download path with the branch's original-source-file (DOCX/PNG/JPEG) download path - download-pdf.ts: combine main's versionToFilenameSuffix helper with the branch's server-provided Content-Disposition filename support
This commit is contained in:
@@ -3,7 +3,7 @@ import type { EnvelopeItem } from '@prisma/client';
|
||||
import { getEnvelopeItemPdfUrl } from '../utils/envelope-download';
|
||||
import { downloadFile } from './download-file';
|
||||
|
||||
type DocumentVersion = 'original' | 'signed';
|
||||
type DocumentVersion = 'original' | 'signed' | 'pending';
|
||||
|
||||
type DownloadPDFProps = {
|
||||
envelopeItem: Pick<EnvelopeItem, 'id' | 'envelopeId'>;
|
||||
@@ -14,12 +14,17 @@ type DownloadPDFProps = {
|
||||
* Specifies which version of the document to download.
|
||||
* 'signed': Downloads the signed version (default).
|
||||
* 'original': Downloads the original version (may be DOCX, PNG, JPEG if converted).
|
||||
* 'pending': Downloads the original document with currently-inserted fields burned in.
|
||||
* Only valid while the envelope is in PENDING status. Not supported via
|
||||
* recipient token.
|
||||
*/
|
||||
version?: DocumentVersion;
|
||||
};
|
||||
|
||||
const getFilenameFromContentDisposition = (header: string | null): string | null => {
|
||||
if (!header) return null;
|
||||
if (!header) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filenameStarMatch = header.match(/filename\*=(?:UTF-8''|utf-8'')([^;]+)/i);
|
||||
if (filenameStarMatch) {
|
||||
@@ -39,12 +44,18 @@ const getFilenameFromContentDisposition = (header: string | null): string | null
|
||||
return null;
|
||||
};
|
||||
|
||||
export const downloadPDF = async ({
|
||||
envelopeItem,
|
||||
token,
|
||||
fileName,
|
||||
version = 'signed',
|
||||
}: DownloadPDFProps) => {
|
||||
const versionToFilenameSuffix = (version: DocumentVersion): string => {
|
||||
switch (version) {
|
||||
case 'signed':
|
||||
return '_signed.pdf';
|
||||
case 'pending':
|
||||
return '_pending.pdf';
|
||||
case 'original':
|
||||
return '.pdf';
|
||||
}
|
||||
};
|
||||
|
||||
export const downloadPDF = async ({ envelopeItem, token, fileName, version = 'signed' }: DownloadPDFProps) => {
|
||||
const downloadUrl = getEnvelopeItemPdfUrl({
|
||||
type: 'download',
|
||||
envelopeItem: envelopeItem,
|
||||
@@ -63,8 +74,7 @@ export const downloadPDF = async ({
|
||||
filename = serverFilename;
|
||||
} else {
|
||||
const baseTitle = (fileName ?? 'document').replace(/\.[^/.]+$/, '');
|
||||
const suffix = version === 'signed' ? '_signed.pdf' : '.pdf';
|
||||
filename = `${baseTitle}${suffix}`;
|
||||
filename = `${baseTitle}${versionToFilenameSuffix(version)}`;
|
||||
}
|
||||
|
||||
downloadFile({
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { posthog } from 'posthog-js';
|
||||
|
||||
import { extractPostHogConfig } from '@documenso/lib/constants/feature-flags';
|
||||
|
||||
let posthogPromise: Promise<typeof import('posthog-js')> | null = null;
|
||||
|
||||
const getPosthog = async () => {
|
||||
if (!posthogPromise) {
|
||||
posthogPromise = import('posthog-js');
|
||||
}
|
||||
|
||||
return posthogPromise;
|
||||
};
|
||||
|
||||
export function useAnalytics() {
|
||||
// const featureFlags = useFeatureFlags();
|
||||
const isPostHogEnabled = extractPostHogConfig();
|
||||
@@ -17,7 +25,9 @@ export function useAnalytics() {
|
||||
return;
|
||||
}
|
||||
|
||||
posthog.capture(event, properties);
|
||||
void getPosthog().then(({ default: posthog }) => {
|
||||
posthog.capture(event, properties);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -31,7 +41,9 @@ export function useAnalytics() {
|
||||
return;
|
||||
}
|
||||
|
||||
posthog.captureException(error, properties);
|
||||
void getPosthog().then(({ default: posthog }) => {
|
||||
posthog.captureException(error, properties);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,10 +5,7 @@ type SaveRequest<T, R> = {
|
||||
onResponse?: (response: R) => void;
|
||||
};
|
||||
|
||||
export const useAutoSave = <T, R = void>(
|
||||
onSave: (data: T) => Promise<R>,
|
||||
options: { delay?: number } = {},
|
||||
) => {
|
||||
export const useAutoSave = <T, R = void>(onSave: (data: T) => Promise<R>, options: { delay?: number } = {}) => {
|
||||
const { delay = 2000 } = options;
|
||||
|
||||
const saveTimeoutRef = useRef<NodeJS.Timeout>();
|
||||
|
||||
@@ -11,8 +11,7 @@ export type UseCopyShareLinkOptions = {
|
||||
export function useCopyShareLink({ onSuccess, onError }: UseCopyShareLinkOptions) {
|
||||
const [, copyToClipboard] = useCopyToClipboard();
|
||||
|
||||
const { mutateAsync: createOrGetShareLink, isPending: isCreatingShareLink } =
|
||||
trpc.document.share.useMutation();
|
||||
const { mutateAsync: createOrGetShareLink, isPending: isCreatingShareLink } = trpc.document.share.useMutation();
|
||||
|
||||
/**
|
||||
* Copy a newly created, or pre-existing share link to the user's clipboard.
|
||||
|
||||
@@ -18,9 +18,7 @@ export function useCopyToClipboard(): [CopiedValue, CopyFn] {
|
||||
|
||||
// Try to save to clipboard then save it in the state if worked
|
||||
try {
|
||||
isClipboardApiSupported
|
||||
? await handleClipboardApiCopy(text, blobType)
|
||||
: await handleWriteTextCopy(text);
|
||||
isClipboardApiSupported ? await handleClipboardApiCopy(text, blobType) : await handleWriteTextCopy(text);
|
||||
|
||||
setCopiedText(await text);
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const useDocumentElement = () => {
|
||||
/**
|
||||
@@ -15,9 +14,7 @@ export const useDocumentElement = () => {
|
||||
|
||||
const $page =
|
||||
target.closest<HTMLElement>(pageSelector) ??
|
||||
document
|
||||
.elementsFromPoint(event.clientX, event.clientY)
|
||||
.find((el) => el.matches(pageSelector));
|
||||
document.elementsFromPoint(event.clientX, event.clientY).find((el) => el.matches(pageSelector));
|
||||
|
||||
if (!$page) {
|
||||
return null;
|
||||
@@ -31,19 +28,9 @@ export const useDocumentElement = () => {
|
||||
* as a percentage of the page width and height.
|
||||
*/
|
||||
const getFieldPosition = (page: HTMLElement, field: HTMLElement) => {
|
||||
const {
|
||||
top: pageTop,
|
||||
left: pageLeft,
|
||||
height: pageHeight,
|
||||
width: pageWidth,
|
||||
} = getBoundingClientRect(page);
|
||||
const { top: pageTop, left: pageLeft, height: pageHeight, width: pageWidth } = getBoundingClientRect(page);
|
||||
|
||||
const {
|
||||
top: fieldTop,
|
||||
left: fieldLeft,
|
||||
height: fieldHeight,
|
||||
width: fieldWidth,
|
||||
} = getBoundingClientRect(field);
|
||||
const { top: fieldTop, left: fieldLeft, height: fieldHeight, width: fieldWidth } = getBoundingClientRect(field);
|
||||
|
||||
return {
|
||||
x: ((fieldLeft - pageLeft) / pageWidth) * 100,
|
||||
@@ -60,31 +47,28 @@ export const useDocumentElement = () => {
|
||||
* @param mouseWidth The artifical width of the mouse.
|
||||
* @param mouseHeight The artifical height of the mouse.
|
||||
*/
|
||||
const isWithinPageBounds = useCallback(
|
||||
(event: MouseEvent, pageSelector: string, mouseWidth = 0, mouseHeight = 0) => {
|
||||
const $page = getPage(event, pageSelector);
|
||||
const isWithinPageBounds = useCallback((event: MouseEvent, pageSelector: string, mouseWidth = 0, mouseHeight = 0) => {
|
||||
const $page = getPage(event, pageSelector);
|
||||
|
||||
if (!$page) {
|
||||
return false;
|
||||
}
|
||||
if (!$page) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { top, left, height, width } = $page.getBoundingClientRect();
|
||||
const { top, left, height, width } = $page.getBoundingClientRect();
|
||||
|
||||
const halfMouseWidth = mouseWidth / 2;
|
||||
const halfMouseHeight = mouseHeight / 2;
|
||||
const halfMouseWidth = mouseWidth / 2;
|
||||
const halfMouseHeight = mouseHeight / 2;
|
||||
|
||||
if (event.clientY > top + height - halfMouseHeight || event.clientY < top + halfMouseHeight) {
|
||||
return false;
|
||||
}
|
||||
if (event.clientY > top + height - halfMouseHeight || event.clientY < top + halfMouseHeight) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.clientX > left + width - halfMouseWidth || event.clientX < left + halfMouseWidth) {
|
||||
return false;
|
||||
}
|
||||
if (event.clientX > left + width - halfMouseWidth || event.clientX < left + halfMouseWidth) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
[],
|
||||
);
|
||||
return true;
|
||||
}, []);
|
||||
|
||||
return {
|
||||
getPage,
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import type { Field } from '@prisma/client';
|
||||
import { FieldType } from '@prisma/client';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { getPdfPagesCount } from '@documenso/lib/constants/pdf-viewer';
|
||||
import type { TEditorEnvelope } from '@documenso/lib/types/envelope-editor';
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import type { Field } from '@prisma/client';
|
||||
import { FieldType } from '@prisma/client';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZLocalFieldSchema = z.object({
|
||||
// This is the actual ID of the field if created.
|
||||
@@ -67,10 +65,7 @@ type UseEditorFieldsResponse = {
|
||||
resetForm: (fields?: Field[]) => void;
|
||||
};
|
||||
|
||||
export const useEditorFields = ({
|
||||
envelope,
|
||||
handleFieldsUpdate,
|
||||
}: EditorFieldsProps): UseEditorFieldsResponse => {
|
||||
export const useEditorFields = ({ envelope, handleFieldsUpdate }: EditorFieldsProps): UseEditorFieldsResponse => {
|
||||
const [selectedFieldFormId, setSelectedFieldFormId] = useState<string | null>(null);
|
||||
const [selectedRecipientId, setSelectedRecipientId] = useState<number | null>(null);
|
||||
|
||||
@@ -123,9 +118,7 @@ export const useEditorFields = ({
|
||||
}
|
||||
|
||||
const foundField = localFields.find((field) => field.formId === formId);
|
||||
const recipient = envelope.recipients.find(
|
||||
(recipient) => recipient.id === foundField?.recipientId,
|
||||
);
|
||||
const recipient = envelope.recipients.find((recipient) => recipient.id === foundField?.recipientId);
|
||||
|
||||
if (recipient) {
|
||||
setSelectedRecipient(recipient.id);
|
||||
@@ -319,9 +312,7 @@ export const useEditorFields = ({
|
||||
};
|
||||
};
|
||||
|
||||
const restrictFieldPosValues = (
|
||||
field: Pick<TLocalField, 'positionX' | 'positionY' | 'width' | 'height'>,
|
||||
) => {
|
||||
const restrictFieldPosValues = (field: Pick<TLocalField, 'positionX' | 'positionY' | 'width' | 'height'>) => {
|
||||
return {
|
||||
positionX: Math.max(0, Math.min(100, field.positionX)),
|
||||
positionY: Math.max(0, Math.min(100, field.positionY)),
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
import { useId } from 'react';
|
||||
|
||||
import { ZRecipientActionAuthTypesSchema, ZRecipientAuthOptionsSchema } from '@documenso/lib/types/document-auth';
|
||||
import type { TEditorEnvelope } from '@documenso/lib/types/envelope-editor';
|
||||
import { ZRecipientEmailSchema } from '@documenso/lib/types/recipient';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { DocumentSigningOrder, RecipientRole } from '@prisma/client';
|
||||
import { useId } from 'react';
|
||||
import type { UseFormReturn } from 'react-hook-form';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { prop, sortBy } from 'remeda';
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
ZRecipientActionAuthTypesSchema,
|
||||
ZRecipientAuthOptionsSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import type { TEditorEnvelope } from '@documenso/lib/types/envelope-editor';
|
||||
import { ZRecipientEmailSchema } from '@documenso/lib/types/recipient';
|
||||
|
||||
const LocalRecipientSchema = z.object({
|
||||
formId: z.string().min(1),
|
||||
id: z.number().optional(),
|
||||
@@ -48,9 +43,7 @@ type UseEditorRecipientsResponse = {
|
||||
resetForm: (options?: ResetFormOptions) => void;
|
||||
};
|
||||
|
||||
export const useEditorRecipients = ({
|
||||
envelope,
|
||||
}: EditorRecipientsProps): UseEditorRecipientsResponse => {
|
||||
export const useEditorRecipients = ({ envelope }: EditorRecipientsProps): UseEditorRecipientsResponse => {
|
||||
const initialId = useId();
|
||||
|
||||
const generateDefaultValues = (options?: ResetFormOptions) => {
|
||||
@@ -83,8 +76,7 @@ export const useEditorRecipients = ({
|
||||
return {
|
||||
signers,
|
||||
signingOrder: documentMeta?.signingOrder ?? envelope.documentMeta.signingOrder,
|
||||
allowDictateNextSigner:
|
||||
documentMeta?.allowDictateNextSigner ?? envelope.documentMeta.allowDictateNextSigner,
|
||||
allowDictateNextSigner: documentMeta?.allowDictateNextSigner ?? envelope.documentMeta.allowDictateNextSigner,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
export const useElementBounds = (elementOrSelector: HTMLElement | string, withScroll = false) => {
|
||||
const [bounds, setBounds] = useState({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/consistent-type-assertions */
|
||||
import { RefObject, useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
/**
|
||||
* Calculate the width and height of a text element.
|
||||
|
||||
@@ -15,7 +15,9 @@ export function useEnvelopeAutosave<T>(saveFn: (data: T) => Promise<void>, delay
|
||||
// A debounce or promise means something is pending
|
||||
setIsPending(true);
|
||||
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
timeoutRef.current = setTimeout(async () => {
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||
import { PDF_VIEWER_CONTENT_SELECTOR, PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import type { Field } from '@prisma/client';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import type { Field } from '@prisma/client';
|
||||
|
||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||
import {
|
||||
PDF_VIEWER_CONTENT_SELECTOR,
|
||||
PDF_VIEWER_PAGE_SELECTOR,
|
||||
} from '@documenso/lib/constants/pdf-viewer';
|
||||
|
||||
export const useFieldPageCoords = (
|
||||
field: Pick<Field, 'positionX' | 'positionY' | 'width' | 'height' | 'page'>,
|
||||
) => {
|
||||
export const useFieldPageCoords = (field: Pick<Field, 'positionX' | 'positionY' | 'width' | 'height' | 'page'>) => {
|
||||
const [coords, setCoords] = useState({
|
||||
x: 0,
|
||||
y: 0,
|
||||
@@ -19,9 +12,7 @@ export const useFieldPageCoords = (
|
||||
});
|
||||
|
||||
const calculateCoords = useCallback(() => {
|
||||
const $page = document.querySelector<HTMLElement>(
|
||||
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`,
|
||||
);
|
||||
const $page = document.querySelector<HTMLElement>(`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`);
|
||||
|
||||
if (!$page) {
|
||||
return;
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { PDF_VIEWER_CONTENT_SELECTOR, PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
PDF_VIEWER_CONTENT_SELECTOR,
|
||||
PDF_VIEWER_PAGE_SELECTOR,
|
||||
} from '@documenso/lib/constants/pdf-viewer';
|
||||
|
||||
/**
|
||||
* Returns whether the PDF page element for the given page number is currently
|
||||
* present in the DOM. With virtual list rendering only pages near the viewport
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import Konva from 'konva';
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
|
||||
import Konva from 'konva';
|
||||
|
||||
import { type PageRenderData } from '../providers/envelope-render-provider';
|
||||
import type { PageRenderData } from '../providers/envelope-render-provider';
|
||||
|
||||
type RenderFunction = (props: { stage: Konva.Stage; pageLayer: Konva.Layer }) => void;
|
||||
|
||||
@@ -66,7 +65,7 @@ export const usePageRenderer = (renderFunction: RenderFunction, pageData: PageRe
|
||||
pageLayer: pageLayer.current,
|
||||
});
|
||||
|
||||
void document.fonts.ready.then(function () {
|
||||
void document.fonts.ready.then(() => {
|
||||
pageLayer.current?.batchDraw();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
function dispatchStorageEvent(key: string, newValue: string | null) {
|
||||
window.dispatchEvent(new StorageEvent('storage', { key, newValue }));
|
||||
@@ -25,20 +25,13 @@ const useSessionStorageSubscribe = (callback: (event: StorageEvent) => void) =>
|
||||
return () => window.removeEventListener('storage', callback);
|
||||
};
|
||||
|
||||
export function useSessionStorage<T>(
|
||||
key: string,
|
||||
initialValue: T,
|
||||
): [T, Dispatch<SetStateAction<T>>] {
|
||||
export function useSessionStorage<T>(key: string, initialValue: T): [T, Dispatch<SetStateAction<T>>] {
|
||||
const serializedInitialValue = JSON.stringify(initialValue);
|
||||
|
||||
const getSnapshot = () => getSessionStorageItem(key);
|
||||
const getServerSnapshot = () => serializedInitialValue;
|
||||
|
||||
const store = React.useSyncExternalStore(
|
||||
useSessionStorageSubscribe,
|
||||
getSnapshot,
|
||||
getServerSnapshot,
|
||||
);
|
||||
const store = React.useSyncExternalStore(useSessionStorageSubscribe, getSnapshot, getServerSnapshot);
|
||||
|
||||
const setState: Dispatch<SetStateAction<T>> = React.useCallback(
|
||||
(v) => {
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
import React, { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { EnvelopeType, Prisma, ReadStatus, SendStatus, SigningStatus } from '@prisma/client';
|
||||
import { useSearchParams } from 'react-router';
|
||||
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
import {
|
||||
DEFAULT_EDITOR_CONFIG,
|
||||
@@ -17,11 +11,16 @@ import type { TUpdateEnvelopeRequest } from '@documenso/trpc/server/envelope-rou
|
||||
import type { TRecipientColor } from '@documenso/ui/lib/recipient-colors';
|
||||
import { getRecipientColor } from '@documenso/ui/lib/recipient-colors';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { EnvelopeType, Prisma, ReadStatus, SendStatus, SigningStatus } from '@prisma/client';
|
||||
import type React from 'react';
|
||||
import { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router';
|
||||
|
||||
import type { TDocumentEmailSettings } from '../../types/document-email';
|
||||
import { formatDocumentsPath, formatTemplatesPath } from '../../utils/teams';
|
||||
import { useEditorFields } from '../hooks/use-editor-fields';
|
||||
import type { TLocalField } from '../hooks/use-editor-fields';
|
||||
import { useEditorFields } from '../hooks/use-editor-fields';
|
||||
import { useEditorRecipients } from '../hooks/use-editor-recipients';
|
||||
import { useEnvelopeAutosave } from '../hooks/use-envelope-autosave';
|
||||
|
||||
@@ -176,16 +175,12 @@ export const EnvelopeEditorProvider = ({
|
||||
setEnvelope((prev) => ({
|
||||
...prev,
|
||||
recipients,
|
||||
fields: prev.fields.filter((field) =>
|
||||
recipients.some((recipient) => recipient.id === field.recipientId),
|
||||
),
|
||||
fields: prev.fields.filter((field) => recipients.some((recipient) => recipient.id === field.recipientId)),
|
||||
}));
|
||||
|
||||
// Reset the local fields to ensure deleted recipient fields are removed.
|
||||
editorFields.resetForm(
|
||||
envelope.fields.filter((field) =>
|
||||
recipients.some((recipient) => recipient.id === field.recipientId),
|
||||
),
|
||||
envelope.fields.filter((field) => recipients.some((recipient) => recipient.id === field.recipientId)),
|
||||
);
|
||||
|
||||
setAutosaveError(false);
|
||||
@@ -203,9 +198,7 @@ export const EnvelopeEditorProvider = ({
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
const setRecipientsAsync = async (
|
||||
localRecipients: TSetEnvelopeRecipientsRequest['recipients'],
|
||||
) => {
|
||||
const setRecipientsAsync = async (localRecipients: TSetEnvelopeRecipientsRequest['recipients']) => {
|
||||
setRecipientsDebounced(localRecipients);
|
||||
await flushSetRecipients();
|
||||
};
|
||||
@@ -346,8 +339,7 @@ export const EnvelopeEditorProvider = ({
|
||||
};
|
||||
|
||||
const getRecipientColorKey = useCallback(
|
||||
(recipientId: number) =>
|
||||
getRecipientColor(envelope.recipients.findIndex((r) => r.id === recipientId)),
|
||||
(recipientId: number) => getRecipientColor(envelope.recipients.findIndex((r) => r.id === recipientId)),
|
||||
[envelope.recipients],
|
||||
);
|
||||
|
||||
@@ -536,10 +528,7 @@ const mapLocalRecipientsToRecipients = ({
|
||||
documentDeletedAt: foundRecipient?.documentDeletedAt || null,
|
||||
expired: foundRecipient?.expired || null,
|
||||
signedAt: foundRecipient?.signedAt || null,
|
||||
authOptions:
|
||||
recipient.actionAuth.length > 0
|
||||
? { actionAuth: recipient.actionAuth, accessAuth: [] }
|
||||
: null,
|
||||
authOptions: recipient.actionAuth.length > 0 ? { actionAuth: recipient.actionAuth, accessAuth: [] } : null,
|
||||
signingOrder: recipient.signingOrder ?? null,
|
||||
rejectionReason: foundRecipient?.rejectionReason || null,
|
||||
role: recipient.role,
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { type Field, type Recipient } from '@prisma/client';
|
||||
|
||||
import type { DocumentDataVersion } from '@documenso/lib/types/document';
|
||||
import { getDocumentDataUrl } from '@documenso/lib/utils/envelope-download';
|
||||
import type { TRecipientColor } from '@documenso/ui/lib/recipient-colors';
|
||||
import { getRecipientColor } from '@documenso/ui/lib/recipient-colors';
|
||||
import type { Field, Recipient } from '@prisma/client';
|
||||
import type React from 'react';
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import type { TEnvelope } from '../../types/envelope';
|
||||
import type { FieldRenderMode } from '../../universal/field-renderer/render-field';
|
||||
|
||||
/**
|
||||
* The signature data for an inserted signature field.
|
||||
*
|
||||
* Loaded separately from the envelope to avoid bloating the envelope.get response
|
||||
* with potentially large base64 image payloads.
|
||||
*/
|
||||
export type EnvelopeRenderFieldSignature = {
|
||||
fieldId: number;
|
||||
signatureImageAsBase64: string | null;
|
||||
typedSignature: string | null;
|
||||
};
|
||||
|
||||
export type PageRenderData = {
|
||||
scale: number;
|
||||
pageIndex: number;
|
||||
@@ -50,6 +60,7 @@ type EnvelopeRenderProviderValue = {
|
||||
currentEnvelopeItem: EnvelopeRenderItem | null;
|
||||
setCurrentEnvelopeItem: (envelopeItemId: string) => void;
|
||||
fields: Field[];
|
||||
signatures: EnvelopeRenderFieldSignature[];
|
||||
recipients: Pick<Recipient, 'id' | 'name' | 'email' | 'signingStatus'>[];
|
||||
getRecipientColorKey: (recipientId: number) => TRecipientColor;
|
||||
|
||||
@@ -89,6 +100,15 @@ interface EnvelopeRenderProviderProps {
|
||||
*/
|
||||
fields?: Field[];
|
||||
|
||||
/**
|
||||
* Optional inserted signature data for signature fields.
|
||||
*
|
||||
* Fetched separately from the envelope to keep the envelope response lean.
|
||||
* If a signature field has no entry here, the renderer will fall back to
|
||||
* showing the field type placeholder.
|
||||
*/
|
||||
signatures?: EnvelopeRenderFieldSignature[];
|
||||
|
||||
/**
|
||||
* Optional recipient used to determine the color of the fields and hover
|
||||
* previews.
|
||||
@@ -137,6 +157,7 @@ export const EnvelopeRenderProvider = ({
|
||||
envelope,
|
||||
envelopeItems: envelopeItemsFromProps,
|
||||
fields,
|
||||
signatures,
|
||||
token,
|
||||
presignToken,
|
||||
recipients = [],
|
||||
@@ -192,10 +213,7 @@ export const EnvelopeRenderProvider = ({
|
||||
}
|
||||
}, [currentItem, envelopeItems]);
|
||||
|
||||
const recipientIds = useMemo(
|
||||
() => recipients.map((recipient) => recipient.id).sort(),
|
||||
[recipients],
|
||||
);
|
||||
const recipientIds = useMemo(() => recipients.map((recipient) => recipient.id).sort(), [recipients]);
|
||||
|
||||
const getRecipientColorKey = useCallback(
|
||||
(recipientId: number) => getRecipientColor(recipientIds.findIndex((id) => id === recipientId)),
|
||||
@@ -212,6 +230,7 @@ export const EnvelopeRenderProvider = ({
|
||||
currentEnvelopeItem: currentItem,
|
||||
setCurrentEnvelopeItem,
|
||||
fields: fields ?? [],
|
||||
signatures: signatures ?? [],
|
||||
recipients,
|
||||
getRecipientColorKey,
|
||||
renderError,
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import type { I18n, Messages } from '@lingui/core';
|
||||
import { setupI18n } from '@lingui/core';
|
||||
|
||||
import {
|
||||
APP_I18N_OPTIONS,
|
||||
SUPPORTED_LANGUAGE_CODES,
|
||||
isValidLanguageCode,
|
||||
} from '../../constants/i18n';
|
||||
import { APP_I18N_OPTIONS, isValidLanguageCode, SUPPORTED_LANGUAGE_CODES } from '../../constants/i18n';
|
||||
import { env } from '../../utils/env';
|
||||
import { remember } from '../../utils/remember';
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { type Messages, setupI18n } from '@lingui/core';
|
||||
import { I18nProvider } from '@lingui/react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import type { I18nLocaleData } from '../../constants/i18n';
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import type { OrganisationSession } from '@documenso/trpc/server/organisation-router/get-organisation-session.types';
|
||||
import type React from 'react';
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
type OrganisationProviderValue = OrganisationSession;
|
||||
|
||||
@@ -27,7 +26,5 @@ export const useOptionalCurrentOrganisation = () => {
|
||||
};
|
||||
|
||||
export const OrganisationProvider = ({ children, organisation }: OrganisationProviderProps) => {
|
||||
return (
|
||||
<OrganisationContext.Provider value={organisation}>{children}</OrganisationContext.Provider>
|
||||
);
|
||||
return <OrganisationContext.Provider value={organisation}>{children}</OrganisationContext.Provider>;
|
||||
};
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import type { Session } from '@prisma/client';
|
||||
import { useLocation } from 'react-router';
|
||||
|
||||
import { authClient } from '@documenso/auth/client';
|
||||
import type { SessionUser } from '@documenso/auth/server/lib/session/session';
|
||||
import { trpc } from '@documenso/trpc/client';
|
||||
import type { TGetOrganisationSessionResponse } from '@documenso/trpc/server/organisation-router/get-organisation-session.types';
|
||||
import type { Session } from '@prisma/client';
|
||||
import type React from 'react';
|
||||
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { useLocation } from 'react-router';
|
||||
|
||||
import { SKIP_QUERY_BATCH_META } from '../../constants/trpc';
|
||||
|
||||
@@ -74,8 +72,7 @@ export const SessionProvider = ({ children, initialSession }: SessionProviderPro
|
||||
.catch((e) => {
|
||||
const errorMessage = typeof e.message === 'string' ? e.message.toLowerCase() : '';
|
||||
|
||||
const isNetworkError =
|
||||
errorMessage.includes('networkerror') || errorMessage.includes('failed to fetch');
|
||||
const isNetworkError = errorMessage.includes('networkerror') || errorMessage.includes('failed to fetch');
|
||||
|
||||
// If the error is a transient network/abort error (e.g. page refresh while
|
||||
// fetch was in-flight), return null to signal we should skip the state update.
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import type { Recipient } from '@prisma/client';
|
||||
import {
|
||||
DocumentDistributionMethod,
|
||||
ReadStatus,
|
||||
RecipientRole,
|
||||
SendStatus,
|
||||
SigningStatus,
|
||||
} from '@prisma/client';
|
||||
import { DocumentDistributionMethod, ReadStatus, RecipientRole, SendStatus, SigningStatus } from '@prisma/client';
|
||||
|
||||
type RecipientForType = Pick<Recipient, 'role' | 'signingStatus' | 'readStatus' | 'sendStatus'>;
|
||||
|
||||
@@ -29,10 +23,7 @@ export const getRecipientType = (
|
||||
return RecipientStatusType.REJECTED;
|
||||
}
|
||||
|
||||
if (
|
||||
recipient.readStatus === ReadStatus.OPENED &&
|
||||
recipient.signingStatus === SigningStatus.NOT_SIGNED
|
||||
) {
|
||||
if (recipient.readStatus === ReadStatus.OPENED && recipient.signingStatus === SigningStatus.NOT_SIGNED) {
|
||||
return RecipientStatusType.OPENED;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user