feat: signing reminders (#1749)

This commit is contained in:
Ephraim Duncan
2026-04-14 11:01:53 +00:00
committed by GitHub
parent 6d7bd212bf
commit 4935f387bf
69 changed files with 1426 additions and 156 deletions
+3
View File
@@ -66,6 +66,9 @@ export const extractDerivedDocumentMeta = (
// Envelope expiration.
envelopeExpirationPeriod:
meta.envelopeExpirationPeriod ?? settings.envelopeExpirationPeriod ?? null,
// Reminder settings.
reminderSettings: meta.reminderSettings ?? settings.reminderSettings ?? null,
} satisfies Omit<DocumentMeta, 'id'>;
};
+3
View File
@@ -67,6 +67,9 @@ export const buildEmbeddedFeatures = (
allowConfigureExpirationPeriod:
features.settings?.allowConfigureExpirationPeriod ??
DEFAULT_EMBEDDED_EDITOR_CONFIG.settings.allowConfigureExpirationPeriod,
allowConfigureReminders:
features.settings?.allowConfigureReminders ??
DEFAULT_EMBEDDED_EDITOR_CONFIG.settings.allowConfigureReminders,
allowConfigureEmailSender:
features.settings?.allowConfigureEmailSender ??
DEFAULT_EMBEDDED_EDITOR_CONFIG.settings.allowConfigureEmailSender,
+1 -1
View File
@@ -252,7 +252,7 @@ export type EnvelopeItemPermissions = {
export const getEnvelopeItemPermissions = (
envelope: Pick<Envelope, 'completedAt' | 'deletedAt' | 'type' | 'status'>,
recipients: Recipient[],
recipients: Pick<Recipient, 'role' | 'signingStatus' | 'sendStatus'>[],
): EnvelopeItemPermissions => {
// Always reject completed/rejected/deleted envelopes.
if (
+3
View File
@@ -9,6 +9,7 @@ import type { ORGANISATION_MEMBER_ROLE_MAP } from '@documenso/lib/constants/orga
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '../constants/date-formats';
import { DEFAULT_ENVELOPE_EXPIRATION_PERIOD } from '../constants/envelope-expiration';
import { DEFAULT_ENVELOPE_REMINDER_SETTINGS } from '../constants/envelope-reminder';
import {
LOWEST_ORGANISATION_ROLE,
ORGANISATION_MEMBER_ROLE_HIERARCHY,
@@ -142,6 +143,8 @@ export const generateDefaultOrganisationSettings = (): Omit<
envelopeExpirationPeriod: DEFAULT_ENVELOPE_EXPIRATION_PERIOD,
reminderSettings: DEFAULT_ENVELOPE_REMINDER_SETTINGS,
aiFeaturesEnabled: false,
};
};
+14 -6
View File
@@ -1,10 +1,11 @@
import type { Envelope } from '@prisma/client';
import { type Field, type Recipient, RecipientRole, SigningStatus } from '@prisma/client';
import { type Field, RecipientRole, SigningStatus } from '@prisma/client';
import { isSignatureFieldType } from '@documenso/prisma/guards/is-signature-field';
import { NEXT_PUBLIC_WEBAPP_URL } from '../constants/app';
import { AppError, AppErrorCode } from '../errors/app-error';
import type { TRecipientLite } from '../types/recipient';
import { extractLegacyIds } from '../universal/id';
import { zEmail } from './zod';
@@ -20,7 +21,7 @@ export const RECIPIENT_ROLES_THAT_REQUIRE_FIELDS = [RecipientRole.SIGNER] as con
*
* Currently only SIGNERs are validated - they must have at least one signature field.
*/
export const getRecipientsWithMissingFields = <T extends Pick<Recipient, 'id' | 'role'>>(
export const getRecipientsWithMissingFields = <T extends Pick<TRecipientLite, 'id' | 'role'>>(
recipients: T[],
fields: Pick<Field, 'type' | 'recipientId'>[],
): T[] => {
@@ -42,7 +43,10 @@ export const formatSigningLink = (token: string) => `${NEXT_PUBLIC_WEBAPP_URL()}
/**
* Whether a recipient can be modified by the document owner.
*/
export const canRecipientBeModified = (recipient: Recipient, fields: Field[]) => {
export const canRecipientBeModified = (
recipient: TRecipientLite,
fields: Pick<Field, 'recipientId' | 'inserted'>[],
) => {
if (!recipient) {
return false;
}
@@ -72,7 +76,10 @@ export const canRecipientBeModified = (recipient: Recipient, fields: Field[]) =>
* - They are not a Viewer or CCer
* - They can be modified (canRecipientBeModified)
*/
export const canRecipientFieldsBeModified = (recipient: Recipient, fields: Field[]) => {
export const canRecipientFieldsBeModified = (
recipient: TRecipientLite,
fields: Pick<Field, 'recipientId' | 'inserted'>[],
) => {
if (!canRecipientBeModified(recipient, fields)) {
return false;
}
@@ -81,7 +88,7 @@ export const canRecipientFieldsBeModified = (recipient: Recipient, fields: Field
};
export const mapRecipientToLegacyRecipient = (
recipient: Recipient,
recipient: TRecipientLite,
envelope: Pick<Envelope, 'type' | 'secondaryId'>,
) => {
const legacyId = extractLegacyIds(envelope);
@@ -92,6 +99,7 @@ export const mapRecipientToLegacyRecipient = (
};
};
export const findRecipientByEmail = <T extends { email: string }>({
recipients,
userEmail,
@@ -102,7 +110,7 @@ export const findRecipientByEmail = <T extends { email: string }>({
teamEmail?: string | null;
}) => recipients.find((r) => r.email === userEmail || (teamEmail && r.email === teamEmail));
export const isRecipientEmailValidForSending = (recipient: Pick<Recipient, 'email'>) => {
export const isRecipientEmailValidForSending = (recipient: Pick<TRecipientLite, 'email'>) => {
return zEmail().safeParse(recipient.email).success;
};
+2
View File
@@ -208,6 +208,8 @@ export const generateDefaultTeamSettings = (): Omit<TeamGlobalSettings, 'id' | '
envelopeExpirationPeriod: null,
reminderSettings: null,
aiFeaturesEnabled: null,
};
};