From 2d7988f484a790a5d055d5ae498dbd2fb7e1417a Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Sun, 17 Nov 2024 22:57:40 +0000 Subject: [PATCH] feat: update recipient expiry handling --- .../_action-items/resend-document.tsx | 2 + .../(dashboard)/avatar/stack-avatar.tsx | 4 +- .../document/complete-document-with-token.ts | 11 ++++ .../server-only/document/resend-document.tsx | 19 ++++++- .../document/update-expired-recipients.ts | 57 +++++++++++++++++++ .../document-flow/document-expiry-dialog.tsx | 2 +- 6 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 packages/lib/server-only/document/update-expired-recipients.ts diff --git a/apps/web/src/app/(dashboard)/documents/_action-items/resend-document.tsx b/apps/web/src/app/(dashboard)/documents/_action-items/resend-document.tsx index f24c38c72..2a9e8ffa3 100644 --- a/apps/web/src/app/(dashboard)/documents/_action-items/resend-document.tsx +++ b/apps/web/src/app/(dashboard)/documents/_action-items/resend-document.tsx @@ -101,6 +101,8 @@ export const ResendDocumentActionItem = ({ setIsOpen(false); } catch (err) { + console.log(err); + toast({ title: _(msg`Something went wrong`), description: _(msg`This document could not be re-sent at this time. Please try again.`), diff --git a/apps/web/src/components/(dashboard)/avatar/stack-avatar.tsx b/apps/web/src/components/(dashboard)/avatar/stack-avatar.tsx index 7d19d9027..f514292a6 100644 --- a/apps/web/src/components/(dashboard)/avatar/stack-avatar.tsx +++ b/apps/web/src/components/(dashboard)/avatar/stack-avatar.tsx @@ -41,10 +41,8 @@ export const StackAvatar = ({ first, zIndex, fallbackText = '', type }: StackAva classes = 'bg-documenso-200 text-documenso-800'; break; case RecipientStatusType.REJECTED: - classes = 'bg-red-200 text-red-800'; - break; case RecipientStatusType.EXPIRED: - classes = 'bg-gray-200 text-gray-700'; + classes = 'bg-red-200 text-red-800'; break; default: 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 81655b965..bae404fc7 100644 --- a/packages/lib/server-only/document/complete-document-with-token.ts +++ b/packages/lib/server-only/document/complete-document-with-token.ts @@ -16,6 +16,7 @@ import type { TRecipientActionAuth } from '../../types/document-auth'; import { getIsRecipientsTurnToSign } from '../recipient/get-is-recipient-turn'; import { triggerWebhook } from '../webhooks/trigger/trigger-webhook'; import { sendPendingEmail } from './send-pending-email'; +import { updateExpiredRecipients } from './update-expired-recipients'; export type CompleteDocumentWithTokenOptions = { token: string; @@ -61,12 +62,22 @@ export const completeDocumentWithToken = async ({ throw new Error(`Document ${document.id} has no recipient with token ${token}`); } + await updateExpiredRecipients(documentId); + const [recipient] = document.Recipient; + if (recipient.expired && recipient.expired < new Date()) { + throw new Error(`Recipient ${recipient.id} signature period has expired`); + } + if (recipient.signingStatus === SigningStatus.SIGNED) { throw new Error(`Recipient ${recipient.id} has already signed`); } + if (recipient.signingStatus === SigningStatus.EXPIRED) { + throw new Error(`Recipient ${recipient.id} signature period has expired`); + } + if (document.documentMeta?.signingOrder === DocumentSigningOrder.SEQUENTIAL) { const isRecipientsTurn = await getIsRecipientsTurnToSign({ token: recipient.token }); diff --git a/packages/lib/server-only/document/resend-document.tsx b/packages/lib/server-only/document/resend-document.tsx index d87be344d..1843a3d68 100644 --- a/packages/lib/server-only/document/resend-document.tsx +++ b/packages/lib/server-only/document/resend-document.tsx @@ -14,8 +14,8 @@ import type { RequestMetadata } from '@documenso/lib/universal/extract-request-m import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs'; import { renderCustomEmailTemplate } from '@documenso/lib/utils/render-custom-email-template'; import { prisma } from '@documenso/prisma'; -import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/client'; import type { Prisma } from '@documenso/prisma/client'; +import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/client'; import { getI18nInstance } from '../../client-only/providers/i18n.server'; import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app'; @@ -106,6 +106,23 @@ export const resendDocument = async ({ return; } + let newExpiryDate: Date | null = null; + if (recipient.expired) { + const durationInMs = recipient.expired.getTime() - document.updatedAt.getTime(); + newExpiryDate = new Date(Date.now() + durationInMs); + + await prisma.recipient.update({ + where: { id: recipient.id }, + data: { + expired: newExpiryDate, + signingStatus: + recipient.signingStatus === SigningStatus.EXPIRED + ? SigningStatus.NOT_SIGNED + : recipient.signingStatus, + }, + }); + } + const i18n = await getI18nInstance(document.documentMeta?.language); const recipientEmailType = RECIPIENT_ROLE_TO_EMAIL_TYPE[recipient.role]; diff --git a/packages/lib/server-only/document/update-expired-recipients.ts b/packages/lib/server-only/document/update-expired-recipients.ts new file mode 100644 index 000000000..85543e13a --- /dev/null +++ b/packages/lib/server-only/document/update-expired-recipients.ts @@ -0,0 +1,57 @@ +import { prisma } from '@documenso/prisma'; +import { SigningStatus } from '@documenso/prisma/client'; + +import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs'; +import { createDocumentAuditLogData } from '../../utils/document-audit-logs'; + +export const updateExpiredRecipients = async (documentId: number) => { + const now = new Date(); + + const expiredRecipients = await prisma.recipient.findMany({ + where: { + documentId, + expired: { + lt: now, + }, + signingStatus: { + not: SigningStatus.EXPIRED, + }, + }, + }); + + if (expiredRecipients.length > 0) { + await prisma.recipient.updateMany({ + where: { + id: { + in: expiredRecipients.map((recipient) => recipient.id), + }, + }, + data: { + signingStatus: SigningStatus.EXPIRED, + }, + }); + + await Promise.all( + expiredRecipients.map(async (recipient) => + prisma.documentAuditLog.create({ + data: createDocumentAuditLogData({ + type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_EXPIRED, + documentId, + user: { + name: recipient.name, + email: recipient.email, + }, + data: { + recipientEmail: recipient.email, + recipientName: recipient.name, + recipientId: recipient.id, + recipientRole: recipient.role, + }, + }), + }), + ), + ); + } + + return expiredRecipients; +}; diff --git a/packages/ui/primitives/document-flow/document-expiry-dialog.tsx b/packages/ui/primitives/document-flow/document-expiry-dialog.tsx index 326ceabaf..91c17a2ab 100644 --- a/packages/ui/primitives/document-flow/document-expiry-dialog.tsx +++ b/packages/ui/primitives/document-flow/document-expiry-dialog.tsx @@ -184,7 +184,7 @@ export function DocumentExpiryDialog({ - Set Document Expiry + Set Recipient Expiry Set the expiry date for the document signing recipient. The recipient will not be able to sign the document after this date.