mirror of
https://github.com/documenso/documenso.git
synced 2025-11-10 04:22:32 +10:00
fix: envelope autosave (#2103)
This commit is contained in:
@ -1,6 +1,7 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { useForm, useWatch } from 'react-hook-form';
|
import { useForm, useWatch } from 'react-hook-form';
|
||||||
import type { z } from 'zod';
|
import type { z } from 'zod';
|
||||||
|
|
||||||
@ -60,7 +61,12 @@ export const EditorFieldSignatureForm = ({
|
|||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form>
|
<form>
|
||||||
<fieldset className="flex flex-col gap-2">
|
<fieldset className="flex flex-col gap-2">
|
||||||
<EditorGenericFontSizeField formControl={form.control} />
|
<div>
|
||||||
|
<EditorGenericFontSizeField formControl={form.control} />
|
||||||
|
<p className="text-muted-foreground mt-0.5 text-xs">
|
||||||
|
<Trans>The typed signature font size</Trans>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@ -57,8 +57,6 @@ export default function EnvelopeEditorFieldsPageRenderer() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleResizeOrMove = (event: KonvaEventObject<Event>) => {
|
const handleResizeOrMove = (event: KonvaEventObject<Event>) => {
|
||||||
console.log('Field resized or moved');
|
|
||||||
|
|
||||||
const { current: container } = canvasElement;
|
const { current: container } = canvasElement;
|
||||||
|
|
||||||
if (!container) {
|
if (!container) {
|
||||||
@ -273,9 +271,6 @@ export default function EnvelopeEditorFieldsPageRenderer() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`pointerPosition.x: ${pointerPosition.x}`);
|
|
||||||
console.log(`pointerPosition.y: ${pointerPosition.y}`);
|
|
||||||
|
|
||||||
x1 = pointerPosition.x / scale;
|
x1 = pointerPosition.x / scale;
|
||||||
y1 = pointerPosition.y / scale;
|
y1 = pointerPosition.y / scale;
|
||||||
x2 = pointerPosition.x / scale;
|
x2 = pointerPosition.x / scale;
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import type {
|
|||||||
TNameFieldMeta,
|
TNameFieldMeta,
|
||||||
TNumberFieldMeta,
|
TNumberFieldMeta,
|
||||||
TRadioFieldMeta,
|
TRadioFieldMeta,
|
||||||
|
TSignatureFieldMeta,
|
||||||
TTextFieldMeta,
|
TTextFieldMeta,
|
||||||
} from '@documenso/lib/types/field-meta';
|
} from '@documenso/lib/types/field-meta';
|
||||||
import { canRecipientFieldsBeModified } from '@documenso/lib/utils/recipients';
|
import { canRecipientFieldsBeModified } from '@documenso/lib/utils/recipients';
|
||||||
@ -38,6 +39,7 @@ import { EditorFieldInitialsForm } from '~/components/forms/editor/editor-field-
|
|||||||
import { EditorFieldNameForm } from '~/components/forms/editor/editor-field-name-form';
|
import { EditorFieldNameForm } from '~/components/forms/editor/editor-field-name-form';
|
||||||
import { EditorFieldNumberForm } from '~/components/forms/editor/editor-field-number-form';
|
import { EditorFieldNumberForm } from '~/components/forms/editor/editor-field-number-form';
|
||||||
import { EditorFieldRadioForm } from '~/components/forms/editor/editor-field-radio-form';
|
import { EditorFieldRadioForm } from '~/components/forms/editor/editor-field-radio-form';
|
||||||
|
import { EditorFieldSignatureForm } from '~/components/forms/editor/editor-field-signature-form';
|
||||||
import { EditorFieldTextForm } from '~/components/forms/editor/editor-field-text-form';
|
import { EditorFieldTextForm } from '~/components/forms/editor/editor-field-text-form';
|
||||||
|
|
||||||
import { EnvelopeEditorFieldDragDrop } from './envelope-editor-fields-drag-drop';
|
import { EnvelopeEditorFieldDragDrop } from './envelope-editor-fields-drag-drop';
|
||||||
@ -189,7 +191,7 @@ export const EnvelopeEditorFieldsPage = () => {
|
|||||||
|
|
||||||
{/* Field details section. */}
|
{/* Field details section. */}
|
||||||
<AnimateGenericFadeInOut key={editorFields.selectedField?.formId}>
|
<AnimateGenericFadeInOut key={editorFields.selectedField?.formId}>
|
||||||
{selectedField && selectedField.type !== FieldType.SIGNATURE && (
|
{selectedField && (
|
||||||
<section>
|
<section>
|
||||||
<Separator className="my-4" />
|
<Separator className="my-4" />
|
||||||
|
|
||||||
@ -199,6 +201,12 @@ export const EnvelopeEditorFieldsPage = () => {
|
|||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
{match(selectedField.type)
|
{match(selectedField.type)
|
||||||
|
.with(FieldType.SIGNATURE, () => (
|
||||||
|
<EditorFieldSignatureForm
|
||||||
|
value={selectedField?.fieldMeta as TSignatureFieldMeta | undefined}
|
||||||
|
onValueChange={(value) => updateSelectedFieldMeta(value)}
|
||||||
|
/>
|
||||||
|
))
|
||||||
.with(FieldType.CHECKBOX, () => (
|
.with(FieldType.CHECKBOX, () => (
|
||||||
<EditorFieldCheckboxForm
|
<EditorFieldCheckboxForm
|
||||||
value={selectedField?.fieldMeta as TCheckboxFieldMeta | undefined}
|
value={selectedField?.fieldMeta as TCheckboxFieldMeta | undefined}
|
||||||
|
|||||||
@ -50,6 +50,7 @@ type UseEditorFieldsResponse = {
|
|||||||
|
|
||||||
// Field operations
|
// Field operations
|
||||||
addField: (field: Omit<TLocalField, 'formId'>) => TLocalField;
|
addField: (field: Omit<TLocalField, 'formId'>) => TLocalField;
|
||||||
|
setFieldId: (formId: string, id: number) => void;
|
||||||
removeFieldsByFormId: (formIds: string[]) => void;
|
removeFieldsByFormId: (formIds: string[]) => void;
|
||||||
updateFieldByFormId: (formId: string, updates: Partial<TLocalField>) => void;
|
updateFieldByFormId: (formId: string, updates: Partial<TLocalField>) => void;
|
||||||
duplicateField: (field: TLocalField, recipientId?: number) => TLocalField;
|
duplicateField: (field: TLocalField, recipientId?: number) => TLocalField;
|
||||||
@ -160,6 +161,17 @@ export const useEditorFields = ({
|
|||||||
[localFields, remove, triggerFieldsUpdate],
|
[localFields, remove, triggerFieldsUpdate],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const setFieldId = (formId: string, id: number) => {
|
||||||
|
const index = localFields.findIndex((field) => field.formId === formId);
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
update(index, {
|
||||||
|
...localFields[index],
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const updateFieldByFormId = useCallback(
|
const updateFieldByFormId = useCallback(
|
||||||
(formId: string, updates: Partial<TLocalField>) => {
|
(formId: string, updates: Partial<TLocalField>) => {
|
||||||
const index = localFields.findIndex((field) => field.formId === formId);
|
const index = localFields.findIndex((field) => field.formId === formId);
|
||||||
@ -269,6 +281,7 @@ export const useEditorFields = ({
|
|||||||
|
|
||||||
// Field operations
|
// Field operations
|
||||||
addField,
|
addField,
|
||||||
|
setFieldId,
|
||||||
removeFieldsByFormId,
|
removeFieldsByFormId,
|
||||||
updateFieldByFormId,
|
updateFieldByFormId,
|
||||||
duplicateField,
|
duplicateField,
|
||||||
|
|||||||
@ -97,6 +97,11 @@ export const EnvelopeEditorProvider = ({
|
|||||||
const [envelope, setEnvelope] = useState(initialEnvelope);
|
const [envelope, setEnvelope] = useState(initialEnvelope);
|
||||||
const [autosaveError, setAutosaveError] = useState<boolean>(false);
|
const [autosaveError, setAutosaveError] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const editorFields = useEditorFields({
|
||||||
|
envelope,
|
||||||
|
handleFieldsUpdate: (fields) => setFieldsDebounced(fields),
|
||||||
|
});
|
||||||
|
|
||||||
const envelopeUpdateMutationQuery = trpc.envelope.update.useMutation({
|
const envelopeUpdateMutationQuery = trpc.envelope.update.useMutation({
|
||||||
onSuccess: (response, input) => {
|
onSuccess: (response, input) => {
|
||||||
setEnvelope({
|
setEnvelope({
|
||||||
@ -184,13 +189,24 @@ export const EnvelopeEditorProvider = ({
|
|||||||
triggerSave: setFieldsDebounced,
|
triggerSave: setFieldsDebounced,
|
||||||
flush: setFieldsAsync,
|
flush: setFieldsAsync,
|
||||||
isPending: isFieldsMutationPending,
|
isPending: isFieldsMutationPending,
|
||||||
} = useEnvelopeAutosave(async (fields: TLocalField[]) => {
|
} = useEnvelopeAutosave(async (localFields: TLocalField[]) => {
|
||||||
await envelopeFieldSetMutationQuery.mutateAsync({
|
const envelopeFields = await envelopeFieldSetMutationQuery.mutateAsync({
|
||||||
envelopeId: envelope.id,
|
envelopeId: envelope.id,
|
||||||
envelopeType: envelope.type,
|
envelopeType: envelope.type,
|
||||||
fields,
|
fields: localFields,
|
||||||
});
|
});
|
||||||
}, 1000);
|
|
||||||
|
// Insert the IDs into the local fields.
|
||||||
|
envelopeFields.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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
triggerSave: setEnvelopeDebounced,
|
triggerSave: setEnvelopeDebounced,
|
||||||
@ -221,11 +237,6 @@ export const EnvelopeEditorProvider = ({
|
|||||||
setEnvelopeDebounced(envelopeUpdates);
|
setEnvelopeDebounced(envelopeUpdates);
|
||||||
};
|
};
|
||||||
|
|
||||||
const editorFields = useEditorFields({
|
|
||||||
envelope,
|
|
||||||
handleFieldsUpdate: (fields) => setFieldsDebounced(fields),
|
|
||||||
});
|
|
||||||
|
|
||||||
const getRecipientColorKey = useCallback(
|
const getRecipientColorKey = useCallback(
|
||||||
(recipientId: number) => {
|
(recipientId: number) => {
|
||||||
const recipientIndex = envelope.recipients.findIndex(
|
const recipientIndex = envelope.recipients.findIndex(
|
||||||
|
|||||||
@ -306,7 +306,10 @@ export const setFieldsForDocument = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return upsertedField;
|
return {
|
||||||
|
...upsertedField,
|
||||||
|
formId: field.formId,
|
||||||
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -340,17 +343,25 @@ export const setFieldsForDocument = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Filter out fields that have been removed or have been updated.
|
// Filter out fields that have been removed or have been updated.
|
||||||
const filteredFields = existingFields.filter((field) => {
|
const mappedFilteredFields = existingFields
|
||||||
const isRemoved = removedFields.find((removedField) => removedField.id === field.id);
|
.filter((field) => {
|
||||||
const isUpdated = persistedFields.find((persistedField) => persistedField.id === field.id);
|
const isRemoved = removedFields.find((removedField) => removedField.id === field.id);
|
||||||
|
const isUpdated = persistedFields.find((persistedField) => persistedField.id === field.id);
|
||||||
|
|
||||||
return !isRemoved && !isUpdated;
|
return !isRemoved && !isUpdated;
|
||||||
});
|
})
|
||||||
|
.map((field) => ({
|
||||||
|
...mapFieldToLegacyField(field, envelope),
|
||||||
|
formId: undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mappedPersistentFields = persistedFields.map((field) => ({
|
||||||
|
...mapFieldToLegacyField(field, envelope),
|
||||||
|
formId: field?.formId,
|
||||||
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fields: [...filteredFields, ...persistedFields].map((field) =>
|
fields: [...mappedFilteredFields, ...mappedPersistentFields],
|
||||||
mapFieldToLegacyField(field, envelope),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -359,6 +370,7 @@ export const setFieldsForDocument = async ({
|
|||||||
*/
|
*/
|
||||||
type FieldData = {
|
type FieldData = {
|
||||||
id?: number | null;
|
id?: number | null;
|
||||||
|
formId?: string;
|
||||||
envelopeItemId: string;
|
envelopeItemId: string;
|
||||||
type: FieldType;
|
type: FieldType;
|
||||||
recipientId: number;
|
recipientId: number;
|
||||||
|
|||||||
@ -27,6 +27,7 @@ export type SetFieldsForTemplateOptions = {
|
|||||||
id: EnvelopeIdOptions;
|
id: EnvelopeIdOptions;
|
||||||
fields: {
|
fields: {
|
||||||
id?: number | null;
|
id?: number | null;
|
||||||
|
formId?: string;
|
||||||
envelopeItemId: string;
|
envelopeItemId: string;
|
||||||
type: FieldType;
|
type: FieldType;
|
||||||
recipientId: number;
|
recipientId: number;
|
||||||
@ -111,10 +112,10 @@ export const setFieldsForTemplate = async ({
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const persistedFields = await prisma.$transaction(
|
const persistedFields = await Promise.all(
|
||||||
// Disabling as wrapping promises here causes type issues
|
// Disabling as wrapping promises here causes type issues
|
||||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||||
linkedFields.map((field) => {
|
linkedFields.map(async (field) => {
|
||||||
const parsedFieldMeta = field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : undefined;
|
const parsedFieldMeta = field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : undefined;
|
||||||
|
|
||||||
if (field.type === FieldType.TEXT && field.fieldMeta) {
|
if (field.type === FieldType.TEXT && field.fieldMeta) {
|
||||||
@ -176,7 +177,7 @@ export const setFieldsForTemplate = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Proceed with upsert operation
|
// Proceed with upsert operation
|
||||||
return prisma.field.upsert({
|
const upsertedField = await prisma.field.upsert({
|
||||||
where: {
|
where: {
|
||||||
id: field._persisted?.id ?? -1,
|
id: field._persisted?.id ?? -1,
|
||||||
envelopeId: envelope.id,
|
envelopeId: envelope.id,
|
||||||
@ -219,6 +220,11 @@ export const setFieldsForTemplate = async ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...upsertedField,
|
||||||
|
formId: field.formId,
|
||||||
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -240,9 +246,17 @@ export const setFieldsForTemplate = async ({
|
|||||||
return !isRemoved && !isUpdated;
|
return !isRemoved && !isUpdated;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mappedFilteredFields = filteredFields.map((field) => ({
|
||||||
|
...mapFieldToLegacyField(field, envelope),
|
||||||
|
formId: undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mappedPersistentFields = persistedFields.map((field) => ({
|
||||||
|
...mapFieldToLegacyField(field, envelope),
|
||||||
|
formId: field?.formId,
|
||||||
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fields: [...filteredFields, ...persistedFields].map((field) =>
|
fields: [...mappedFilteredFields, ...mappedPersistentFields],
|
||||||
mapFieldToLegacyField(field, envelope),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -24,7 +24,7 @@ export const setEnvelopeFieldsRoute = authenticatedProcedure
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await match(envelopeType)
|
const result = await match(envelopeType)
|
||||||
.with(EnvelopeType.DOCUMENT, async () =>
|
.with(EnvelopeType.DOCUMENT, async () =>
|
||||||
setFieldsForDocument({
|
setFieldsForDocument({
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
@ -63,4 +63,11 @@ export const setEnvelopeFieldsRoute = authenticatedProcedure
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.exhaustive();
|
.exhaustive();
|
||||||
|
|
||||||
|
return {
|
||||||
|
fields: result.fields.map((field) => ({
|
||||||
|
id: field.id,
|
||||||
|
formId: field.formId,
|
||||||
|
})),
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -12,6 +12,7 @@ export const ZSetEnvelopeFieldsRequestSchema = z.object({
|
|||||||
.number()
|
.number()
|
||||||
.optional()
|
.optional()
|
||||||
.describe('The id of the field. If not provided, a new field will be created.'),
|
.describe('The id of the field. If not provided, a new field will be created.'),
|
||||||
|
formId: z.string().optional().describe('A temporary ID to keep track of new fields created'),
|
||||||
envelopeItemId: z.string().describe('The id of the envelope item to put the field on'),
|
envelopeItemId: z.string().describe('The id of the envelope item to put the field on'),
|
||||||
recipientId: z.number(),
|
recipientId: z.number(),
|
||||||
type: z.nativeEnum(FieldType),
|
type: z.nativeEnum(FieldType),
|
||||||
@ -45,7 +46,14 @@ export const ZSetEnvelopeFieldsRequestSchema = z.object({
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZSetEnvelopeFieldsResponseSchema = z.void();
|
export const ZSetEnvelopeFieldsResponseSchema = z.object({
|
||||||
|
fields: z
|
||||||
|
.object({
|
||||||
|
id: z.number(),
|
||||||
|
formId: z.string().optional(),
|
||||||
|
})
|
||||||
|
.array(),
|
||||||
|
});
|
||||||
|
|
||||||
export type TSetEnvelopeFieldsRequest = z.infer<typeof ZSetEnvelopeFieldsRequestSchema>;
|
export type TSetEnvelopeFieldsRequest = z.infer<typeof ZSetEnvelopeFieldsRequestSchema>;
|
||||||
export type TSetEnvelopeFieldsResponse = z.infer<typeof ZSetEnvelopeFieldsResponseSchema>;
|
export type TSetEnvelopeFieldsResponse = z.infer<typeof ZSetEnvelopeFieldsResponseSchema>;
|
||||||
|
|||||||
Reference in New Issue
Block a user