mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 08:13:56 +10:00
feat: allow editing pending documents (#1346)
## Description Adds the ability for the document owner to edit recipients and their fields after the document has been sent. A recipient can only be updated or deleted if: - The recipient has not inserted any fields - Has not completed the document <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes - **New Features** - Added new localization messages to clarify user actions regarding document signing. - Enhanced French translations for improved user interaction. - **Improvements** - Updated localization strings in German and English for clearer feedback on signer and recipient statuses. - Improved overall structure of localization files for better maintainability. - **Dependency Updates** - Upgraded `next-axiom` and `remeda` libraries to their latest versions, potentially enhancing performance and stability. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Mythie <me@lucasjamessmith.me>
This commit is contained in:
@ -32,11 +32,16 @@ import {
|
||||
ZFieldMetaSchema,
|
||||
} from '@documenso/lib/types/field-meta';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import {
|
||||
canRecipientBeModified,
|
||||
canRecipientFieldsBeModified,
|
||||
} from '@documenso/lib/utils/recipients';
|
||||
import type { Field, Recipient } from '@documenso/prisma/client';
|
||||
import { FieldType, RecipientRole, SendStatus } from '@documenso/prisma/client';
|
||||
|
||||
import { getSignerColorStyles, useSignerColors } from '../../lib/signer-colors';
|
||||
import { cn } from '../../lib/utils';
|
||||
import { Alert, AlertDescription } from '../alert';
|
||||
import { Button } from '../button';
|
||||
import { Card, CardContent } from '../card';
|
||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from '../command';
|
||||
@ -191,8 +196,6 @@ export const AddFieldsFormPartial = ({
|
||||
selectedSignerIndex === -1 ? 0 : selectedSignerIndex,
|
||||
);
|
||||
|
||||
const hasSelectedSignerBeenSent = selectedSigner?.sendStatus === SendStatus.SENT;
|
||||
|
||||
const filterFieldsWithEmptyValues = (fields: typeof localFields, fieldType: string) =>
|
||||
fields
|
||||
.filter((field) => field.type === fieldType)
|
||||
@ -225,11 +228,13 @@ export const AddFieldsFormPartial = ({
|
||||
const hasErrors =
|
||||
emptyCheckboxFields.length > 0 || emptyRadioFields.length > 0 || emptySelectFields.length > 0;
|
||||
|
||||
const isFieldsDisabled =
|
||||
!selectedSigner ||
|
||||
hasSelectedSignerBeenSent ||
|
||||
selectedSigner?.role === RecipientRole.VIEWER ||
|
||||
selectedSigner?.role === RecipientRole.CC;
|
||||
const isFieldsDisabled = useMemo(() => {
|
||||
if (!selectedSigner) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !canRecipientFieldsBeModified(selectedSigner, fields);
|
||||
}, [selectedSigner, fields]);
|
||||
|
||||
const [isFieldWithinBounds, setIsFieldWithinBounds] = useState(false);
|
||||
const [coords, setCoords] = useState({
|
||||
@ -568,7 +573,8 @@ export const AddFieldsFormPartial = ({
|
||||
recipientIndex={recipientIndex === -1 ? 0 : recipientIndex}
|
||||
field={field}
|
||||
disabled={
|
||||
selectedSigner?.email !== field.signerEmail || hasSelectedSignerBeenSent
|
||||
selectedSigner?.email !== field.signerEmail ||
|
||||
!canRecipientBeModified(selectedSigner, fields)
|
||||
}
|
||||
minHeight={fieldBounds.current.height}
|
||||
minWidth={fieldBounds.current.width}
|
||||
@ -976,6 +982,7 @@ export const AddFieldsFormPartial = ({
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFlowFormContainerContent>
|
||||
|
||||
{hasErrors && (
|
||||
<div className="mt-4">
|
||||
<ul>
|
||||
@ -993,6 +1000,18 @@ export const AddFieldsFormPartial = ({
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedSigner && !canRecipientFieldsBeModified(selectedSigner, fields) && (
|
||||
<Alert variant="warning">
|
||||
<AlertDescription>
|
||||
<Trans>
|
||||
This recipient can no longer be modified as they have signed a field, or completed
|
||||
the document.
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<DocumentFlowFormContainerFooter>
|
||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ import { prop, sortBy } from 'remeda';
|
||||
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
|
||||
import { ZRecipientAuthOptionsSchema } from '@documenso/lib/types/document-auth';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { canRecipientBeModified as utilCanRecipientBeModified } from '@documenso/lib/utils/recipients';
|
||||
import type { Field, Recipient } from '@documenso/prisma/client';
|
||||
import { DocumentSigningOrder, RecipientRole, SendStatus } from '@documenso/prisma/client';
|
||||
import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';
|
||||
@ -159,21 +160,19 @@ export const AddSignersFormPartial = ({
|
||||
(recipient) => recipient.sendStatus === SendStatus.SENT,
|
||||
);
|
||||
|
||||
const hasBeenSentToRecipientId = useCallback(
|
||||
(id?: number) => {
|
||||
if (!id) {
|
||||
return false;
|
||||
}
|
||||
const canRecipientBeModified = (recipientId?: number) => {
|
||||
if (recipientId === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return recipients.some(
|
||||
(recipient) =>
|
||||
recipient.id === id &&
|
||||
recipient.sendStatus === SendStatus.SENT &&
|
||||
recipient.role !== RecipientRole.CC,
|
||||
);
|
||||
},
|
||||
[recipients],
|
||||
);
|
||||
const recipient = recipients.find((recipient) => recipient.id === recipientId);
|
||||
|
||||
if (!recipient) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return utilCanRecipientBeModified(recipient, fields);
|
||||
};
|
||||
|
||||
const onAddSigner = () => {
|
||||
appendSigner({
|
||||
@ -189,10 +188,10 @@ export const AddSignersFormPartial = ({
|
||||
const onRemoveSigner = (index: number) => {
|
||||
const signer = signers[index];
|
||||
|
||||
if (hasBeenSentToRecipientId(signer.nativeId)) {
|
||||
if (!canRecipientBeModified(signer.nativeId)) {
|
||||
toast({
|
||||
title: _(msg`Cannot remove signer`),
|
||||
description: _(msg`This signer has already received the document.`),
|
||||
description: _(msg`This signer has already signed the document.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
|
||||
@ -235,7 +234,7 @@ export const AddSignersFormPartial = ({
|
||||
const [reorderedSigner] = items.splice(result.source.index, 1);
|
||||
|
||||
let insertIndex = result.destination.index;
|
||||
while (insertIndex < items.length && hasBeenSentToRecipientId(items[insertIndex].nativeId)) {
|
||||
while (insertIndex < items.length && !canRecipientBeModified(items[insertIndex].nativeId)) {
|
||||
insertIndex++;
|
||||
}
|
||||
|
||||
@ -243,7 +242,7 @@ export const AddSignersFormPartial = ({
|
||||
|
||||
const updatedSigners = items.map((item, index) => ({
|
||||
...item,
|
||||
signingOrder: hasBeenSentToRecipientId(item.nativeId) ? item.signingOrder : index + 1,
|
||||
signingOrder: !canRecipientBeModified(item.nativeId) ? item.signingOrder : index + 1,
|
||||
}));
|
||||
|
||||
updatedSigners.forEach((item, index) => {
|
||||
@ -270,7 +269,7 @@ export const AddSignersFormPartial = ({
|
||||
|
||||
await form.trigger('signers');
|
||||
},
|
||||
[form, hasBeenSentToRecipientId, watchedSigners],
|
||||
[form, canRecipientBeModified, watchedSigners],
|
||||
);
|
||||
|
||||
const triggerDragAndDrop = useCallback(
|
||||
@ -315,9 +314,19 @@ export const AddSignersFormPartial = ({
|
||||
if (index === oldIndex) {
|
||||
return { ...signer, signingOrder: newIndex + 1 };
|
||||
} else if (index >= newIndex && index < oldIndex) {
|
||||
return { ...signer, signingOrder: (signer.signingOrder ?? index + 1) + 1 };
|
||||
return {
|
||||
...signer,
|
||||
signingOrder: !canRecipientBeModified(signer.nativeId)
|
||||
? signer.signingOrder
|
||||
: (signer.signingOrder ?? index + 1) + 1,
|
||||
};
|
||||
} else if (index <= newIndex && index > oldIndex) {
|
||||
return { ...signer, signingOrder: Math.max(1, (signer.signingOrder ?? index + 1) - 1) };
|
||||
return {
|
||||
...signer,
|
||||
signingOrder: !canRecipientBeModified(signer.nativeId)
|
||||
? signer.signingOrder
|
||||
: Math.max(1, (signer.signingOrder ?? index + 1) - 1),
|
||||
};
|
||||
}
|
||||
return signer;
|
||||
});
|
||||
@ -326,7 +335,7 @@ export const AddSignersFormPartial = ({
|
||||
form.setValue(`signers.${index}.signingOrder`, signer.signingOrder);
|
||||
});
|
||||
},
|
||||
[form],
|
||||
[form, canRecipientBeModified],
|
||||
);
|
||||
|
||||
const handleSigningOrderChange = useCallback(
|
||||
@ -417,7 +426,7 @@ export const AddSignersFormPartial = ({
|
||||
isDragDisabled={
|
||||
!isSigningOrderSequential ||
|
||||
isSubmitting ||
|
||||
hasBeenSentToRecipientId(signer.nativeId) ||
|
||||
!canRecipientBeModified(signer.nativeId) ||
|
||||
!signer.signingOrder
|
||||
}
|
||||
>
|
||||
@ -433,7 +442,7 @@ export const AddSignersFormPartial = ({
|
||||
>
|
||||
<motion.fieldset
|
||||
data-native-id={signer.nativeId}
|
||||
disabled={isSubmitting || hasBeenSentToRecipientId(signer.nativeId)}
|
||||
disabled={isSubmitting || !canRecipientBeModified(signer.nativeId)}
|
||||
className={cn('grid grid-cols-10 items-end gap-2 pb-2', {
|
||||
'border-b pt-2': showAdvancedSettings,
|
||||
'grid-cols-12 pr-3': isSigningOrderSequential,
|
||||
@ -466,7 +475,7 @@ export const AddSignersFormPartial = ({
|
||||
disabled={
|
||||
snapshot.isDragging ||
|
||||
isSubmitting ||
|
||||
hasBeenSentToRecipientId(signer.nativeId)
|
||||
!canRecipientBeModified(signer.nativeId)
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
@ -500,7 +509,7 @@ export const AddSignersFormPartial = ({
|
||||
disabled={
|
||||
snapshot.isDragging ||
|
||||
isSubmitting ||
|
||||
hasBeenSentToRecipientId(signer.nativeId)
|
||||
!canRecipientBeModified(signer.nativeId)
|
||||
}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
@ -534,7 +543,7 @@ export const AddSignersFormPartial = ({
|
||||
disabled={
|
||||
snapshot.isDragging ||
|
||||
isSubmitting ||
|
||||
hasBeenSentToRecipientId(signer.nativeId)
|
||||
!canRecipientBeModified(signer.nativeId)
|
||||
}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
@ -562,7 +571,7 @@ export const AddSignersFormPartial = ({
|
||||
disabled={
|
||||
snapshot.isDragging ||
|
||||
isSubmitting ||
|
||||
hasBeenSentToRecipientId(signer.nativeId)
|
||||
!canRecipientBeModified(signer.nativeId)
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
@ -585,7 +594,7 @@ export const AddSignersFormPartial = ({
|
||||
disabled={
|
||||
snapshot.isDragging ||
|
||||
isSubmitting ||
|
||||
hasBeenSentToRecipientId(signer.nativeId)
|
||||
!canRecipientBeModified(signer.nativeId)
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
@ -601,7 +610,7 @@ export const AddSignersFormPartial = ({
|
||||
disabled={
|
||||
snapshot.isDragging ||
|
||||
isSubmitting ||
|
||||
hasBeenSentToRecipientId(signer.nativeId) ||
|
||||
!canRecipientBeModified(signer.nativeId) ||
|
||||
signers.length === 1
|
||||
}
|
||||
onClick={() => onRemoveSigner(index)}
|
||||
|
||||
@ -210,6 +210,7 @@ export const FieldItem = ({
|
||||
onFocus?.();
|
||||
}}
|
||||
ref={$el}
|
||||
data-field-id={field.nativeId}
|
||||
>
|
||||
{match(field.type)
|
||||
.with('CHECKBOX', () => <CheckboxField field={field} />)
|
||||
|
||||
Reference in New Issue
Block a user