From 9fc5ec11f4e624a537990a28e86ce11d47ce8f5f Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Wed, 20 Nov 2024 22:10:09 +0000 Subject: [PATCH] feat: add reminder to advanced settings --- .../documents/[id]/edit-document.tsx | 3 +- packages/lib/jobs/client.ts | 2 ++ .../lib/jobs/definitions/cron/test-cron.ts | 28 +++++++++++++++ .../document-meta/upsert-document-meta.ts | 6 +++- .../document/complete-document-with-token.ts | 2 +- .../server-only/document/send-document.tsx | 12 ++++++- .../migration.sql | 2 ++ packages/prisma/schema.prisma | 1 + .../trpc/server/document-router/router.ts | 13 +++++-- .../trpc/server/document-router/schema.ts | 2 ++ .../primitives/document-flow/add-settings.tsx | 36 +++++++++++++++++++ .../document-flow/add-settings.types.ts | 1 + 12 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 packages/lib/jobs/definitions/cron/test-cron.ts create mode 100644 packages/prisma/migrations/20241120202053_add_reminder_days_to_document_meta/migration.sql diff --git a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx index 3030794ba..15f064fea 100644 --- a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx +++ b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx @@ -203,7 +203,7 @@ export const EditDocumentForm = ({ const onAddSettingsFormSubmit = async (data: TAddSettingsFormSchema) => { try { - const { timezone, dateFormat, redirectUrl, language } = data.meta; + const { timezone, dateFormat, redirectUrl, language, reminderDays } = data.meta; await setSettingsForDocument({ documentId: document.id, @@ -220,6 +220,7 @@ export const EditDocumentForm = ({ dateFormat, redirectUrl, language: isValidLanguageCode(language) ? language : undefined, + reminderDays, }, }); diff --git a/packages/lib/jobs/client.ts b/packages/lib/jobs/client.ts index 366c2f517..91e1bba83 100644 --- a/packages/lib/jobs/client.ts +++ b/packages/lib/jobs/client.ts @@ -1,4 +1,5 @@ import { JobClient } from './client/client'; +import { TEST_CRON_JOB_DEFINITION } from './definitions/cron/test-cron'; import { SEND_CONFIRMATION_EMAIL_JOB_DEFINITION } from './definitions/emails/send-confirmation-email'; import { SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION } from './definitions/emails/send-rejection-emails'; import { SEND_SIGNING_EMAIL_JOB_DEFINITION } from './definitions/emails/send-signing-email'; @@ -19,6 +20,7 @@ export const jobsClient = new JobClient([ SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION, SEAL_DOCUMENT_JOB_DEFINITION, SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION, + TEST_CRON_JOB_DEFINITION, ] as const); export const jobs = jobsClient; diff --git a/packages/lib/jobs/definitions/cron/test-cron.ts b/packages/lib/jobs/definitions/cron/test-cron.ts new file mode 100644 index 000000000..1f4c81dca --- /dev/null +++ b/packages/lib/jobs/definitions/cron/test-cron.ts @@ -0,0 +1,28 @@ +import { prisma } from '@documenso/prisma'; + +import type { JobDefinition } from '../../client/_internal/job'; + +const TEST_CRON_JOB_DEFINITION_ID = 'test.cron'; + +export const TEST_CRON_JOB_DEFINITION = { + id: TEST_CRON_JOB_DEFINITION_ID, + name: 'Test Cron Job', + version: '1.0.0', + trigger: { + type: 'cron', + schedule: '* * * * *', + }, + handler: async ({ io }) => { + // send a mail to all recipients of all documents + const documents = await prisma.document.findMany({}); + + console.log(`Found ${documents.length} unsigned documents`); + + for (const document of documents) { + // eslint-disable-next-line @typescript-eslint/require-await + await io.runTask(`send-reminder-${document.id}-${document.id}`, async () => { + console.log(`Sent reminder for document ${document.id} to recipient ${document.id}`); + }); + } + }, +} as const satisfies JobDefinition; diff --git a/packages/lib/server-only/document-meta/upsert-document-meta.ts b/packages/lib/server-only/document-meta/upsert-document-meta.ts index c6f4fd7a3..22283ca2d 100644 --- a/packages/lib/server-only/document-meta/upsert-document-meta.ts +++ b/packages/lib/server-only/document-meta/upsert-document-meta.ts @@ -24,6 +24,7 @@ export type CreateDocumentMetaOptions = { signingOrder?: DocumentSigningOrder; distributionMethod?: DocumentDistributionMethod; typedSignatureEnabled?: boolean; + reminderDays?: number; language?: SupportedLanguageCodes; userId: number; requestMetadata: RequestMetadata; @@ -42,6 +43,7 @@ export const upsertDocumentMeta = async ({ emailSettings, distributionMethod, typedSignatureEnabled, + reminderDays, language, requestMetadata, }: CreateDocumentMetaOptions) => { @@ -96,6 +98,7 @@ export const upsertDocumentMeta = async ({ emailSettings, distributionMethod, typedSignatureEnabled, + reminderDays, language, }, update: { @@ -109,6 +112,7 @@ export const upsertDocumentMeta = async ({ emailSettings, distributionMethod, typedSignatureEnabled, + reminderDays, language, }, }); @@ -123,7 +127,7 @@ export const upsertDocumentMeta = async ({ user, requestMetadata, data: { - changes: diffDocumentMetaChanges(originalDocumentMeta ?? {}, upsertedDocumentMeta), + changes, }, }), }); diff --git a/packages/lib/server-only/document/complete-document-with-token.ts b/packages/lib/server-only/document/complete-document-with-token.ts index 342ec5d56..81655b965 100644 --- a/packages/lib/server-only/document/complete-document-with-token.ts +++ b/packages/lib/server-only/document/complete-document-with-token.ts @@ -8,8 +8,8 @@ import { RecipientRole, SendStatus, SigningStatus, + WebhookTriggerEvents, } from '@documenso/prisma/client'; -import { WebhookTriggerEvents } from '@documenso/prisma/client'; import { jobs } from '../../jobs/client'; import type { TRecipientActionAuth } from '../../types/document-auth'; diff --git a/packages/lib/server-only/document/send-document.tsx b/packages/lib/server-only/document/send-document.tsx index 7266963b7..89f328d4d 100644 --- a/packages/lib/server-only/document/send-document.tsx +++ b/packages/lib/server-only/document/send-document.tsx @@ -9,8 +9,8 @@ import { RecipientRole, SendStatus, SigningStatus, + WebhookTriggerEvents, } from '@documenso/prisma/client'; -import { WebhookTriggerEvents } from '@documenso/prisma/client'; import { jobs } from '../../jobs/client'; import { extractDerivedDocumentEmailSettings } from '../../types/document-email'; @@ -180,6 +180,16 @@ export const sendDocument = async ({ requestMetadata, }, }); + + // TODO: Duncan: Audit Log + await jobs.triggerJob({ + name: 'send.signing.reminder.email', + payload: { + documentId, + recipientId: recipient.id, + initialDelay: 24 * 60 * 60 * 1000, // 24 hours in milliseconds + }, + }); }), ); } diff --git a/packages/prisma/migrations/20241120202053_add_reminder_days_to_document_meta/migration.sql b/packages/prisma/migrations/20241120202053_add_reminder_days_to_document_meta/migration.sql new file mode 100644 index 000000000..5f3d5a5df --- /dev/null +++ b/packages/prisma/migrations/20241120202053_add_reminder_days_to_document_meta/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "DocumentMeta" ADD COLUMN "reminderDays" INTEGER DEFAULT 0; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 1fee5eae9..73d87c5fa 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -378,6 +378,7 @@ model DocumentMeta { language String @default("en") distributionMethod DocumentDistributionMethod @default(EMAIL) emailSettings Json? + reminderDays Int? @default(0) } enum ReadStatus { diff --git a/packages/trpc/server/document-router/router.ts b/packages/trpc/server/document-router/router.ts index 14e8c8eb7..7a456248f 100644 --- a/packages/trpc/server/document-router/router.ts +++ b/packages/trpc/server/document-router/router.ts @@ -259,13 +259,20 @@ export const documentRouter = router({ const requestMetadata = extractNextApiRequestMetadata(ctx.req); - if (meta.timezone || meta.dateFormat || meta.redirectUrl) { + if ( + meta.timezone || + meta.dateFormat || + meta.redirectUrl || + meta.language || + meta.reminderDays + ) { await upsertDocumentMeta({ documentId, dateFormat: meta.dateFormat, timezone: meta.timezone, redirectUrl: meta.redirectUrl, language: meta.language, + reminderDays: meta.reminderDays, userId: ctx.user.id, requestMetadata, }); @@ -420,7 +427,8 @@ export const documentRouter = router({ meta.dateFormat || meta.redirectUrl || meta.distributionMethod || - meta.emailSettings + meta.emailSettings || + meta.reminderDays ) { await upsertDocumentMeta({ documentId, @@ -430,6 +438,7 @@ export const documentRouter = router({ timezone: meta.timezone, redirectUrl: meta.redirectUrl, distributionMethod: meta.distributionMethod, + reminderDays: meta.reminderDays, userId: ctx.user.id, emailSettings: meta.emailSettings, requestMetadata: extractNextApiRequestMetadata(ctx.req), diff --git a/packages/trpc/server/document-router/schema.ts b/packages/trpc/server/document-router/schema.ts index c56c02259..73ce05c75 100644 --- a/packages/trpc/server/document-router/schema.ts +++ b/packages/trpc/server/document-router/schema.ts @@ -98,6 +98,7 @@ export const ZSetSettingsForDocumentMutationSchema = z.object({ 'Please enter a valid URL, make sure you include http:// or https:// part of the url.', }), language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(), + reminderDays: z.number().optional(), }), }); @@ -167,6 +168,7 @@ export const ZSendDocumentMutationSchema = z.object({ 'Please enter a valid URL, make sure you include http:// or https:// part of the url.', }), emailSettings: ZDocumentEmailSettingsSchema.optional(), + reminderDays: z.number().optional(), }), }); diff --git a/packages/ui/primitives/document-flow/add-settings.tsx b/packages/ui/primitives/document-flow/add-settings.tsx index 9b73336f5..2afb053b7 100644 --- a/packages/ui/primitives/document-flow/add-settings.tsx +++ b/packages/ui/primitives/document-flow/add-settings.tsx @@ -100,6 +100,7 @@ export const AddSettingsFormPartial = ({ ?.value ?? DEFAULT_DOCUMENT_DATE_FORMAT, redirectUrl: document.documentMeta?.redirectUrl ?? '', language: document.documentMeta?.language ?? 'en', + reminderDays: document.documentMeta?.reminderDays ?? 0, }, }, }); @@ -390,6 +391,41 @@ export const AddSettingsFormPartial = ({ )} /> + + ( + + + Reminder{' '} + + + + + + + + Set the number of days between reminders for this document. + + + + + + + field.onChange(parseInt(e.target.value, 10))} + /> + + + + + )} + /> diff --git a/packages/ui/primitives/document-flow/add-settings.types.ts b/packages/ui/primitives/document-flow/add-settings.types.ts index dcdd98a72..66b690188 100644 --- a/packages/ui/primitives/document-flow/add-settings.types.ts +++ b/packages/ui/primitives/document-flow/add-settings.types.ts @@ -45,6 +45,7 @@ export const ZAddSettingsFormSchema = z.object({ .union([z.string(), z.enum(SUPPORTED_LANGUAGE_CODES)]) .optional() .default('en'), + reminderDays: z.number().optional(), }), });