diff --git a/app/api/views-dataroom/route.ts b/app/api/views-dataroom/route.ts index 1e9b20039..35d7de7b2 100644 --- a/app/api/views-dataroom/route.ts +++ b/app/api/views-dataroom/route.ts @@ -15,6 +15,7 @@ import { import { verifyDataroomSession } from "@/lib/auth/dataroom-auth"; import { PreviewSession, verifyPreviewSession } from "@/lib/auth/preview-auth"; import { sendOtpVerificationEmail } from "@/lib/emails/send-email-otp-verification"; +import { sendSignedNDAEmail } from "@/lib/emails/send-signed-nda"; import { getFile } from "@/lib/files/get-file"; import { newId } from "@/lib/id-helper"; import { @@ -29,7 +30,8 @@ import { CustomUser, WatermarkConfigSchema } from "@/lib/types"; import { checkPassword, decryptEncrpytedPassword, log } from "@/lib/utils"; import { extractEmailDomain, isEmailMatched } from "@/lib/utils/email-domain"; import { generateOTP } from "@/lib/utils/generate-otp"; -import { LOCALHOST_IP } from "@/lib/utils/geo"; +import { geolocation } from "@vercel/functions"; +import { LOCALHOST_GEO_DATA, LOCALHOST_IP } from "@/lib/utils/geo"; import { checkGlobalBlockList } from "@/lib/utils/global-block-list"; import { validateEmail } from "@/lib/utils/validate-email"; @@ -132,6 +134,21 @@ export async function POST(request: NextRequest) { select: { plan: true, globalBlockList: true, + users: { + select: { + role: true, + user: { + select: { + email: true, + }, + }, + }, + }, + }, + }, + agreement: { + select: { + name: true, }, }, customFields: { @@ -687,6 +704,63 @@ export async function POST(request: NextRequest) { })(), ); } + + // Send NDA completion email if agreement was signed and notifications are enabled + if ( + hasConfirmedAgreement && + link.enableAgreement && + link.agreementId && + link.enableNotification && + link.agreement + ) { + waitUntil( + (async () => { + try { + // Get location data + const geo = + process.env.VERCEL === "1" + ? geolocation(request) + : LOCALHOST_GEO_DATA; + const locationString = + geo.city && geo.country + ? geo.region && geo.region !== geo.city + ? `${geo.city}, ${geo.region}, ${geo.country}` + : `${geo.city}, ${geo.country}` + : undefined; + + // Get team members for CC + const adminUser = link.team?.users.find( + (u) => u.role === "ADMIN", + ); + const adminEmail = adminUser?.user.email || null; + const teamMembers = link.team?.users + .map((u) => u.user.email) + .filter( + (email): email is string => + !!email && email !== adminEmail, + ) || []; + + // Send NDA completion email + await sendSignedNDAEmail({ + ownerEmail: adminEmail, + viewId: newDataroomView.id, + dataroomId: dataroomId, + agreementName: link.agreement?.name || "NDA", + linkName: link.name || `Link #${linkId.slice(-5)}`, + viewerEmail: email ?? null, + viewerName: name ?? null, + teamMembers: teamMembers.length > 0 ? teamMembers : undefined, + locationString, + }); + } catch (error) { + log({ + message: `Failed to send NDA completion email for dataroom view: ${newDataroomView.id}. \n\n ${error}`, + type: "error", + }); + } + })(), + ); + } } const dataroomViewId = @@ -817,6 +891,64 @@ export async function POST(request: NextRequest) { })(), ); } + + // Send NDA completion email if agreement was signed and notifications are enabled + if ( + newView && + hasConfirmedAgreement && + link.enableAgreement && + link.agreementId && + link.enableNotification && + link.agreement + ) { + waitUntil( + (async () => { + try { + // Get location data + const geo = + process.env.VERCEL === "1" + ? geolocation(request) + : LOCALHOST_GEO_DATA; + const locationString = + geo.city && geo.country + ? geo.region && geo.region !== geo.city + ? `${geo.city}, ${geo.region}, ${geo.country}` + : `${geo.city}, ${geo.country}` + : undefined; + + // Get team members for CC + const adminUser = link.team?.users.find( + (u) => u.role === "ADMIN", + ); + const adminEmail = adminUser?.user.email || null; + const teamMembers = link.team?.users + .map((u) => u.user.email) + .filter( + (email): email is string => + !!email && email !== adminEmail, + ) || []; + + // Send NDA completion email (for document view within dataroom, link to dataroom) + await sendSignedNDAEmail({ + ownerEmail: adminEmail, + viewId: newView.id, + dataroomId: dataroomId, // Link to dataroom even for document views + agreementName: link.agreement?.name || "NDA", + linkName: link.name || `Link #${linkId.slice(-5)}`, + viewerEmail: email ?? null, + viewerName: name ?? null, + teamMembers: teamMembers.length > 0 ? teamMembers : undefined, + locationString, + }); + } catch (error) { + log({ + message: `Failed to send NDA completion email for dataroom document view: ${newView.id}. \n\n ${error}`, + type: "error", + }); + } + })(), + ); + } } // if document version has pages, then return pages diff --git a/app/api/views/route.ts b/app/api/views/route.ts index 9ae89ef4c..be781e1bf 100644 --- a/app/api/views/route.ts +++ b/app/api/views/route.ts @@ -11,6 +11,7 @@ import { hashToken } from "@/lib/api/auth/token"; import { verifyPreviewSession } from "@/lib/auth/preview-auth"; import { PreviewSession } from "@/lib/auth/preview-auth"; import { sendOtpVerificationEmail } from "@/lib/emails/send-email-otp-verification"; +import { sendSignedNDAEmail } from "@/lib/emails/send-signed-nda"; import { getFeatureFlags } from "@/lib/featureFlags"; import { getFile } from "@/lib/files/get-file"; import { newId } from "@/lib/id-helper"; @@ -23,7 +24,8 @@ import { CustomUser, WatermarkConfigSchema } from "@/lib/types"; import { checkPassword, decryptEncrpytedPassword, log } from "@/lib/utils"; import { isEmailMatched } from "@/lib/utils/email-domain"; import { generateOTP } from "@/lib/utils/generate-otp"; -import { LOCALHOST_IP } from "@/lib/utils/geo"; +import { geolocation } from "@vercel/functions"; +import { LOCALHOST_GEO_DATA, LOCALHOST_IP } from "@/lib/utils/geo"; import { checkGlobalBlockList } from "@/lib/utils/global-block-list"; import { validateEmail } from "@/lib/utils/validate-email"; @@ -108,6 +110,21 @@ export async function POST(request: NextRequest) { select: { plan: true, globalBlockList: true, + users: { + select: { + role: true, + user: { + select: { + email: true, + }, + }, + }, + }, + }, + }, + agreement: { + select: { + name: true, }, }, customFields: { @@ -658,6 +675,64 @@ export async function POST(request: NextRequest) { }), ); } + + // Send NDA completion email if agreement was signed and notifications are enabled + if ( + hasConfirmedAgreement && + link.enableAgreement && + link.agreementId && + link.enableNotification && + link.agreement + ) { + waitUntil( + (async () => { + try { + // Get location data + const geo = + process.env.VERCEL === "1" + ? geolocation(request) + : LOCALHOST_GEO_DATA; + const locationString = + geo.city && geo.country + ? geo.region && geo.region !== geo.city + ? `${geo.city}, ${geo.region}, ${geo.country}` + : `${geo.city}, ${geo.country}` + : undefined; + + // Get team members for CC + const adminUser = link.team?.users.find( + (u) => u.role === "ADMIN", + ); + const adminEmail = adminUser?.user.email || null; + const teamMembers = link.team?.users + .map((u) => u.user.email) + .filter( + (email): email is string => + !!email && email !== adminEmail, + ) || []; + + // Send NDA completion email + await sendSignedNDAEmail({ + ownerEmail: adminEmail, + viewId: newView.id, + documentId: documentId, + dataroomId: undefined, // Document view, not dataroom + agreementName: link.agreement?.name || "NDA", + linkName: link.name || `Link #${linkId.slice(-5)}`, + viewerEmail: email ?? null, + viewerName: name ?? null, + teamMembers: teamMembers.length > 0 ? teamMembers : undefined, + locationString, + }); + } catch (error) { + log({ + message: `Failed to send NDA completion email for view: ${newView.id}. \n\n ${error}`, + type: "error", + }); + } + })(), + ); + } } const returnObject = { diff --git a/components/analytics/views-table.tsx b/components/analytics/views-table.tsx index 00309045d..8cf32b3ac 100644 --- a/components/analytics/views-table.tsx +++ b/components/analytics/views-table.tsx @@ -24,6 +24,7 @@ import { Download, DownloadCloudIcon, FileBadgeIcon, + FileTextIcon, ServerIcon, ThumbsDownIcon, ThumbsUpIcon, @@ -101,12 +102,48 @@ const columns: ColumnDef[] = [ )} {row.original.agreementResponse && ( - - - + <> + + + + + + + )} {row.original.downloadedAt && ( + + Someone signed your NDA agreement + + + + + Papermark + + + NDA Agreement Signed + + + {displayName} has just signed + the agreement{" "} + {agreementName} + {locationString ? ( + + {" "} + from {locationString} + + ) : null}{" "} + via the link {linkName}. + + + You can download the NDA completion certificate as proof of + agreement acceptance. + +
+ +
+