mirror of
https://github.com/hanzoai/sign.git
synced 2026-10-21 01:21:51 +00:00
The form learns server ids only on a step change, so each autosave after the first sent the recipients it had just created without an id: the server deleted them and created them again, with new ids and signing tokens. The provider now keeps the id it got back for each row's formId, sent as clientId and echoed by envelope.recipient.set.
654 lines
20 KiB
TypeScript
654 lines
20 KiB
TypeScript
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 '@hanzo/esign-lib/constants/trpc';
|
|
import {
|
|
DEFAULT_EDITOR_CONFIG,
|
|
type EnvelopeEditorConfig,
|
|
type TEditorEnvelope,
|
|
} from '@hanzo/esign-lib/types/envelope-editor';
|
|
import type { TGetEditorEnvelopeResponse } from '@hanzo/esign-trpc/server/envelope-router/get-editor-envelope.types';
|
|
import type {
|
|
TSetEnvelopeFieldsRequest,
|
|
TSetEnvelopeFieldsResponse,
|
|
} from '@hanzo/esign-trpc/server/envelope-router/set-envelope-fields.types';
|
|
import type {
|
|
TSetEnvelopeRecipientsRequest,
|
|
TSetEnvelopeRecipientsResponse,
|
|
} from '@hanzo/esign-trpc/server/envelope-router/set-envelope-recipients.types';
|
|
import type {
|
|
TUpdateEnvelopeRequest,
|
|
TUpdateEnvelopeResponse,
|
|
} from '@hanzo/esign-trpc/server/envelope-router/update-envelope.types';
|
|
import { useZapMutation, useZapQuery } from '@hanzo/esign-trpc/zap/react';
|
|
import type { TRecipientColor } from '@hanzo/esign-ui/lib/recipient-colors';
|
|
import { AVAILABLE_RECIPIENT_COLORS } from '@hanzo/esign-ui/lib/recipient-colors';
|
|
import { useToast } from '@hanzo/esign-ui/primitives/use-toast';
|
|
|
|
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 { useEditorRecipients } from '../hooks/use-editor-recipients';
|
|
import { useEnvelopeAutosave } from '../hooks/use-envelope-autosave';
|
|
|
|
export type EnvelopeEditorStep = 'upload' | 'addFields' | 'preview';
|
|
|
|
type UpdateEnvelopePayload = Pick<TUpdateEnvelopeRequest, 'data' | 'meta'>;
|
|
|
|
/** A signer row as the recipients form holds it: `formId` is its stable key. */
|
|
type SetRecipientsPayload = (TSetEnvelopeRecipientsRequest['recipients'][number] & {
|
|
formId?: string;
|
|
})[];
|
|
|
|
type EnvelopeEditorProviderValue = {
|
|
editorConfig: EnvelopeEditorConfig;
|
|
|
|
envelope: TEditorEnvelope;
|
|
|
|
isEmbedded: boolean;
|
|
isDocument: boolean;
|
|
isTemplate: boolean;
|
|
|
|
setLocalEnvelope: (localEnvelope: Partial<TEditorEnvelope>) => void;
|
|
updateEnvelope: (envelopeUpdates: UpdateEnvelopePayload) => void;
|
|
updateEnvelopeAsync: (envelopeUpdates: UpdateEnvelopePayload) => Promise<void>;
|
|
setRecipientsDebounced: (recipients: SetRecipientsPayload) => void;
|
|
setRecipientsAsync: (recipients: SetRecipientsPayload) => Promise<void>;
|
|
|
|
getRecipientColorKey: (recipientId: number) => TRecipientColor;
|
|
|
|
editorFields: ReturnType<typeof useEditorFields>;
|
|
editorRecipients: ReturnType<typeof useEditorRecipients>;
|
|
|
|
isAutosaving: boolean;
|
|
flushAutosave: () => Promise<TEditorEnvelope>;
|
|
autosaveError: boolean;
|
|
resetForms: () => void;
|
|
|
|
relativePath: {
|
|
basePath: string;
|
|
envelopePath: string;
|
|
editorPath: string;
|
|
documentRootPath: string;
|
|
templateRootPath: string;
|
|
};
|
|
|
|
navigateToStep: (step: EnvelopeEditorStep) => void;
|
|
syncEnvelope: () => Promise<void>;
|
|
|
|
registerExternalFlush: (key: string, flush: () => Promise<void>) => () => void;
|
|
registerPendingMutation: (promise: Promise<unknown>) => void;
|
|
|
|
organisationEmails?: { id: string; email: string }[];
|
|
};
|
|
|
|
interface EnvelopeEditorProviderProps {
|
|
children: React.ReactNode;
|
|
editorConfig?: EnvelopeEditorConfig;
|
|
initialEnvelope: TEditorEnvelope;
|
|
organisationEmails?: { id: string; email: string }[];
|
|
}
|
|
|
|
const EnvelopeEditorContext = createContext<EnvelopeEditorProviderValue | null>(null);
|
|
|
|
export const useCurrentEnvelopeEditor = () => {
|
|
const context = useContext(EnvelopeEditorContext);
|
|
|
|
if (!context) {
|
|
throw new Error('useCurrentEnvelopeEditor must be used within a EnvelopeEditorProvider');
|
|
}
|
|
|
|
return context;
|
|
};
|
|
|
|
export const EnvelopeEditorProvider = ({
|
|
children,
|
|
editorConfig = DEFAULT_EDITOR_CONFIG,
|
|
initialEnvelope,
|
|
organisationEmails,
|
|
}: EnvelopeEditorProviderProps) => {
|
|
const { t } = useLingui();
|
|
const { toast } = useToast();
|
|
|
|
const [_searchParams, setSearchParams] = useSearchParams();
|
|
|
|
const [envelope, _setEnvelope] = useState(initialEnvelope);
|
|
const [autosaveError, setAutosaveError] = useState<boolean>(false);
|
|
|
|
const envelopeRef = useRef(initialEnvelope);
|
|
|
|
const externalFlushCallbacksRef = useRef<Map<string, () => Promise<void>>>(new Map());
|
|
const pendingMutationsRef = useRef<Set<Promise<unknown>>>(new Set());
|
|
|
|
const registerExternalFlush = useCallback((key: string, flush: () => Promise<void>) => {
|
|
externalFlushCallbacksRef.current.set(key, flush);
|
|
|
|
return () => {
|
|
externalFlushCallbacksRef.current.delete(key);
|
|
};
|
|
}, []);
|
|
|
|
const registerPendingMutation = useCallback((promise: Promise<unknown>) => {
|
|
pendingMutationsRef.current.add(promise);
|
|
|
|
void promise.finally(() => {
|
|
pendingMutationsRef.current.delete(promise);
|
|
});
|
|
}, []);
|
|
|
|
const setEnvelope: typeof _setEnvelope = (action) => {
|
|
_setEnvelope((prev) => {
|
|
const next = typeof action === 'function' ? action(prev) : action;
|
|
envelopeRef.current = next;
|
|
return next;
|
|
});
|
|
};
|
|
|
|
const isEmbedded = editorConfig.embedded !== undefined;
|
|
|
|
const editorFields = useEditorFields({
|
|
envelope,
|
|
handleFieldsUpdate: (fields) => setFieldsDebounced(fields),
|
|
});
|
|
|
|
const editorRecipients = useEditorRecipients({
|
|
envelope,
|
|
});
|
|
|
|
/**
|
|
* Server ids of the signers created while the form is open, by formId. The
|
|
* form learns ids only when it is reset (a step change), so without this a
|
|
* signer created by one autosave is sent id-less by the next, deleted and
|
|
* created again: its id, signing token and fields are lost on every keystroke
|
|
* that follows, which is what adding a second recipient did.
|
|
*/
|
|
const recipientIdByFormIdRef = useRef<Map<string, number>>(new Map());
|
|
|
|
const withKnownRecipientIds = (localRecipients: SetRecipientsPayload) =>
|
|
localRecipients.map(({ formId, ...recipient }) => ({
|
|
...recipient,
|
|
id: recipient.id ?? (formId ? recipientIdByFormIdRef.current.get(formId) : undefined),
|
|
clientId: formId,
|
|
}));
|
|
|
|
const setRecipientsMutation = useZapMutation<
|
|
TSetEnvelopeRecipientsResponse,
|
|
TSetEnvelopeRecipientsRequest
|
|
>('envelope.recipient.set');
|
|
const setFieldsMutation = useZapMutation<TSetEnvelopeFieldsResponse, TSetEnvelopeFieldsRequest>(
|
|
'envelope.field.set',
|
|
);
|
|
const updateEnvelopeMutation = useZapMutation<TUpdateEnvelopeResponse, TUpdateEnvelopeRequest>(
|
|
'envelope.update',
|
|
);
|
|
|
|
/**
|
|
* Handles debouncing the recipients updates to the server.
|
|
*
|
|
* Will set the local envelope recipients and fields after the update is complete.
|
|
*/
|
|
const {
|
|
triggerSave: setRecipientsDebounced,
|
|
flush: flushSetRecipients,
|
|
isPending: isRecipientsMutationPending,
|
|
} = useEnvelopeAutosave(async (localRecipients: SetRecipientsPayload) => {
|
|
try {
|
|
let recipients: TEditorEnvelope['recipients'] = [];
|
|
|
|
// The autosave runs after the render that scheduled it, so it reads the
|
|
// envelope as it stands now rather than as that render saw it.
|
|
const current = envelopeRef.current;
|
|
|
|
if (!isEmbedded) {
|
|
const response = await setRecipientsMutation.mutateAsync({
|
|
envelopeId: current.id,
|
|
envelopeType: current.type,
|
|
recipients: withKnownRecipientIds(localRecipients),
|
|
});
|
|
|
|
for (const recipient of response.data) {
|
|
if (recipient.clientId) {
|
|
recipientIdByFormIdRef.current.set(recipient.clientId, recipient.id);
|
|
}
|
|
}
|
|
|
|
recipients = response.data;
|
|
} else {
|
|
recipients = mapLocalRecipientsToRecipients({ envelope: current, localRecipients });
|
|
}
|
|
|
|
setEnvelope((prev) => ({
|
|
...prev,
|
|
recipients,
|
|
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(
|
|
envelopeRef.current.fields.filter((field) =>
|
|
recipients.some((recipient) => recipient.id === field.recipientId),
|
|
),
|
|
);
|
|
|
|
setAutosaveError(false);
|
|
} catch (err) {
|
|
console.error(err);
|
|
|
|
setAutosaveError(true);
|
|
|
|
toast({
|
|
title: t`Save failed`,
|
|
description: t`We encountered an error while attempting to save your changes. Your changes cannot be saved at this time.`,
|
|
variant: 'destructive',
|
|
duration: 7500,
|
|
});
|
|
}
|
|
}, 1000);
|
|
|
|
const setRecipientsAsync = async (localRecipients: SetRecipientsPayload) => {
|
|
setRecipientsDebounced(localRecipients);
|
|
await flushSetRecipients();
|
|
};
|
|
|
|
/**
|
|
* Handles debouncing the fields updates to the server.
|
|
*
|
|
* Will set the local envelope fields after the update is complete.
|
|
*/
|
|
const {
|
|
triggerSave: setFieldsDebounced,
|
|
flush: flushSetFields,
|
|
isPending: isFieldsMutationPending,
|
|
} = useEnvelopeAutosave(async (localFields: TLocalField[]) => {
|
|
try {
|
|
let fields: TSetEnvelopeFieldsResponse['data'] = [];
|
|
|
|
if (!isEmbedded) {
|
|
const response = await setFieldsMutation.mutateAsync({
|
|
envelopeId: envelope.id,
|
|
envelopeType: envelope.type,
|
|
fields: localFields,
|
|
});
|
|
|
|
fields = response.data;
|
|
} else {
|
|
fields = mapLocalFieldsToFields({ envelope, localFields });
|
|
}
|
|
|
|
setEnvelope((prev) => ({
|
|
...prev,
|
|
fields,
|
|
}));
|
|
|
|
setAutosaveError(false);
|
|
|
|
// Insert the IDs into the local fields.
|
|
fields.forEach((field) => {
|
|
const localField = localFields.find((localField) => localField.formId === field.formId);
|
|
|
|
if (localField && !localField.id) {
|
|
localField.id = field.id;
|
|
|
|
editorFields.setFieldId(localField.formId, field.id);
|
|
}
|
|
});
|
|
} catch (err) {
|
|
console.error(err);
|
|
|
|
setAutosaveError(true);
|
|
|
|
toast({
|
|
title: t`Save failed`,
|
|
description: t`We encountered an error while attempting to save your changes. Your changes cannot be saved at this time.`,
|
|
variant: 'destructive',
|
|
duration: 7500,
|
|
});
|
|
}
|
|
}, 2000);
|
|
|
|
const setFieldsAsync = async (localFields: TLocalField[]) => {
|
|
setFieldsDebounced(localFields);
|
|
await flushSetFields();
|
|
};
|
|
|
|
/**
|
|
* Handles debouncing the envelope updates to the server.
|
|
*
|
|
* Will set the local envelope after the update is complete.
|
|
*/
|
|
const {
|
|
triggerSave: updateEnvelopeDebounced,
|
|
flush: flushUpdateEnvelope,
|
|
isPending: isEnvelopeMutationPending,
|
|
} = useEnvelopeAutosave(async ({ data, meta }: UpdateEnvelopePayload) => {
|
|
try {
|
|
const response = !isEmbedded
|
|
? await updateEnvelopeMutation.mutateAsync({
|
|
envelopeId: envelope.id,
|
|
data,
|
|
meta,
|
|
})
|
|
: {};
|
|
|
|
setEnvelope((prev) => ({
|
|
...prev,
|
|
...data,
|
|
authOptions: {
|
|
globalAccessAuth: data?.globalAccessAuth || [],
|
|
globalActionAuth: data?.globalActionAuth || [],
|
|
},
|
|
...response,
|
|
documentMeta: {
|
|
...prev.documentMeta,
|
|
...meta,
|
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
emailSettings: (meta?.emailSettings || null) as unknown as TDocumentEmailSettings | null,
|
|
},
|
|
}));
|
|
|
|
setAutosaveError(false);
|
|
} catch (err) {
|
|
console.error(err);
|
|
|
|
setAutosaveError(true);
|
|
|
|
toast({
|
|
title: t`Save failed`,
|
|
description: t`We encountered an error while attempting to save your changes. Your changes cannot be saved at this time.`,
|
|
variant: 'destructive',
|
|
duration: 7500,
|
|
});
|
|
}
|
|
}, 1000);
|
|
|
|
const updateEnvelopeAsync = async (envelopeUpdates: UpdateEnvelopePayload) => {
|
|
updateEnvelopeDebounced(envelopeUpdates);
|
|
await flushUpdateEnvelope();
|
|
};
|
|
|
|
/**
|
|
* Updates the local envelope and debounces the update to the server.
|
|
*
|
|
* Use this when you want to update the local envelope immediately while debouncing
|
|
* the actual update to the server.
|
|
*/
|
|
const updateEnvelope = (envelopeUpdates: UpdateEnvelopePayload) => {
|
|
setEnvelope((prev) => ({
|
|
...prev,
|
|
...envelopeUpdates.data,
|
|
meta: {
|
|
...prev.documentMeta,
|
|
...envelopeUpdates.meta,
|
|
},
|
|
}));
|
|
|
|
updateEnvelopeDebounced(envelopeUpdates);
|
|
};
|
|
|
|
const getRecipientColorKey = useCallback(
|
|
(recipientId: number) => {
|
|
const recipientIndex = envelope.recipients.findIndex(
|
|
(recipient) => recipient.id === recipientId,
|
|
);
|
|
|
|
return AVAILABLE_RECIPIENT_COLORS[
|
|
Math.max(recipientIndex, 0) % AVAILABLE_RECIPIENT_COLORS.length
|
|
];
|
|
},
|
|
[envelope.recipients],
|
|
);
|
|
|
|
const { refetch: reloadEnvelope } = useZapQuery<TGetEditorEnvelopeResponse>(
|
|
'envelope.editor.get',
|
|
{
|
|
envelopeId: envelope.id,
|
|
},
|
|
{
|
|
enabled: !isEmbedded,
|
|
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
|
},
|
|
);
|
|
|
|
/**
|
|
* Fetch and sync the envelope back into the editor.
|
|
*
|
|
* Overrides everything.
|
|
*/
|
|
const syncEnvelope = async () => {
|
|
await flushAutosave();
|
|
|
|
// Bypass syncing for embedded mode.
|
|
if (isEmbedded) {
|
|
return;
|
|
}
|
|
|
|
const fetchedEnvelopeData = await reloadEnvelope();
|
|
|
|
if (fetchedEnvelopeData.data) {
|
|
setEnvelope(fetchedEnvelopeData.data);
|
|
|
|
editorRecipients.resetForm({
|
|
recipients: fetchedEnvelopeData.data.recipients,
|
|
documentMeta: fetchedEnvelopeData.data.documentMeta,
|
|
});
|
|
|
|
editorFields.resetForm(fetchedEnvelopeData.data.fields);
|
|
}
|
|
};
|
|
|
|
const setLocalEnvelope = (localEnvelope: Partial<TEditorEnvelope>) => {
|
|
setEnvelope((prev) => ({ ...prev, ...localEnvelope }));
|
|
};
|
|
|
|
const isAutosaving = useMemo(() => {
|
|
return isFieldsMutationPending || isRecipientsMutationPending || isEnvelopeMutationPending;
|
|
}, [isFieldsMutationPending, isRecipientsMutationPending, isEnvelopeMutationPending]);
|
|
|
|
const relativePath = useMemo(() => {
|
|
let documentRootPath = formatDocumentsPath(envelope.team.url);
|
|
let templateRootPath = formatTemplatesPath(envelope.team.url);
|
|
|
|
const basePath = envelope.type === EnvelopeType.DOCUMENT ? documentRootPath : templateRootPath;
|
|
let envelopePath = `${basePath}/${envelope.id}`;
|
|
let editorPath = `${basePath}/${envelope.id}/edit`;
|
|
|
|
if (editorConfig.embedded) {
|
|
let embeddedEditorPath =
|
|
editorConfig.embedded.mode === 'edit'
|
|
? `/embed/v2/authoring/envelope/edit/${envelope.id}`
|
|
: `/embed/v2/authoring/envelope/create`;
|
|
|
|
embeddedEditorPath += `?token=${editorConfig.embedded.presignToken}`;
|
|
|
|
envelopePath = embeddedEditorPath;
|
|
editorPath = embeddedEditorPath;
|
|
documentRootPath = embeddedEditorPath;
|
|
templateRootPath = embeddedEditorPath;
|
|
}
|
|
|
|
return {
|
|
basePath,
|
|
envelopePath,
|
|
editorPath,
|
|
documentRootPath,
|
|
templateRootPath,
|
|
};
|
|
}, [envelope.type, envelope.id]);
|
|
|
|
const navigateToStep = (step: EnvelopeEditorStep) => {
|
|
setSearchParams((prev) => {
|
|
const newParams = new URLSearchParams(prev);
|
|
|
|
if (step === 'upload') {
|
|
newParams.delete('step');
|
|
} else {
|
|
newParams.set('step', step);
|
|
}
|
|
|
|
return newParams;
|
|
});
|
|
};
|
|
|
|
const resetForms = () => {
|
|
editorRecipients.resetForm({
|
|
recipients: envelopeRef.current.recipients,
|
|
documentMeta: envelopeRef.current.documentMeta,
|
|
});
|
|
|
|
editorFields.resetForm(envelopeRef.current.fields);
|
|
};
|
|
|
|
const flushAutosave = async (): Promise<TEditorEnvelope> => {
|
|
await Promise.all([flushSetFields(), flushSetRecipients(), flushUpdateEnvelope()]);
|
|
|
|
// Flush all registered external flushes (e.g., upload page's debounced item updates).
|
|
const externalFlushes = Array.from(externalFlushCallbacksRef.current.values());
|
|
await Promise.all(externalFlushes.map(async (flush) => flush()));
|
|
|
|
// Await all registered pending mutations (e.g., in-flight creates/deletes).
|
|
// Use allSettled so a single failed mutation doesn't prevent awaiting the rest.
|
|
if (pendingMutationsRef.current.size > 0) {
|
|
await Promise.allSettled(Array.from(pendingMutationsRef.current));
|
|
}
|
|
|
|
return envelopeRef.current;
|
|
};
|
|
|
|
return (
|
|
<EnvelopeEditorContext.Provider
|
|
value={{
|
|
editorConfig,
|
|
envelope,
|
|
isEmbedded,
|
|
isDocument: envelope.type === EnvelopeType.DOCUMENT,
|
|
isTemplate: envelope.type === EnvelopeType.TEMPLATE,
|
|
setLocalEnvelope,
|
|
getRecipientColorKey,
|
|
updateEnvelope,
|
|
updateEnvelopeAsync,
|
|
setRecipientsDebounced,
|
|
setRecipientsAsync,
|
|
editorFields,
|
|
editorRecipients,
|
|
autosaveError,
|
|
flushAutosave,
|
|
isAutosaving,
|
|
relativePath,
|
|
syncEnvelope,
|
|
navigateToStep,
|
|
resetForms,
|
|
registerExternalFlush,
|
|
registerPendingMutation,
|
|
organisationEmails,
|
|
}}
|
|
>
|
|
{children}
|
|
</EnvelopeEditorContext.Provider>
|
|
);
|
|
};
|
|
|
|
type MapLocalRecipientsToRecipientsOptions = {
|
|
envelope: TEditorEnvelope;
|
|
localRecipients: TSetEnvelopeRecipientsRequest['recipients'];
|
|
};
|
|
|
|
const mapLocalRecipientsToRecipients = ({
|
|
envelope,
|
|
localRecipients,
|
|
}: MapLocalRecipientsToRecipientsOptions): TEditorEnvelope['recipients'] => {
|
|
let smallestRecipientId = localRecipients.reduce((min, recipient) => {
|
|
if (recipient.id && recipient.id < min) {
|
|
return recipient.id;
|
|
}
|
|
|
|
return min;
|
|
}, -1);
|
|
|
|
return localRecipients.map((recipient) => {
|
|
const foundRecipient = envelope.recipients.find((recipient) => recipient.id === recipient.id);
|
|
|
|
let recipientId = recipient.id;
|
|
|
|
if (recipientId === undefined) {
|
|
recipientId = smallestRecipientId;
|
|
smallestRecipientId--;
|
|
}
|
|
|
|
return {
|
|
id: recipientId,
|
|
envelopeId: envelope.id,
|
|
email: recipient.email,
|
|
name: recipient.name,
|
|
token: foundRecipient?.token || '',
|
|
documentDeletedAt: foundRecipient?.documentDeletedAt || null,
|
|
expired: foundRecipient?.expired || null,
|
|
signedAt: foundRecipient?.signedAt || null,
|
|
authOptions:
|
|
recipient.actionAuth.length > 0
|
|
? { actionAuth: recipient.actionAuth, accessAuth: [] }
|
|
: null,
|
|
signingOrder: recipient.signingOrder ?? null,
|
|
rejectionReason: foundRecipient?.rejectionReason || null,
|
|
role: recipient.role,
|
|
readStatus: foundRecipient?.readStatus || ReadStatus.NOT_OPENED,
|
|
signingStatus: foundRecipient?.signingStatus || SigningStatus.NOT_SIGNED,
|
|
sendStatus: foundRecipient?.sendStatus || SendStatus.NOT_SENT,
|
|
expiresAt: foundRecipient?.expiresAt || null,
|
|
expirationNotifiedAt: foundRecipient?.expirationNotifiedAt || null,
|
|
};
|
|
});
|
|
};
|
|
|
|
type MapLocalFieldsToFieldsOptions = {
|
|
localFields: TLocalField[];
|
|
envelope: TEditorEnvelope;
|
|
};
|
|
|
|
const mapLocalFieldsToFields = ({
|
|
envelope,
|
|
localFields,
|
|
}: MapLocalFieldsToFieldsOptions): TSetEnvelopeFieldsResponse['data'] => {
|
|
let smallestFieldId = localFields.reduce((min, field) => {
|
|
if (field.id && field.id < min) {
|
|
return field.id;
|
|
}
|
|
|
|
return min;
|
|
}, -1);
|
|
|
|
return localFields.map((field) => {
|
|
const foundField = envelope.fields.find((envelopeField) => envelopeField.id === field.id);
|
|
|
|
let fieldId = field.id;
|
|
|
|
if (fieldId === undefined) {
|
|
fieldId = smallestFieldId;
|
|
smallestFieldId--;
|
|
}
|
|
|
|
return {
|
|
...field,
|
|
formId: field.formId,
|
|
id: fieldId,
|
|
envelopeId: envelope.id,
|
|
envelopeItemId: field.envelopeItemId,
|
|
type: field.type,
|
|
recipientId: field.recipientId,
|
|
positionX: new Prisma.Decimal(field.positionX),
|
|
positionY: new Prisma.Decimal(field.positionY),
|
|
width: new Prisma.Decimal(field.width),
|
|
height: new Prisma.Decimal(field.height),
|
|
secondaryId: foundField?.secondaryId || '',
|
|
inserted: foundField?.inserted || false,
|
|
customText: foundField?.customText || '',
|
|
fieldMeta: field.fieldMeta || null,
|
|
};
|
|
});
|
|
};
|