feat: add allow document rejection setting to document preferences

Adds an allowDocumentRejection column to organisation settings (default
true), team settings (nullable, inherits from the organisation) and
document meta (default true), with the migration.

Adds the "Allow Document Rejection" field to the organisation and team
document preferences form, wires it through the settings pages, and lists
it in the reset-to-defaults dialog and the admin settings section. Adds a
per-document Yes/No field to the envelope editor settings dialog.

Exposes the field on the document meta create/update schemas and the
document, envelope, template and editor envelope response schemas, and
hides the reject dialog on the V1 signing page when the document
disallows rejection.
This commit is contained in:
Catalin Pit
2026-09-11 09:38:44 +03:00
parent 6a99b40cba
commit d91a3e3828
14 changed files with 147 additions and 1 deletions
@@ -96,6 +96,9 @@ export const DocumentPreferencesResetDialog = ({
<li>
<Trans>Delegate document ownership</Trans>
</li>
<li>
<Trans>Allow document rejection</Trans>
</li>
{showAiFeatures && (
<li>
<Trans>AI features</Trans>
@@ -43,6 +43,7 @@ export type TDocumentPreferencesFormSchema = {
signatureTypes: DocumentSignatureType[];
defaultRecipients: TDefaultRecipients | null;
delegateDocumentOwnership: boolean | null;
allowDocumentRejection: boolean | null;
aiFeaturesEnabled: boolean | null;
};
@@ -57,6 +58,7 @@ type SettingsSubset = Pick<
| 'drawSignatureEnabled'
| 'defaultRecipients'
| 'delegateDocumentOwnership'
| 'allowDocumentRejection'
| 'aiFeaturesEnabled'
>;
@@ -77,6 +79,7 @@ const getDocumentPreferencesFormValues = (settings: SettingsSubset): TDocumentPr
signatureTypes: extractTeamSignatureSettings({ ...settings }),
defaultRecipients: settings.defaultRecipients ? ZDefaultRecipientsSchema.parse(settings.defaultRecipients) : null,
delegateDocumentOwnership: settings.delegateDocumentOwnership,
allowDocumentRejection: settings.allowDocumentRejection,
aiFeaturesEnabled: settings.aiFeaturesEnabled,
};
};
@@ -101,6 +104,7 @@ export const DocumentPreferencesForm = ({ settings, onFormSubmit, canInherit }:
}),
defaultRecipients: ZDefaultRecipientsSchema.nullable(),
delegateDocumentOwnership: z.boolean().nullable(),
allowDocumentRejection: z.boolean().nullable(),
aiFeaturesEnabled: z.boolean().nullable(),
});
@@ -486,6 +490,60 @@ export const DocumentPreferencesForm = ({ settings, onFormSubmit, canInherit }:
)}
/>
<FormField
control={form.control}
name="allowDocumentRejection"
render={({ field }) => (
<InheritableField
className="flex-1"
canInherit={canInherit}
isInherited={field.value === null}
label={<Trans>Allow Document Rejection</Trans>}
testId="allow-document-rejection"
>
<FormControl>
<Select
{...field}
value={field.value === null ? '-1' : field.value.toString()}
onValueChange={(value) =>
field.onChange(value === 'true' ? true : value === 'false' ? false : null)
}
>
<SelectTrigger
className="bg-background text-muted-foreground"
data-testid="allow-document-rejection-trigger"
>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="true">
<Trans>Yes</Trans>
</SelectItem>
<SelectItem value="false">
<Trans>No</Trans>
</SelectItem>
{canInherit && (
<SelectItem value={'-1'}>
<Trans>Inherit from organisation</Trans>
</SelectItem>
)}
</SelectContent>
</Select>
</FormControl>
<FormDescription>
<Trans>
Controls whether recipients can reject newly created documents from the signing page. Existing
documents keep their current setting. You can change it per document in the document settings.
</Trans>
</FormDescription>
</InheritableField>
)}
/>
{isAiFeaturesConfigured && (
<FormField
control={form.control}
@@ -138,6 +138,12 @@ export const AdminGlobalSettingsSection = ({
</DetailsValue>
</DetailsCard>
<DetailsCard label={<Trans>Allow document rejection</Trans>}>
<DetailsValue>
{booleanValue(settings.allowDocumentRejection, inheritedSettings?.allowDocumentRejection)}
</DetailsValue>
</DetailsCard>
<DetailsCard label={<Trans>Typed signature</Trans>}>
<DetailsValue>
{booleanValue(settings.typedSignatureEnabled, inheritedSettings?.typedSignatureEnabled)}
@@ -261,7 +261,9 @@ export const DocumentSigningPageViewV1 = ({
<div className="flex items-center gap-x-4">
<DocumentSigningAttachmentsPopover envelopeId={document.envelopeId} token={recipient.token} />
<DocumentSigningRejectDialog documentId={document.id} token={recipient.token} />
{documentMeta.allowDocumentRejection && (
<DocumentSigningRejectDialog documentId={document.id} token={recipient.token} />
)}
</div>
</div>
@@ -110,6 +110,7 @@ export const ZAddSettingsFormSchema = z.object({
signatureTypes: z.array(z.nativeEnum(DocumentSignatureType)).min(1, {
message: msg`At least one signature type must be enabled`.id,
}),
allowDocumentRejection: z.boolean(),
envelopeExpirationPeriod: ZEnvelopeExpirationPeriod.nullish(),
reminderSettings: ZEnvelopeReminderSettings.nullish(),
}),
@@ -200,6 +201,7 @@ export const EnvelopeEditorSettingsDialog = ({ trigger, ...props }: EnvelopeEdit
emailReplyTo: envelope.documentMeta.emailReplyTo ?? undefined,
emailSettings: ZDocumentEmailSettingsSchema.parse(envelope.documentMeta.emailSettings),
signatureTypes: extractTeamSignatureSettings(envelope.documentMeta),
allowDocumentRejection: envelope.documentMeta.allowDocumentRejection,
envelopeExpirationPeriod: envelope.documentMeta?.envelopeExpirationPeriod ?? null,
reminderSettings: envelope.documentMeta?.reminderSettings ?? null,
},
@@ -249,6 +251,7 @@ export const EnvelopeEditorSettingsDialog = ({ trigger, ...props }: EnvelopeEdit
message,
subject,
emailReplyTo,
allowDocumentRejection,
envelopeExpirationPeriod,
reminderSettings,
} = data.meta;
@@ -278,6 +281,7 @@ export const EnvelopeEditorSettingsDialog = ({ trigger, ...props }: EnvelopeEdit
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
typedSignatureEnabled: signatureTypes.includes(DocumentSignatureType.TYPE),
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
allowDocumentRejection,
envelopeExpirationPeriod,
reminderSettings,
},
@@ -465,6 +469,52 @@ export const EnvelopeEditorSettingsDialog = ({ trigger, ...props }: EnvelopeEdit
/>
)}
<FormField
control={form.control}
name="meta.allowDocumentRejection"
render={({ field }) => (
<FormItem>
<FormLabel className="flex flex-row items-center">
<Trans>Allow Document Rejection</Trans>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="max-w-xs text-muted-foreground">
<Trans>
Controls whether recipients can reject the document from the signing page.
</Trans>
</TooltipContent>
</Tooltip>
</FormLabel>
<FormControl>
<Select
value={field.value ? 'true' : 'false'}
onValueChange={(value) => field.onChange(value === 'true')}
>
<SelectTrigger className="bg-background" data-testid="allow-document-rejection-trigger">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="true">
<Trans>Yes</Trans>
</SelectItem>
<SelectItem value="false">
<Trans>No</Trans>
</SelectItem>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{settings.allowConfigureDateFormat && (
<FormField
control={form.control}
@@ -39,6 +39,7 @@ export default function OrganisationSettingsDocumentPage() {
signatureTypes,
defaultRecipients,
delegateDocumentOwnership,
allowDocumentRejection,
aiFeaturesEnabled,
} = data;
@@ -46,6 +47,7 @@ export default function OrganisationSettingsDocumentPage() {
documentVisibility === null ||
documentLanguage === null ||
documentDateFormat === null ||
allowDocumentRejection === null ||
aiFeaturesEnabled === null
) {
throw new Error('Should not be possible.');
@@ -63,6 +65,7 @@ export default function OrganisationSettingsDocumentPage() {
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
delegateDocumentOwnership,
allowDocumentRejection,
aiFeaturesEnabled,
},
});
@@ -33,6 +33,7 @@ export default function TeamsSettingsPage() {
signatureTypes,
defaultRecipients,
delegateDocumentOwnership,
allowDocumentRejection,
aiFeaturesEnabled,
} = data;
@@ -57,6 +58,7 @@ export default function TeamsSettingsPage() {
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
}),
delegateDocumentOwnership,
allowDocumentRejection,
},
});
+6
View File
@@ -28,6 +28,7 @@ export const ZDocumentMetaSchema = DocumentMetaSchema.pick({
typedSignatureEnabled: true,
uploadSignatureEnabled: true,
drawSignatureEnabled: true,
allowDocumentRejection: true,
language: true,
emailSettings: true,
});
@@ -105,6 +106,10 @@ export const ZDocumentMetaUploadSignatureEnabledSchema = z
.boolean()
.describe('Whether to allow recipients to sign using an uploaded signature.');
export const ZDocumentMetaAllowDocumentRejectionSchema = z
.boolean()
.describe('Whether to allow recipients to reject the document from the signing page.');
/**
* Note: Any updates to this will cause public API changes. You will need to update
* all corresponding areas where this is used (some places that use this needs to pass
@@ -123,6 +128,7 @@ export const ZDocumentMetaCreateSchema = z.object({
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
allowDocumentRejection: ZDocumentMetaAllowDocumentRejectionSchema.optional(),
emailId: z.string().nullish(),
emailReplyTo: zEmail().nullish(),
emailSettings: ZDocumentEmailSettingsSchema.nullish(),
+1
View File
@@ -63,6 +63,7 @@ export const ZDocumentSchema = LegacyDocumentSchema.pick({
uploadSignatureEnabled: true,
drawSignatureEnabled: true,
allowDictateNextSigner: true,
allowDocumentRejection: true,
language: true,
emailSettings: true,
emailId: true,
+1
View File
@@ -280,6 +280,7 @@ export const ZEditorEnvelopeSchema = EnvelopeSchema.pick({
uploadSignatureEnabled: true,
drawSignatureEnabled: true,
allowDictateNextSigner: true,
allowDocumentRejection: true,
language: true,
emailSettings: true,
emailId: true,
+1
View File
@@ -50,6 +50,7 @@ export const ZEnvelopeSchema = EnvelopeSchema.pick({
uploadSignatureEnabled: true,
drawSignatureEnabled: true,
allowDictateNextSigner: true,
allowDocumentRejection: true,
language: true,
emailSettings: true,
emailId: true,
+1
View File
@@ -55,6 +55,7 @@ export const ZTemplateSchema = TemplateSchema.pick({
uploadSignatureEnabled: true,
drawSignatureEnabled: true,
allowDictateNextSigner: true,
allowDocumentRejection: true,
distributionMethod: true,
redirectUrl: true,
language: true,
@@ -0,0 +1,8 @@
-- AlterTable
ALTER TABLE "DocumentMeta" ADD COLUMN "allowDocumentRejection" BOOLEAN NOT NULL DEFAULT true;
-- AlterTable
ALTER TABLE "OrganisationGlobalSettings" ADD COLUMN "allowDocumentRejection" BOOLEAN NOT NULL DEFAULT true;
-- AlterTable
ALTER TABLE "TeamGlobalSettings" ADD COLUMN "allowDocumentRejection" BOOLEAN;
+4
View File
@@ -583,6 +583,8 @@ model DocumentMeta {
reminderSettings Json? /// [EnvelopeReminderSettings] @zod.custom.use(ZEnvelopeReminderSettingsSchema)
allowDocumentRejection Boolean @default(true)
envelope Envelope?
}
@@ -966,6 +968,7 @@ model OrganisationGlobalSettings {
documentTimezone String? // Nullable to allow using local timezones if not set.
documentDateFormat String @default("yyyy-MM-dd hh:mm a")
delegateDocumentOwnership Boolean @default(false)
allowDocumentRejection Boolean @default(true)
typedSignatureEnabled Boolean @default(true)
uploadSignatureEnabled Boolean @default(true)
@@ -1005,6 +1008,7 @@ model TeamGlobalSettings {
documentTimezone String?
documentDateFormat String?
delegateDocumentOwnership Boolean?
allowDocumentRejection Boolean?
includeSenderDetails Boolean?
includeSigningCertificate Boolean?