-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Nda certificate #1943
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Nda certificate #1943
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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", | ||
| }); | ||
| } | ||
| })(), | ||
| ); | ||
| } | ||
|
Comment on lines
+679
to
+735
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major 🧩 Analysis chain🏁 Script executed: # Find the sendSignedNDAEmail function definition to verify its return type
rg -n "sendSignedNDAEmail\s*=" -A 15 --type=tsRepository: mfts/papermark Length of output: 864 🏁 Script executed: # Search for all usages of sendSignedNDAEmail to understand error handling patterns
rg -n "sendSignedNDAEmail" -B 3 -A 5 --type=tsRepository: mfts/papermark Length of output: 4161 🏁 Script executed: # Check how agreementResponse is used/stored in the route
rg -n "agreementResponse" -B 3 -A 3 --type=tsRepository: mfts/papermark Length of output: 14233 🏁 Script executed: # Read the full sendSignedNDAEmail implementation
cat lib/emails/send-signed-nda.tsRepository: mfts/papermark Length of output: 1438 🏁 Script executed: # Check the sendEmail function to understand error handling
rg -n "export const sendEmail" -A 20 --type=tsRepository: mfts/papermark Length of output: 4414 🏁 Script executed: # Look at the context around the NDA email sending in app/api/views/route.ts
sed -n '500,740p' app/api/views/route.ts | head -250Repository: mfts/papermark Length of output: 8081 Perfect. I've now verified all the claims in the review comment. Let me confirm the findings: Verification Results:
Both suggestions are valid, actionable, and practical. The review comment is accurate. NDA completion email block is solid; capture email send failures and consider de‑duplication The NDA completion email logic (agreement + notifications gate, geo enrichment, admin + CC derivation, and
🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| const returnObject = { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -24,6 +24,7 @@ import { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Download, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DownloadCloudIcon, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FileBadgeIcon, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FileTextIcon, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ServerIcon, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ThumbsDownIcon, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ThumbsUpIcon, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -101,12 +102,48 @@ const columns: ColumnDef<View>[] = [ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </BadgeTooltip> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {row.original.agreementResponse && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <BadgeTooltip | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| content={`Agreed to ${row.original.agreementResponse.agreement.name}`} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key="agreement" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <FileBadgeIcon className="h-4 w-4 text-emerald-500 hover:text-emerald-600" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </BadgeTooltip> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <BadgeTooltip | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| content={`Agreed to ${row.original.agreementResponse.agreement.name}`} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key="agreement" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <FileBadgeIcon className="h-4 w-4 text-emerald-500 hover:text-emerald-600" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </BadgeTooltip> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <BadgeTooltip | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| content="Download NDA Certificate" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key="certificate" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={async (e) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| e.stopPropagation(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await fetch( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `/api/views/${row.original.id}/nda-certificate`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!response.ok) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Failed to generate certificate", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const blob = await response.blob(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const url = window.URL.createObjectURL(blob); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const a = document.createElement("a"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| a.href = url; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| a.download = `NDA-Certificate-${row.original.viewerEmail || "Anonymous"}-${Date.now()}.pdf`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| document.body.appendChild(a); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| a.click(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| window.URL.revokeObjectURL(url); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| document.body.removeChild(a); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| toast.error("Failed to download certificate"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+116
to
+139
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inconsistent behavior compared to other download implementations. Two inconsistencies with
a.href = url;
- a.download = `NDA-Certificate-${row.original.viewerEmail || "Anonymous"}-${Date.now()}.pdf`;
+ a.download = `NDA-Certificate-${row.original.viewerName || row.original.viewerEmail || "Anonymous"}-${Date.now()}.pdf`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
+ toast.success("NDA certificate downloaded successfully!");
} catch (error) {Note: The 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="h-4 w-4 text-emerald-500 hover:text-emerald-600 cursor-pointer" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <FileTextIcon className="h-4 w-4" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </BadgeTooltip> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {row.original.downloadedAt && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <BadgeTooltip | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NDA completion email logic is sound; consider DRYing geo/admin lookup and guarding on missing admin email.
The conditions around
hasConfirmedAgreement,enableAgreement,agreementId,enableNotification, andagreementlook correct, and runningsendSignedNDAEmailviawaitUntilkeeps the main response non‑blocking. Two follow‑ups:locationStringconstruction andadminEmail/teamMembersderivation are duplicated across DATAROOM and DOCUMENT views. A small helper (e.g.,buildNdaEmailContext({ link, request, viewId, dataroomId, viewer })) would reduce copy‑paste and keep future changes in sync.ADMINuser exists,adminEmailwill benulland still passed asownerEmail. IfsendSignedNDAEmailassumes a non‑null owner address, this will just log errors on every call. Either short‑circuit when!adminEmailor makesendSignedNDAEmailexplicitly tolerate a missing owner (e.g., skip sending or fall back to another role).Given the error handling and
waitUntil, this is non‑blocking but worth tightening up.Also applies to: 895-951