fix: resolve follow-up issues from oxlint migration

This commit is contained in:
ephraimduncan
2026-03-06 05:45:16 +00:00
parent ae02169e97
commit 835c55e2ca
68 changed files with 433 additions and 430 deletions
+4 -1
View File
@@ -12,11 +12,14 @@
"dependencies": { "dependencies": {
"@documenso/prisma": "*", "@documenso/prisma": "*",
"luxon": "^3.7.2", "luxon": "^3.7.2",
"next": "15.5.12" "next": "15.5.12",
"react": "^18",
"react-dom": "^18"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20", "@types/node": "^20",
"@types/react": "18.3.27", "@types/react": "18.3.27",
"@types/react-dom": "^18",
"typescript": "5.6.2" "typescript": "5.6.2"
} }
} }
+56 -110
View File
@@ -39,6 +39,8 @@
"@ts-rest/core": "^3.52.1", "@ts-rest/core": "^3.52.1",
"@ts-rest/open-api": "^3.52.1", "@ts-rest/open-api": "^3.52.1",
"@ts-rest/serverless": "^3.52.1", "@ts-rest/serverless": "^3.52.1",
"@types/react": "18.3.27",
"@types/react-dom": "^18",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"dotenv-cli": "^11.0.0", "dotenv-cli": "^11.0.0",
"husky": "^9.1.7", "husky": "^9.1.7",
@@ -57,6 +59,7 @@
"prisma-extension-kysely": "^3.0.0", "prisma-extension-kysely": "^3.0.0",
"prisma-json-types-generator": "^3.6.2", "prisma-json-types-generator": "^3.6.2",
"prisma-kysely": "^2.3.0", "prisma-kysely": "^2.3.0",
"react-dom": "^18",
"rimraf": "^6.1.2", "rimraf": "^6.1.2",
"superjson": "^2.2.5", "superjson": "^2.2.5",
"syncpack": "^14.0.0-alpha.27", "syncpack": "^14.0.0-alpha.27",
@@ -100,6 +103,26 @@
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
}, },
"apps/docs/node_modules/@types/react": {
"version": "19.2.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
}
},
"apps/docs/node_modules/@types/react-dom": {
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^19.2.0"
}
},
"apps/docs/node_modules/react": { "apps/docs/node_modules/react": {
"version": "19.2.4", "version": "19.2.4",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
@@ -109,31 +132,38 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"apps/docs/node_modules/typescript": { "apps/docs/node_modules/react-dom": {
"version": "5.9.3", "version": "19.2.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
"dev": true, "license": "MIT",
"license": "Apache-2.0", "dependencies": {
"bin": { "scheduler": "^0.27.0"
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
}, },
"engines": { "peerDependencies": {
"node": ">=14.17" "react": "^19.2.4"
} }
}, },
"apps/docs/node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"license": "MIT"
},
"apps/openpage-api": { "apps/openpage-api": {
"name": "@documenso/openpage-api", "name": "@documenso/openpage-api",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@documenso/prisma": "*", "@documenso/prisma": "*",
"luxon": "^3.7.2", "luxon": "^3.7.2",
"next": "15.5.12" "next": "15.5.12",
"react": "^18",
"react-dom": "^18"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20", "@types/node": "^20",
"@types/react": "18.3.27", "@types/react": "18.3.27",
"@types/react-dom": "^18",
"typescript": "5.6.2" "typescript": "5.6.2"
} }
}, },
@@ -147,17 +177,6 @@
"undici-types": "~6.21.0" "undici-types": "~6.21.0"
} }
}, },
"apps/openpage-api/node_modules/@types/react": {
"version": "18.3.27",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.2.2"
}
},
"apps/openpage-api/node_modules/nanoid": { "apps/openpage-api/node_modules/nanoid": {
"version": "3.3.11", "version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@@ -362,27 +381,6 @@
"undici-types": "~6.21.0" "undici-types": "~6.21.0"
} }
}, },
"apps/remix/node_modules/@types/react": {
"version": "18.3.27",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.2.2"
}
},
"apps/remix/node_modules/@types/react-dom": {
"version": "18.3.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^18.0.0"
}
},
"apps/remix/node_modules/lucide-react": { "apps/remix/node_modules/lucide-react": {
"version": "0.554.0", "version": "0.554.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.554.0.tgz", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.554.0.tgz",
@@ -392,19 +390,6 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
} }
}, },
"apps/remix/node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
},
"peerDependencies": {
"react": "^18.3.1"
}
},
"apps/remix/node_modules/tailwindcss": { "apps/remix/node_modules/tailwindcss": {
"version": "3.4.19", "version": "3.4.19",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
@@ -13465,26 +13450,26 @@
"version": "15.7.15", "version": "15.7.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "19.2.14", "version": "18.3.27",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/prop-types": "*",
"csstype": "^3.2.2" "csstype": "^3.2.2"
} }
}, },
"node_modules/@types/react-dom": { "node_modules/@types/react-dom": {
"version": "19.2.3", "version": "18.3.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"@types/react": "^19.2.0" "@types/react": "^18.0.0"
} }
}, },
"node_modules/@types/resolve": { "node_modules/@types/resolve": {
@@ -24659,23 +24644,18 @@
} }
}, },
"node_modules/react-dom": { "node_modules/react-dom": {
"version": "19.2.4", "version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"scheduler": "^0.27.0" "loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^19.2.4" "react": "^18.3.1"
} }
}, },
"node_modules/react-dom/node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"license": "MIT"
},
"node_modules/react-draggable": { "node_modules/react-draggable": {
"version": "4.4.6", "version": "4.4.6",
"resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz", "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz",
@@ -29762,27 +29742,6 @@
"typescript": "5.6.2" "typescript": "5.6.2"
} }
}, },
"packages/ui/node_modules/@types/react": {
"version": "18.3.27",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.2.2"
}
},
"packages/ui/node_modules/@types/react-dom": {
"version": "18.3.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^18.0.0"
}
},
"packages/ui/node_modules/lucide-react": { "packages/ui/node_modules/lucide-react": {
"version": "0.554.0", "version": "0.554.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.554.0.tgz", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.554.0.tgz",
@@ -29792,19 +29751,6 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
} }
}, },
"packages/ui/node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
},
"peerDependencies": {
"react": "^18.3.1"
}
},
"packages/ui/node_modules/tailwind-merge": { "packages/ui/node_modules/tailwind-merge": {
"version": "1.14.0", "version": "1.14.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz",
+3
View File
@@ -76,12 +76,15 @@
"prisma-extension-kysely": "^3.0.0", "prisma-extension-kysely": "^3.0.0",
"prisma-json-types-generator": "^3.6.2", "prisma-json-types-generator": "^3.6.2",
"prisma-kysely": "^2.3.0", "prisma-kysely": "^2.3.0",
"react-dom": "^18",
"rimraf": "^6.1.2", "rimraf": "^6.1.2",
"superjson": "^2.2.5", "superjson": "^2.2.5",
"syncpack": "^14.0.0-alpha.27", "syncpack": "^14.0.0-alpha.27",
"turbo": "^1.13.4", "turbo": "^1.13.4",
"vite": "^7.2.4", "vite": "^7.2.4",
"vite-plugin-static-copy": "^3.1.4", "vite-plugin-static-copy": "^3.1.4",
"@types/react": "18.3.27",
"@types/react-dom": "^18",
"zod-openapi": "^4.2.4", "zod-openapi": "^4.2.4",
"zod-prisma-types": "3.3.5" "zod-prisma-types": "3.3.5"
}, },
+8 -8
View File
@@ -189,7 +189,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
fields: parsedMetaFields, fields: parsedMetaFields,
}, },
}; };
} catch (err) { } catch {
return { return {
status: 404, status: 404,
body: { body: {
@@ -276,7 +276,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
status: 200, status: 200,
body: { downloadUrl: url }, body: { downloadUrl: url },
}; };
} catch (err) { } catch {
return { return {
status: 500, status: 500,
body: { body: {
@@ -341,7 +341,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
completedAt: deletedDocument.completedAt, completedAt: deletedDocument.completedAt,
}, },
}; };
} catch (err) { } catch {
return { return {
status: 404, status: 404,
body: { body: {
@@ -478,7 +478,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
})), })),
}, },
}; };
} catch (err) { } catch {
return { return {
status: 404, status: 404,
body: { body: {
@@ -593,7 +593,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
template: fullTemplate, template: fullTemplate,
}, },
}; };
} catch (err) { } catch {
return { return {
status: 404, status: 404,
body: { body: {
@@ -637,7 +637,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
updatedAt: deletedTemplate.updatedAt, updatedAt: deletedTemplate.updatedAt,
}, },
}; };
} catch (err) { } catch {
return { return {
status: 404, status: 404,
body: { body: {
@@ -1077,7 +1077,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
message: 'Document resend successfully initiated', message: 'Document resend successfully initiated',
}, },
}; };
} catch (err) { } catch {
return { return {
status: 500, status: 500,
body: { body: {
@@ -1185,7 +1185,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${newRecipient.token}`, signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${newRecipient.token}`,
}, },
}; };
} catch (err) { } catch {
return { return {
status: 500, status: 500,
body: { body: {
+82 -57
View File
@@ -54,13 +54,11 @@ export class MailChannelsTransport implements Transport<SentMessageInfo> {
const mailCc = this.toMailChannelsAddresses(mail.data.cc); const mailCc = this.toMailChannelsAddresses(mail.data.cc);
const mailBcc = this.toMailChannelsAddresses(mail.data.bcc); const mailBcc = this.toMailChannelsAddresses(mail.data.bcc);
const from: MailChannelsAddress = const [from] = this.toMailChannelsAddresses(mail.data.from);
typeof mail.data.from === 'string'
? { email: mail.data.from } if (!from) {
: { return callback(new Error('Missing required field "from"'), null);
email: mail.data.from?.address, }
name: mail.data.from?.name,
};
const requestHeaders: Record<string, string> = { const requestHeaders: Record<string, string> = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -70,56 +68,15 @@ export class MailChannelsTransport implements Transport<SentMessageInfo> {
requestHeaders['X-Auth-Token'] = this._options.apiKey; requestHeaders['X-Auth-Token'] = this._options.apiKey;
} }
fetch(this._options.endpoint, { void this.sendMailRequest({
method: 'POST', callback,
headers: requestHeaders, from,
body: JSON.stringify({ mail,
from: from, mailBcc,
subject: mail.data.subject, mailCc,
personalizations: [ mailTo,
{ requestHeaders,
to: mailTo, });
cc: mailCc.length > 0 ? mailCc : undefined,
bcc: mailBcc.length > 0 ? mailBcc : undefined,
dkim_domain: env('NEXT_PRIVATE_MAILCHANNELS_DKIM_DOMAIN') || undefined,
dkim_selector: env('NEXT_PRIVATE_MAILCHANNELS_DKIM_SELECTOR') || undefined,
dkim_private_key: env('NEXT_PRIVATE_MAILCHANNELS_DKIM_PRIVATE_KEY') || undefined,
},
],
content: [
{
type: 'text/plain',
value: mail.data.text?.toString('utf-8') ?? '',
},
{
type: 'text/html',
value: mail.data.html?.toString('utf-8') ?? '',
},
],
}),
})
.then((res) => {
if (res.status >= 200 && res.status <= 299) {
return callback(null, {
messageId: '',
envelope: {
from: mail.data.from,
to: mail.data.to,
},
accepted: mail.data.to,
rejected: [],
pending: [],
});
}
res
.json()
.then((data) => callback(new Error(`MailChannels error: ${data.message}`), null))
.catch((err) => callback(err, null));
})
.catch((err) => {
return callback(err, null);
});
} }
/** /**
@@ -154,4 +111,72 @@ export class MailChannelsTransport implements Transport<SentMessageInfo> {
}, },
]; ];
} }
private async sendMailRequest({
callback,
from,
mail,
mailBcc,
mailCc,
mailTo,
requestHeaders,
}: {
callback: (_err: Error | null, _info: SentMessageInfo) => void;
from: MailChannelsAddress;
mail: MailMessage;
mailBcc: Array<MailChannelsAddress>;
mailCc: Array<MailChannelsAddress>;
mailTo: Array<MailChannelsAddress>;
requestHeaders: Record<string, string>;
}) {
try {
const response = await fetch(this._options.endpoint, {
method: 'POST',
headers: requestHeaders,
body: JSON.stringify({
from,
subject: mail.data.subject,
personalizations: [
{
to: mailTo,
cc: mailCc.length > 0 ? mailCc : undefined,
bcc: mailBcc.length > 0 ? mailBcc : undefined,
dkim_domain: env('NEXT_PRIVATE_MAILCHANNELS_DKIM_DOMAIN') || undefined,
dkim_selector: env('NEXT_PRIVATE_MAILCHANNELS_DKIM_SELECTOR') || undefined,
dkim_private_key: env('NEXT_PRIVATE_MAILCHANNELS_DKIM_PRIVATE_KEY') || undefined,
},
],
content: [
{
type: 'text/plain',
value: mail.data.text?.toString('utf-8') ?? '',
},
{
type: 'text/html',
value: mail.data.html?.toString('utf-8') ?? '',
},
],
}),
});
if (response.status >= 200 && response.status <= 299) {
return callback(null, {
messageId: '',
envelope: {
from: mail.data.from,
to: mail.data.to,
},
accepted: mail.data.to,
rejected: [],
pending: [],
});
}
const data = await response.json();
return callback(new Error(`MailChannels error: ${data.message}`), null);
} catch (error) {
return callback(error instanceof Error ? error : new Error('Failed to send email'), null);
}
}
} }
@@ -39,7 +39,7 @@ export function useAnalytics() {
* *
* @param eventFlag The event to check against feature flags to determine whether tracking is enabled. * @param eventFlag The event to check against feature flags to determine whether tracking is enabled.
*/ */
const startSessionRecording = (eventFlag?: string) => { const startSessionRecording = (_eventFlag?: string) => {
return; return;
// const isSessionRecordingEnabled = featureFlags.getFlag(FEATURE_FLAG_GLOBAL_SESSION_RECORDING); // const isSessionRecordingEnabled = featureFlags.getFlag(FEATURE_FLAG_GLOBAL_SESSION_RECORDING);
// const isSessionRecordingEnabledForEvent = Boolean(eventFlag && featureFlags.getFlag(eventFlag)); // const isSessionRecordingEnabledForEvent = Boolean(eventFlag && featureFlags.getFlag(eventFlag));
@@ -40,7 +40,7 @@ export function useCopyShareLink({ onSuccess, onError }: UseCopyShareLinkOptions
} }
onSuccess?.(); onSuccess?.();
} catch (e) { } catch {
onError?.(); onError?.();
} }
}; };
@@ -14,13 +14,16 @@ export function useCopyToClipboard(): [CopiedValue, CopyFn] {
return false; return false;
} }
const isClipboardApiSupported = Boolean(typeof ClipboardItem && navigator.clipboard.write); const isClipboardApiSupported =
typeof ClipboardItem !== 'undefined' && typeof navigator.clipboard.write === 'function';
// Try to save to clipboard then save it in the state if worked // Try to save to clipboard then save it in the state if worked
try { try {
isClipboardApiSupported if (isClipboardApiSupported) {
? await handleClipboardApiCopy(text, blobType) await handleClipboardApiCopy(text, blobType);
: await handleWriteTextCopy(text); } else {
await handleWriteTextCopy(text);
}
setCopiedText(await text); setCopiedText(await text);
return true; return true;
@@ -41,7 +44,7 @@ export function useCopyToClipboard(): [CopiedValue, CopyFn] {
const handleClipboardApiCopy = async (value: CopyValue, blobType = 'text/plain') => { const handleClipboardApiCopy = async (value: CopyValue, blobType = 'text/plain') => {
try { try {
await navigator.clipboard.write([new ClipboardItem({ [blobType]: value })]); await navigator.clipboard.write([new ClipboardItem({ [blobType]: value })]);
} catch (e) { } catch {
// Fallback attempt. // Fallback attempt.
await handleWriteTextCopy(value); await handleWriteTextCopy(value);
} }
@@ -251,7 +251,7 @@ export const useEditorFields = ({
const getFieldByFormId = useCallback( const getFieldByFormId = useCallback(
(formId: string): TLocalField | undefined => { (formId: string): TLocalField | undefined => {
return localFields.find((field) => field.formId === formId) as TLocalField | undefined; return localFields.find((field) => field.formId === formId);
}, },
[localFields], [localFields],
); );
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/consistent-type-assertions */ /* eslint-disable @typescript-eslint/consistent-type-assertions */
import { RefObject, useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
/** /**
* Calculate the width and height of a text element. * Calculate the width and height of a text element.
@@ -276,7 +276,7 @@ export const EnvelopeEditorProvider = ({
[envelope.recipients], [envelope.recipients],
); );
const { refetch: reloadEnvelope, isLoading: isReloadingEnvelope } = trpc.envelope.get.useQuery( const { refetch: reloadEnvelope, isLoading: _isReloadingEnvelope } = trpc.envelope.get.useQuery(
{ {
envelopeId: envelope.id, envelopeId: envelope.id,
}, },
@@ -189,7 +189,7 @@ export const EnvelopeRenderProvider = ({
}, [envelope.envelopeItems]); }, [envelope.envelopeItems]);
const recipientIds = useMemo( const recipientIds = useMemo(
() => recipients.map((recipient) => recipient.id).sort(), () => recipients.map((recipient) => recipient.id).sort((left, right) => left - right),
[recipients], [recipients],
); );
+4 -1
View File
@@ -170,5 +170,8 @@ export const convertToLocalSystemFormat = (
}; };
export const isValidDateFormat = (dateFormat: unknown): dateFormat is ValidDateFormat => { export const isValidDateFormat = (dateFormat: unknown): dateFormat is ValidDateFormat => {
return VALID_DATE_FORMAT_VALUES.includes(dateFormat as ValidDateFormat); return (
typeof dateFormat === 'string' &&
VALID_DATE_FORMAT_VALUES.some((validDateFormat) => validDateFormat === dateFormat)
);
}; };
+2 -1
View File
@@ -73,4 +73,5 @@ export const SUPPORTED_LANGUAGES: Record<string, SupportedLanguage> = {
} satisfies Record<SupportedLanguageCodes, SupportedLanguage>; } satisfies Record<SupportedLanguageCodes, SupportedLanguage>;
export const isValidLanguageCode = (code: unknown): code is SupportedLanguageCodes => export const isValidLanguageCode = (code: unknown): code is SupportedLanguageCodes =>
SUPPORTED_LANGUAGE_CODES.includes(code as SupportedLanguageCodes); typeof code === 'string' &&
SUPPORTED_LANGUAGE_CODES.some((languageCode) => languageCode === code);
+5 -5
View File
@@ -91,11 +91,11 @@ export class InngestJobProvider extends BaseJobProvider {
return { return {
wait: step.sleep, wait: step.sleep,
logger: { logger: {
info: ctx.logger.info, info: (...args) => ctx.logger.info(...args),
debug: ctx.logger.debug, debug: (...args) => ctx.logger.debug(...args),
error: ctx.logger.error, error: (...args) => ctx.logger.error(...args),
warn: ctx.logger.warn, warn: (...args) => ctx.logger.warn(...args),
log: ctx.logger.info, log: (...args) => ctx.logger.info(...args),
}, },
runTask: async (cacheKey, callback) => { runTask: async (cacheKey, callback) => {
const result = await step.run(cacheKey, callback); const result = await step.run(cacheKey, callback);
@@ -7,7 +7,7 @@ import type { TExecuteWebhookJobDefinition } from './execute-webhook';
export const run = async ({ export const run = async ({
payload, payload,
io, io: _io,
}: { }: {
payload: TExecuteWebhookJobDefinition; payload: TExecuteWebhookJobDefinition;
io: JobRunIO; io: JobRunIO;
@@ -28,10 +28,11 @@ export const run = async ({
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
webhookEndpoint: url, webhookEndpoint: url,
}; };
const requestBody: Prisma.InputJsonValue = JSON.parse(JSON.stringify(payloadData));
const response = await fetch(url, { const response = await fetch(url, {
method: 'POST', method: 'POST',
body: JSON.stringify(payloadData), body: JSON.stringify(requestBody),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-Documenso-Secret': secret ?? '', 'X-Documenso-Secret': secret ?? '',
@@ -44,7 +45,7 @@ export const run = async ({
try { try {
responseBody = JSON.parse(body); responseBody = JSON.parse(body);
} catch (err) { } catch {
responseBody = body; responseBody = body;
} }
@@ -53,7 +54,7 @@ export const run = async ({
url, url,
event, event,
status: response.ok ? WebhookCallStatus.SUCCESS : WebhookCallStatus.FAILED, status: response.ok ? WebhookCallStatus.SUCCESS : WebhookCallStatus.FAILED,
requestBody: payloadData as Prisma.InputJsonValue, requestBody,
responseCode: response.status, responseCode: response.status,
responseBody, responseBody,
responseHeaders: Object.fromEntries(response.headers.entries()), responseHeaders: Object.fromEntries(response.headers.entries()),
@@ -91,9 +91,13 @@ export const adminFindUnsealedDocuments = async ({
]); ]);
const count = Number(countResult[0]?.count ?? 0); const count = Number(countResult[0]?.count ?? 0);
const formattedData: AdminUnsealedDocument[] = data.map((document) => ({
...document,
id: String(document.id),
}));
return { return {
data: data as unknown as AdminUnsealedDocument[], data: formattedData,
count, count,
currentPage: Math.max(page, 1), currentPage: Math.max(page, 1),
perPage, perPage,
@@ -32,7 +32,7 @@ export type TeamInsights = {
export type UserInsights = { export type UserInsights = {
id: number; id: number;
name: string; name: string | null;
email: string; email: string;
documentCount: number; documentCount: number;
signedDocumentCount: number; signedDocumentCount: number;
@@ -98,7 +98,7 @@ export async function getOrganisationDetailedInsights({
case 'documents': case 'documents':
return await getDocumentInsights(organisationId, offset, perPage, createdAtFrom); return await getDocumentInsights(organisationId, offset, perPage, createdAtFrom);
default: default:
throw new Error(`Invalid view: ${view}`); throw new Error('Invalid view');
} }
})(); })();
@@ -149,9 +149,14 @@ async function getTeamInsights(
const [teams, countResult] = await Promise.all([teamsQuery.execute(), countQuery.execute()]); const [teams, countResult] = await Promise.all([teamsQuery.execute(), countQuery.execute()]);
const count = Number(countResult[0]?.count || 0); const count = Number(countResult[0]?.count || 0);
const teamInsights: TeamInsights[] = teams.map((team) => ({
...team,
memberCount: Number(team.memberCount),
documentCount: Number(team.documentCount),
}));
return { return {
teams: teams as TeamInsights[], teams: teamInsights,
users: [], users: [],
documents: [], documents: [],
totalPages: Math.ceil(Number(count) / perPage), totalPages: Math.ceil(Number(count) / perPage),
@@ -208,10 +213,15 @@ async function getUserInsights(
const [users, countResult] = await Promise.all([usersQuery.execute(), countQuery.execute()]); const [users, countResult] = await Promise.all([usersQuery.execute(), countQuery.execute()]);
const count = Number(countResult[0]?.count || 0); const count = Number(countResult[0]?.count || 0);
const userInsights: UserInsights[] = users.map((user) => ({
...user,
documentCount: Number(user.documentCount),
signedDocumentCount: Number(user.signedDocumentCount),
}));
return { return {
teams: [], teams: [],
users: users as UserInsights[], users: userInsights,
documents: [], documents: [],
totalPages: Math.ceil(Number(count) / perPage), totalPages: Math.ceil(Number(count) / perPage),
}; };
@@ -223,18 +233,13 @@ async function getDocumentInsights(
perPage: number, perPage: number,
createdAtFrom: Date | null, createdAtFrom: Date | null,
): Promise<OrganisationDetailedInsights> { ): Promise<OrganisationDetailedInsights> {
let documentsQuery = kyselyPrisma.$kysely const documentsQuery = kyselyPrisma.$kysely
.selectFrom('Envelope as e') .selectFrom('Envelope as e')
.innerJoin('Team as t', 'e.teamId', 't.id') .innerJoin('Team as t', 'e.teamId', 't.id')
.where('t.organisationId', '=', organisationId) .where('t.organisationId', '=', organisationId)
.where('e.deletedAt', 'is', null) .where('e.deletedAt', 'is', null)
.where(() => sql`e.type = ${EnvelopeType.DOCUMENT}::"EnvelopeType"`); .where(() => sql`e.type = ${EnvelopeType.DOCUMENT}::"EnvelopeType"`)
.$if(!!createdAtFrom, (qb) => qb.where('e.createdAt', '>=', createdAtFrom!))
if (createdAtFrom) {
documentsQuery = documentsQuery.where('e.createdAt', '>=', createdAtFrom);
}
documentsQuery = documentsQuery
.select([ .select([
'e.id as id', 'e.id as id',
'e.title as title', 'e.title as title',
@@ -247,33 +252,33 @@ async function getDocumentInsights(
.limit(perPage) .limit(perPage)
.offset(offset); .offset(offset);
let countQuery = kyselyPrisma.$kysely const countQuery = kyselyPrisma.$kysely
.selectFrom('Envelope as e') .selectFrom('Envelope as e')
.innerJoin('Team as t', 'e.teamId', 't.id') .innerJoin('Team as t', 'e.teamId', 't.id')
.where('t.organisationId', '=', organisationId) .where('t.organisationId', '=', organisationId)
.where('e.deletedAt', 'is', null) .where('e.deletedAt', 'is', null)
.where(() => sql`e.type = ${EnvelopeType.DOCUMENT}::"EnvelopeType"`); .where(() => sql`e.type = ${EnvelopeType.DOCUMENT}::"EnvelopeType"`)
.$if(!!createdAtFrom, (qb) => qb.where('e.createdAt', '>=', createdAtFrom!))
if (createdAtFrom) { .select(sql<number>`count(*)`.as('count'));
countQuery = countQuery.where('e.createdAt', '>=', createdAtFrom);
}
countQuery = countQuery.select(({ fn }) => [fn.countAll().as('count')]);
const [documents, countResult] = await Promise.all([ const [documents, countResult] = await Promise.all([
documentsQuery.execute(), documentsQuery.execute(),
countQuery.execute(), countQuery.executeTakeFirst(),
]); ]);
const count = Number(countResult?.count || 0);
const count = Number((countResult[0] as { count: number })?.count || 0); const documentInsights: DocumentInsights[] = documents.map((document) => ({
title: document.title,
status: document.status,
createdAt: document.createdAt,
completedAt: document.completedAt,
teamName: document.teamName,
id: String(document.id),
}));
return { return {
teams: [], teams: [],
users: [], users: [],
documents: documents.map((doc) => ({ documents: documentInsights,
...doc,
id: String((doc as { id: number }).id),
})) as DocumentInsights[],
totalPages: Math.ceil(Number(count) / perPage), totalPages: Math.ceil(Number(count) / perPage),
}; };
} }
+1 -1
View File
@@ -47,7 +47,7 @@ export type PdfToImagesOptions = {
export const pdfToImages = async (pdfBytes: Uint8Array, options: PdfToImagesOptions = {}) => { export const pdfToImages = async (pdfBytes: Uint8Array, options: PdfToImagesOptions = {}) => {
const { scale = 2 } = options; const { scale = 2 } = options;
const task = await pdfjsLib.getDocument({ const task = pdfjsLib.getDocument({
data: pdfBytes, data: pdfBytes,
CanvasFactory: SkiaCanvasFactory, CanvasFactory: SkiaCanvasFactory,
}); });
@@ -4,6 +4,7 @@ import { omit } from 'remeda';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import { AppError, AppErrorCode } from '../../errors/app-error'; import { AppError, AppErrorCode } from '../../errors/app-error';
import { ZFieldMetaSchema } from '../../types/field-meta';
import { import {
ZWebhookDocumentSchema, ZWebhookDocumentSchema,
mapEnvelopeToWebhookDocumentPayload, mapEnvelopeToWebhookDocumentPayload,
@@ -158,7 +159,7 @@ export const duplicateEnvelope = async ({ id, userId, teamId }: DuplicateEnvelop
height: field.height, height: field.height,
customText: '', customText: '',
inserted: false, inserted: false,
fieldMeta: field.fieldMeta as PrismaJson.FieldMeta, fieldMeta: field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : undefined,
})), })),
}, },
}, },
@@ -25,7 +25,7 @@ export type GetRecipientEnvelopeByTokenOptions = {
export const getEnvelopeForDirectTemplateSigning = async ({ export const getEnvelopeForDirectTemplateSigning = async ({
token, token,
userId, userId,
accessAuth, accessAuth: _accessAuth,
}: GetRecipientEnvelopeByTokenOptions): Promise<EnvelopeForSigningResponse> => { }: GetRecipientEnvelopeByTokenOptions): Promise<EnvelopeForSigningResponse> => {
if (!token) { if (!token) {
throw new AppError(AppErrorCode.NOT_FOUND, { throw new AppError(AppErrorCode.NOT_FOUND, {
@@ -193,12 +193,12 @@ export const updateEnvelope = async ({
isDeepEqual(documentGlobalActionAuth, newGlobalActionAuth); isDeepEqual(documentGlobalActionAuth, newGlobalActionAuth);
const isDocumentVisibilitySame = const isDocumentVisibilitySame =
data.visibility === undefined || data.visibility === envelope.visibility; data.visibility === undefined || data.visibility === envelope.visibility;
const isFolderSame = data.folderId === undefined || data.folderId === envelope.folderId; const _isFolderSame = data.folderId === undefined || data.folderId === envelope.folderId;
const isTemplateTypeSame = const _isTemplateTypeSame =
data.templateType === undefined || data.templateType === envelope.templateType; data.templateType === undefined || data.templateType === envelope.templateType;
const isPublicDescriptionSame = const _isPublicDescriptionSame =
data.publicDescription === undefined || data.publicDescription === envelope.publicDescription; data.publicDescription === undefined || data.publicDescription === envelope.publicDescription;
const isPublicTitleSame = const _isPublicTitleSame =
data.publicTitle === undefined || data.publicTitle === envelope.publicTitle; data.publicTitle === undefined || data.publicTitle === envelope.publicTitle;
const auditLogs: CreateDocumentAuditLogDataResponse[] = []; const auditLogs: CreateDocumentAuditLogDataResponse[] = [];
@@ -27,6 +27,7 @@ Konva.Util['createCanvasElement'] = () => {
get: () => node, get: () => node,
}); });
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return node as unknown as HTMLCanvasElement; return node as unknown as HTMLCanvasElement;
}; };
@@ -34,6 +35,7 @@ Konva.Util.createImageElement = () => {
const node = new Image(); const node = new Image();
node.toString = () => '[object HTMLImageElement]'; node.toString = () => '[object HTMLImageElement]';
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return node as unknown as HTMLImageElement; return node as unknown as HTMLImageElement;
}; };
@@ -6,7 +6,7 @@ import { NEXT_PRIVATE_INTERNAL_WEBAPP_URL } from '../../constants/app';
* Adds a rejection stamp to each page of a PDF document. * Adds a rejection stamp to each page of a PDF document.
* The stamp is placed in the center of the page. * The stamp is placed in the center of the page.
*/ */
export async function addRejectionStampToPdf(pdf: PDF, reason: string): Promise<PDF> { export async function addRejectionStampToPdf(pdf: PDF, _reason: string): Promise<PDF> {
const pages = pdf.getPages(); const pages = pdf.getPages();
const fontBytes = await fetch(`${NEXT_PRIVATE_INTERNAL_WEBAPP_URL()}/fonts/noto-sans.ttf`).then( const fontBytes = await fetch(`${NEXT_PRIVATE_INTERNAL_WEBAPP_URL()}/fonts/noto-sans.ttf`).then(
@@ -694,10 +694,10 @@ const setTextFieldFontSize = (textField: PDFTextField, font: PDFFont, fontSize:
try { try {
textField.setFontSize(fontSize); textField.setFontSize(fontSize);
} catch (err) { } catch {
let da = textField.acroField.getDefaultAppearance() ?? ''; let da = textField.acroField.getDefaultAppearance() ?? '';
da += `\n ${setFontAndSize(font.name, fontSize)}`; da += `\n ${String(setFontAndSize(font.name, fontSize))}`;
textField.acroField.setDefaultAppearance(da); textField.acroField.setDefaultAppearance(da);
} }
@@ -51,7 +51,7 @@ const parser = new UAParser();
const textMutedForegroundLight = '#929DAE'; const textMutedForegroundLight = '#929DAE';
const textForeground = '#000'; const textForeground = '#000';
const textMutedForeground = '#64748B'; const textMutedForeground = '#64748B';
const textBase = 10; const _textBase = 10;
const textSm = 9; const textSm = 9;
const textXs = 8; const textXs = 8;
const fontMedium = '500'; const fontMedium = '500';
@@ -75,13 +75,13 @@ const getDevice = (userAgent?: string | null): string => {
return `${result.os.name} - ${result.browser.name} ${result.browser.version}`; return `${result.os.name} - ${result.browser.name} ${result.browser.version}`;
}; };
const textMutedForegroundLight = '#929DAE'; const _textMutedForegroundLight = '#929DAE';
const textForeground = '#000'; const _textForeground = '#000';
const textMutedForeground = '#64748B'; const textMutedForeground = '#64748B';
const textRejectedRed = '#dc2626'; const textRejectedRed = '#dc2626';
const textBase = 10; const textBase = 10;
const textSm = 9; const textSm = 9;
const textXs = 8; const _textXs = 8;
const fontMedium = '500'; const fontMedium = '500';
const columnWidthPercentages = [30, 30, 40]; const columnWidthPercentages = [30, 30, 40];
@@ -33,7 +33,7 @@ export const setAvatarImage = async ({
userId, userId,
target, target,
bytes, bytes,
requestMetadata, requestMetadata: _requestMetadata,
}: SetAvatarImageOptions) => { }: SetAvatarImageOptions) => {
let oldAvatarImageId: string | null = null; let oldAvatarImageId: string | null = null;
@@ -13,7 +13,7 @@ export const testCredentialsHandler = async (req: Request) => {
return Response.json({ return Response.json({
name: result.team?.name ?? result.user.name, name: result.team?.name ?? result.user.name,
}); });
} catch (err) { } catch {
return Response.json( return Response.json(
{ {
message: 'Internal Server Error', message: 'Internal Server Error',
@@ -99,7 +99,7 @@ export const deleteTeamEmail = async ({ userId, userEmail, teamId }: DeleteTeamE
html, html,
text, text,
}); });
} catch (e) { } catch {
// Todo: Teams - Alert us. // Todo: Teams - Alert us.
// We don't want to prevent a user from revoking access because an email could not be sent. // We don't want to prevent a user from revoking access because an email could not be sent.
} }
@@ -13,7 +13,7 @@ export const deletedServiceAccountEmail = () => {
const { hostname } = new URL(process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'); const { hostname } = new URL(process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000');
return `deleted-account@${hostname}`; return `deleted-account@${hostname}`;
} catch (error) { } catch {
return LEGACY_DELETED_ACCOUNT_EMAIL; return LEGACY_DELETED_ACCOUNT_EMAIL;
} }
}; };
@@ -13,7 +13,7 @@ export const legacyServiceAccountEmail = () => {
const { hostname } = new URL(process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'); const { hostname } = new URL(process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000');
return `serviceaccount@${hostname}`; return `serviceaccount@${hostname}`;
} catch (error) { } catch {
return LEGACY_SERVICE_ACCOUNT_EMAIL; return LEGACY_SERVICE_ACCOUNT_EMAIL;
} }
}; };
@@ -545,5 +545,5 @@ export const generateSampleWebhookPayload = (
}; };
} }
throw new Error(`Unsupported event type: ${event}`); throw new Error('Unsupported event type');
}; };
@@ -14,7 +14,7 @@ export const validateApiToken = async ({ authorization }: ValidateApiTokenOption
} }
return await getApiTokenByToken({ token }); return await getApiTokenByToken({ token });
} catch (err) { } catch {
throw new Error(`Failed to validate API token`); throw new Error(`Failed to validate API token`);
} }
}; };
-2
View File
@@ -418,5 +418,3 @@ export const ZEnvelopeFieldAndMetaSchema = z.discriminatedUnion('type', [
fieldMeta: ZDropdownFieldMeta.optional().default(FIELD_DROPDOWN_META_DEFAULT_VALUES), fieldMeta: ZDropdownFieldMeta.optional().default(FIELD_DROPDOWN_META_DEFAULT_VALUES),
}), }),
]); ]);
type TEnvelopeFieldAndMeta = z.infer<typeof ZEnvelopeFieldAndMetaSchema>;
@@ -11,7 +11,7 @@ export const MIN_FIELD_WIDTH_PX = 36;
export type FieldToRender = Pick< export type FieldToRender = Pick<
Field, Field,
'envelopeItemId' | 'recipientId' | 'type' | 'page' | 'customText' | 'inserted' | 'recipientId' 'envelopeItemId' | 'recipientId' | 'type' | 'page' | 'customText' | 'inserted'
> & { > & {
renderId: string; // A unique ID for the field in the render. renderId: string; // A unique ID for the field in the render.
width: number; width: number;
@@ -30,7 +30,8 @@ export const renderCheckboxFieldElement = (
const { fieldWidth, fieldHeight } = calculateFieldPosition(field, pageWidth, pageHeight); const { fieldWidth, fieldHeight } = calculateFieldPosition(field, pageWidth, pageHeight);
const checkboxMeta: TCheckboxFieldMeta | null = (field.fieldMeta as TCheckboxFieldMeta) || null; const checkboxMeta: TCheckboxFieldMeta | null =
field.fieldMeta?.type === 'checkbox' ? field.fieldMeta : null;
const checkboxValues = checkboxMeta?.values || []; const checkboxValues = checkboxMeta?.values || [];
const isFirstRender = !pageLayer.findOne(`#${field.renderId}`); const isFirstRender = !pageLayer.findOne(`#${field.renderId}`);
@@ -131,6 +132,7 @@ export const renderCheckboxFieldElement = (
}); });
const checkedValues: number[] = field.customText ? parseCheckboxCustomText(field.customText) : []; const checkedValues: number[] = field.customText ? parseCheckboxCustomText(field.customText) : [];
const isReadOnly = checkboxMeta?.readOnly ?? false;
checkboxValues.forEach(({ value, checked }, index) => { checkboxValues.forEach(({ value, checked }, index) => {
const isCheckboxChecked = match(mode) const isCheckboxChecked = match(mode)
@@ -138,7 +140,7 @@ export const renderCheckboxFieldElement = (
.with('sign', () => checkedValues.includes(index)) .with('sign', () => checkedValues.includes(index))
.with('export', () => { .with('export', () => {
// If it's read-only, check the originally checked state. // If it's read-only, check the originally checked state.
if (checkboxMeta.readOnly) { if (isReadOnly) {
return checked; return checked;
} }
@@ -54,7 +54,8 @@ export const renderDropdownFieldElement = (
const { fieldWidth, fieldHeight } = calculateFieldPosition(field, pageWidth, pageHeight); const { fieldWidth, fieldHeight } = calculateFieldPosition(field, pageWidth, pageHeight);
const dropdownMeta: TDropdownFieldMeta | null = (field.fieldMeta as TDropdownFieldMeta) || null; const dropdownMeta: TDropdownFieldMeta | null =
field.fieldMeta?.type === 'dropdown' ? field.fieldMeta : null;
let selectedValue = translations?.[FieldType.DROPDOWN] || 'Select Option'; let selectedValue = translations?.[FieldType.DROPDOWN] || 'Select Option';
@@ -28,7 +28,7 @@ export type FieldRenderMode = 'edit' | 'sign' | 'export';
export type FieldToRender = Pick< export type FieldToRender = Pick<
Field, Field,
'envelopeItemId' | 'recipientId' | 'type' | 'page' | 'customText' | 'inserted' | 'recipientId' 'envelopeItemId' | 'recipientId' | 'type' | 'page' | 'customText' | 'inserted'
> & { > & {
renderId: string; // A unique ID for the field in the render. renderId: string; // A unique ID for the field in the render.
width: number; width: number;
@@ -20,12 +20,29 @@ import { calculateFieldPosition } from './field-renderer';
const DEFAULT_TEXT_X_PADDING = 6; const DEFAULT_TEXT_X_PADDING = 6;
const getGenericTextFieldMeta = (field: FieldToRender): GenericTextFieldTypeMetas | undefined => {
const fieldMeta = field.fieldMeta;
if (
fieldMeta?.type === 'initials' ||
fieldMeta?.type === 'name' ||
fieldMeta?.type === 'email' ||
fieldMeta?.type === 'date' ||
fieldMeta?.type === 'text' ||
fieldMeta?.type === 'number'
) {
return fieldMeta;
}
return undefined;
};
const upsertFieldText = (field: FieldToRender, options: RenderFieldElementOptions): Konva.Text => { const upsertFieldText = (field: FieldToRender, options: RenderFieldElementOptions): Konva.Text => {
const { pageWidth, pageHeight, mode = 'edit', pageLayer, translations } = options; const { pageWidth, pageHeight, mode = 'edit', pageLayer, translations } = options;
const { fieldWidth, fieldHeight } = calculateFieldPosition(field, pageWidth, pageHeight); const { fieldWidth, fieldHeight } = calculateFieldPosition(field, pageWidth, pageHeight);
const fieldMeta = field.fieldMeta as GenericTextFieldTypeMetas | undefined; const fieldMeta = getGenericTextFieldMeta(field);
const fieldTypeName = translations?.[field.type] || field.type; const fieldTypeName = translations?.[field.type] || field.type;
@@ -27,7 +27,8 @@ export const renderRadioFieldElement = (
) => { ) => {
const { pageWidth, pageHeight, pageLayer, mode, color } = options; const { pageWidth, pageHeight, pageLayer, mode, color } = options;
const radioMeta: TRadioFieldMeta | null = (field.fieldMeta as TRadioFieldMeta) || null; const radioMeta: TRadioFieldMeta | null =
field.fieldMeta?.type === 'radio' ? field.fieldMeta : null;
const radioValues = radioMeta?.values || []; const radioValues = radioMeta?.values || [];
const isFirstRender = !pageLayer.findOne(`#${field.renderId}`); const isFirstRender = !pageLayer.findOne(`#${field.renderId}`);
@@ -122,6 +123,7 @@ export const renderRadioFieldElement = (
}); });
const { fieldWidth, fieldHeight } = calculateFieldPosition(field, pageWidth, pageHeight); const { fieldWidth, fieldHeight } = calculateFieldPosition(field, pageWidth, pageHeight);
const isReadOnly = radioMeta?.readOnly ?? false;
radioValues.forEach(({ value, checked }, index) => { radioValues.forEach(({ value, checked }, index) => {
const isRadioValueChecked = match(mode) const isRadioValueChecked = match(mode)
@@ -129,7 +131,7 @@ export const renderRadioFieldElement = (
.with('sign', () => index.toString() === field.customText) .with('sign', () => index.toString() === field.customText)
.with('export', () => { .with('export', () => {
// If it's read-only, check the originally checked state. // If it's read-only, check the originally checked state.
if (radioMeta.readOnly) { if (isReadOnly) {
return checked; return checked;
} }
+1 -1
View File
@@ -13,7 +13,7 @@ export type GetFileOptions = {
* *
* - Lucas, 2025-11-04 * - Lucas, 2025-11-04
*/ */
const getFile = async ({ type, data }: GetFileOptions) => { const _getFile = async ({ type, data }: GetFileOptions) => {
return await match(type) return await match(type)
.with(DocumentDataType.BYTES, () => getFileFromBytes(data)) .with(DocumentDataType.BYTES, () => getFileFromBytes(data))
.with(DocumentDataType.BYTES_64, () => getFileFromBytes64(data)) .with(DocumentDataType.BYTES_64, () => getFileFromBytes64(data))
+1 -1
View File
@@ -26,7 +26,7 @@ export const getEnvelopeItemPdfUrl = (options: EnvelopeItemPdfUrlOptions) => {
const version = options.version; const version = options.version;
return token return token
? `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/token/${token}/envelopeItem/${id}/download/${version}${presignToken ? `?presignToken=${presignToken}` : ''}` ? `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/token/${token}/envelopeItem/${id}/download/${version}`
: `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/envelope/${envelopeId}/envelopeItem/${id}/download/${version}`; : `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/envelope/${envelopeId}/envelopeItem/${id}/download/${version}`;
} }
+2 -2
View File
@@ -53,8 +53,8 @@ export const validateFieldsUninserted = (): boolean => {
const innerDiv = element.querySelector('div'); const innerDiv = element.querySelector('div');
const hasError = innerDiv?.getAttribute('data-error') === 'true'; const hasError = innerDiv?.getAttribute('data-error') === 'true';
if (hasError) { if (hasError && element instanceof HTMLElement) {
errorElements.push(element as HTMLElement); errorElements.push(element);
} else { } else {
element.removeAttribute('data-error'); element.removeAttribute('data-error');
} }
@@ -151,7 +151,7 @@ export const updateOrganisationMemberRoleRoute = adminProcedure
return; return;
} }
const targetRole = role as OrganisationMemberRole; const targetRole: OrganisationMemberRole = role;
if (currentOrganisationRole === targetRole) { if (currentOrganisationRole === targetRole) {
throw new AppError(AppErrorCode.INVALID_REQUEST, { throw new AppError(AppErrorCode.INVALID_REQUEST, {
@@ -30,8 +30,6 @@ export const accessAuthRequest2FAEmailRoute = procedure
assertRateLimit(rateLimitResult); assertRateLimit(rateLimitResult);
const user = ctx.user;
// Get document and recipient by token // Get document and recipient by token
const envelope = await prisma.envelope.findFirst({ const envelope = await prisma.envelope.findFirst({
where: { where: {
+1 -1
View File
@@ -174,7 +174,7 @@ export const folderRouter = router({
folderId: parentId, folderId: parentId,
type, type,
}); });
} catch (error) { } catch {
throw new AppError(AppErrorCode.NOT_FOUND, { throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Parent folder not found', message: 'Parent folder not found',
}); });
@@ -1,4 +1,4 @@
import { Prisma, WebhookCallStatus, WebhookTriggerEvents } from '@prisma/client'; import type { Prisma, WebhookCallStatus, WebhookTriggerEvents } from '@prisma/client';
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams'; import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
@@ -1,8 +1,7 @@
import { Prisma, WebhookCallStatus, WebhookTriggerEvents } from '@prisma/client'; import { Prisma, WebhookCallStatus } from '@prisma/client';
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams'; import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import type { FindResultResponse } from '@documenso/lib/types/search-params';
import { buildTeamWhereQuery } from '@documenso/lib/utils/teams'; import { buildTeamWhereQuery } from '@documenso/lib/utils/teams';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
@@ -62,7 +61,7 @@ export const resendWebhookCallRoute = authenticatedProcedure
try { try {
responseBody = JSON.parse(body); responseBody = JSON.parse(body);
} catch (err) { } catch {
responseBody = body; responseBody = body;
} }
@@ -1,4 +1,3 @@
import { WebhookCallStatus, WebhookTriggerEvents } from '@prisma/client';
import { z } from 'zod'; import { z } from 'zod';
import WebhookCallSchema from '@documenso/prisma/generated/zod/modelSchema/WebhookCallSchema'; import WebhookCallSchema from '@documenso/prisma/generated/zod/modelSchema/WebhookCallSchema';
@@ -39,17 +39,13 @@ export const DocumentGlobalAuthAccessSelect = ({
})), })),
]; ];
// Convert string array to Option array for MultiSelect const getSelectedOptions = (selectedValues?: string[]) =>
const selectedOptions = selectedValues
(value ?.map((selectedValue) => authOptions.find((option) => option.value === selectedValue))
?.map((val) => authOptions.find((option) => option.value === val)) .filter((option): option is Option => option !== undefined) ?? [];
.filter(Boolean) as Option[]) || [];
// Convert default value to Option array const selectedOptions = getSelectedOptions(value);
const defaultOptions = const defaultOptions = getSelectedOptions(defaultValue);
(defaultValue
?.map((val) => authOptions.find((option) => option.value === val))
.filter(Boolean) as Option[]) || [];
const handleChange = (options: Option[]) => { const handleChange = (options: Option[]) => {
const values = options.map((option) => option.value); const values = options.map((option) => option.value);
@@ -79,7 +75,7 @@ export const DocumentGlobalAuthAccessTooltip = () => (
<InfoIcon className="mx-2 h-4 w-4" /> <InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent className="max-w-md space-y-2 p-4 text-foreground"> <TooltipContent className="text-foreground max-w-md space-y-2 p-4">
<h2> <h2>
<strong> <strong>
<Trans>Document access</Trans> <Trans>Document access</Trans>
@@ -39,17 +39,13 @@ export const DocumentGlobalAuthActionSelect = ({
})), })),
]; ];
// Convert string array to Option array for MultiSelect const getSelectedOptions = (selectedValues?: string[]) =>
const selectedOptions = selectedValues
(value ?.map((selectedValue) => authOptions.find((option) => option.value === selectedValue))
?.map((val) => authOptions.find((option) => option.value === val)) .filter((option): option is Option => option !== undefined) ?? [];
.filter(Boolean) as Option[]) || [];
// Convert default value to Option array const selectedOptions = getSelectedOptions(value);
const defaultOptions = const defaultOptions = getSelectedOptions(defaultValue);
(defaultValue
?.map((val) => authOptions.find((option) => option.value === val))
.filter(Boolean) as Option[]) || [];
const handleChange = (options: Option[]) => { const handleChange = (options: Option[]) => {
const values = options.map((option) => option.value); const values = options.map((option) => option.value);
@@ -79,7 +75,7 @@ export const DocumentGlobalAuthActionTooltip = () => (
<InfoIcon className="mx-2 h-4 w-4" /> <InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent className="max-w-md space-y-2 p-4 text-foreground"> <TooltipContent className="text-foreground max-w-md space-y-2 p-4">
<h2> <h2>
<Trans>Global recipient action authentication</Trans> <Trans>Global recipient action authentication</Trans>
</h2> </h2>
@@ -56,7 +56,7 @@ export function EnvelopeRecipientFieldTooltip({
field, field,
showFieldStatus = false, showFieldStatus = false,
showRecipientTooltip = false, showRecipientTooltip = false,
showRecipientColors = false, showRecipientColors: _showRecipientColors = false,
}: EnvelopeRecipientFieldTooltipProps) { }: EnvelopeRecipientFieldTooltipProps) {
const { t } = useLingui(); const { t } = useLingui();
@@ -145,7 +145,7 @@ export function EnvelopeRecipientFieldTooltip({
> >
<PopoverHover <PopoverHover
trigger={ trigger={
<Avatar className="absolute -left-3 -top-3 z-50 h-6 w-6 border-2 border-solid border-gray-200/50 transition-colors hover:border-gray-200"> <Avatar className="absolute -top-3 -left-3 z-50 h-6 w-6 border-2 border-solid border-gray-200/50 transition-colors hover:border-gray-200">
<AvatarFallback className="bg-neutral-50 text-xs text-gray-400"> <AvatarFallback className="bg-neutral-50 text-xs text-gray-400">
{extractInitials(field.recipient.name || field.recipient.email)} {extractInitials(field.recipient.name || field.recipient.email)}
</AvatarFallback> </AvatarFallback>
@@ -191,12 +191,12 @@ export function EnvelopeRecipientFieldTooltip({
</span> </span>
</p> </p>
<p className="mt-1 text-center text-xs text-muted-foreground"> <p className="text-muted-foreground mt-1 text-center text-xs">
{getRecipientDisplayText(field.recipient)} {getRecipientDisplayText(field.recipient)}
</p> </p>
<button <button
className="absolute right-0 top-0 my-1 p-2 focus:outline-none focus-visible:ring-0" className="absolute top-0 right-0 my-1 p-2 focus:outline-none focus-visible:ring-0"
onClick={() => setHideField(true)} onClick={() => setHideField(true)}
title="Hide field" title="Hide field"
> >
@@ -43,17 +43,13 @@ export const RecipientActionAuthSelect = ({
})), })),
]; ];
// Convert string array to Option array for MultiSelect const getSelectedOptions = (selectedValues?: string[]) =>
const selectedOptions = selectedValues
(value ?.map((selectedValue) => authOptions.find((option) => option.value === selectedValue))
?.map((val) => authOptions.find((option) => option.value === val)) .filter((option): option is Option => option !== undefined) ?? [];
.filter(Boolean) as Option[]) || [];
// Convert default value to Option array const selectedOptions = getSelectedOptions(value);
const defaultOptions = const defaultOptions = getSelectedOptions(defaultValue);
(defaultValue
?.map((val) => authOptions.find((option) => option.value === val))
.filter(Boolean) as Option[]) || [];
const handleChange = (options: Option[]) => { const handleChange = (options: Option[]) => {
const values = options.map((option) => option.value); const values = options.map((option) => option.value);
@@ -76,14 +72,14 @@ export const RecipientActionAuthSelect = ({
<Tooltip> <Tooltip>
<TooltipTrigger <TooltipTrigger
className={cn('absolute right-2 top-1/2 -translate-y-1/2', { className={cn('absolute top-1/2 right-2 -translate-y-1/2', {
'right-8': selectedOptions.length > 0, 'right-8': selectedOptions.length > 0,
})} })}
> >
<InfoIcon className="h-4 w-4" /> <InfoIcon className="h-4 w-4" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent className="max-w-md p-4 text-foreground"> <TooltipContent className="text-foreground max-w-md p-4">
<h2> <h2>
<strong> <strong>
<Trans>Recipient action authentication</Trans> <Trans>Recipient action authentication</Trans>
+1 -1
View File
@@ -37,7 +37,7 @@ function getBaseFontSize(): number {
} }
return parsed; return parsed;
} catch (error) { } catch {
// Fallback to browser default if anything goes wrong // Fallback to browser default if anything goes wrong
return 16; return 16;
} }
@@ -101,7 +101,7 @@ export const AddFieldsFormPartial = ({
onAutoSave, onAutoSave,
canGoBack = false, canGoBack = false,
isDocumentPdfLoaded, isDocumentPdfLoaded,
teamId, teamId: _teamId,
}: AddFieldsFormProps) => { }: AddFieldsFormProps) => {
const { toast } = useToast(); const { toast } = useToast();
const { _ } = useLingui(); const { _ } = useLingui();
@@ -535,22 +535,6 @@ export const AddFieldsFormPartial = ({
); );
}, [recipients]); }, [recipients]);
const recipientsByRole = useMemo(() => {
const recipientsByRole: Record<RecipientRole, Recipient[]> = {
CC: [],
VIEWER: [],
SIGNER: [],
APPROVER: [],
ASSISTANT: [],
};
recipients.forEach((recipient) => {
recipientsByRole[recipient.role].push(recipient);
});
return recipientsByRole;
}, [recipients]);
const handleAdvancedSettings = () => { const handleAdvancedSettings = () => {
setShowAdvancedSettings((prev) => !prev); setShowAdvancedSettings((prev) => !prev);
}; };
@@ -623,10 +607,10 @@ export const AddFieldsFormPartial = ({
{selectedField && ( {selectedField && (
<div <div
className={cn( className={cn(
'dark:text-muted-background pointer-events-none fixed z-50 flex cursor-pointer flex-col items-center justify-center rounded-[2px] bg-white text-muted-foreground ring-2 transition duration-200 [container-type:size]', 'dark:text-muted-background text-muted-foreground [container-type:size] pointer-events-none fixed z-50 flex cursor-pointer flex-col items-center justify-center rounded-[2px] bg-white ring-2 transition duration-200',
selectedSignerStyles?.base, selectedSignerStyles?.base,
{ {
'-rotate-6 scale-90 opacity-50 dark:bg-black/20': !isFieldWithinBounds, 'scale-90 -rotate-6 opacity-50 dark:bg-black/20': !isFieldWithinBounds,
'dark:text-black/60': isFieldWithinBounds, 'dark:text-black/60': isFieldWithinBounds,
}, },
)} )}
@@ -703,7 +687,7 @@ export const AddFieldsFormPartial = ({
selectedRecipient={selectedSigner} selectedRecipient={selectedSigner}
onSelectedRecipientChange={setSelectedSigner} onSelectedRecipientChange={setSelectedSigner}
recipients={recipients} recipients={recipients}
className="mb-12 mt-2" className="mt-2 mb-12"
/> />
)} )}
@@ -725,7 +709,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="flex flex-col items-center justify-center px-6 py-4"> <CardContent className="flex flex-col items-center justify-center px-6 py-4">
<p <p
className={cn( className={cn(
'flex items-center justify-center gap-x-1.5 font-signature text-lg font-normal text-muted-foreground group-data-[selected]:text-foreground', 'font-signature text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-lg font-normal',
)} )}
> >
<Trans>Signature</Trans> <Trans>Signature</Trans>
@@ -749,7 +733,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="flex flex-col items-center justify-center px-6 py-4"> <CardContent className="flex flex-col items-center justify-center px-6 py-4">
<p <p
className={cn( className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground', 'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
)} )}
> >
<Contact className="h-4 w-4" /> <Contact className="h-4 w-4" />
@@ -774,7 +758,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="flex flex-col items-center justify-center px-6 py-4"> <CardContent className="flex flex-col items-center justify-center px-6 py-4">
<p <p
className={cn( className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground', 'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
)} )}
> >
<Mail className="h-4 w-4" /> <Mail className="h-4 w-4" />
@@ -799,7 +783,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="p-4"> <CardContent className="p-4">
<p <p
className={cn( className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground', 'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
)} )}
> >
<User className="h-4 w-4" /> <User className="h-4 w-4" />
@@ -824,7 +808,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="p-4"> <CardContent className="p-4">
<p <p
className={cn( className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground', 'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
)} )}
> >
<CalendarDays className="h-4 w-4" /> <CalendarDays className="h-4 w-4" />
@@ -849,7 +833,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="p-4"> <CardContent className="p-4">
<p <p
className={cn( className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground', 'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
)} )}
> >
<Type className="h-4 w-4" /> <Type className="h-4 w-4" />
@@ -874,7 +858,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="p-4"> <CardContent className="p-4">
<p <p
className={cn( className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground', 'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
)} )}
> >
<Hash className="h-4 w-4" /> <Hash className="h-4 w-4" />
@@ -899,7 +883,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="p-4"> <CardContent className="p-4">
<p <p
className={cn( className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground', 'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
)} )}
> >
<Disc className="h-4 w-4" /> <Disc className="h-4 w-4" />
@@ -924,7 +908,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="p-4"> <CardContent className="p-4">
<p <p
className={cn( className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground', 'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
)} )}
> >
<CheckSquare className="h-4 w-4" /> <CheckSquare className="h-4 w-4" />
@@ -949,7 +933,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="p-4"> <CardContent className="p-4">
<p <p
className={cn( className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground', 'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
)} )}
> >
<ChevronDown className="h-4 w-4" /> <ChevronDown className="h-4 w-4" />
@@ -242,7 +242,7 @@ export const AddSettingsFormPartial = ({
<InfoIcon className="mx-2 h-4 w-4" /> <InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent className="max-w-md space-y-2 p-4 text-foreground"> <TooltipContent className="text-foreground max-w-md space-y-2 p-4">
<Trans> <Trans>
Controls the language for the document, including the language to be used Controls the language for the document, including the language to be used
for email notifications, and the final certificate that is generated and for email notifications, and the final certificate that is generated and
@@ -361,11 +361,11 @@ export const AddSettingsFormPartial = ({
<Accordion type="multiple" className="mt-6"> <Accordion type="multiple" className="mt-6">
<AccordionItem value="advanced-options" className="border-none"> <AccordionItem value="advanced-options" className="border-none">
<AccordionTrigger className="mb-2 rounded border px-3 py-2 text-left text-foreground hover:bg-neutral-200/30 hover:no-underline"> <AccordionTrigger className="text-foreground mb-2 rounded border px-3 py-2 text-left hover:bg-neutral-200/30 hover:no-underline">
<Trans>Advanced Options</Trans> <Trans>Advanced Options</Trans>
</AccordionTrigger> </AccordionTrigger>
<AccordionContent className="-mx-1 px-1 pt-2 text-sm leading-relaxed text-muted-foreground"> <AccordionContent className="text-muted-foreground -mx-1 px-1 pt-2 text-sm leading-relaxed">
<div className="flex flex-col space-y-6"> <div className="flex flex-col space-y-6">
<FormField <FormField
control={form.control} control={form.control}
@@ -379,7 +379,7 @@ export const AddSettingsFormPartial = ({
<InfoIcon className="mx-2 h-4 w-4" /> <InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent className="max-w-xs text-muted-foreground"> <TooltipContent className="text-muted-foreground max-w-xs">
<Trans> <Trans>
Add an external ID to the document. This can be used to identify Add an external ID to the document. This can be used to identify
the document in external systems. the document in external systems.
@@ -418,7 +418,7 @@ export const AddSettingsFormPartial = ({
field.onChange(value); field.onChange(value);
void handleAutoSave(); void handleAutoSave();
}} }}
className="w-full bg-background" className="bg-background w-full"
emptySelectionPlaceholder={t`Select signature types`} emptySelectionPlaceholder={t`Select signature types`}
/> />
</FormControl> </FormControl>
@@ -481,7 +481,10 @@ export const AddSettingsFormPartial = ({
options={TIME_ZONES} options={TIME_ZONES}
{...field} {...field}
onChange={(value) => { onChange={(value) => {
value && field.onChange(value); if (value) {
field.onChange(value);
}
void handleAutoSave(); void handleAutoSave();
}} }}
value={field.value} value={field.value}
@@ -506,7 +509,7 @@ export const AddSettingsFormPartial = ({
<InfoIcon className="mx-2 h-4 w-4" /> <InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent className="max-w-xs text-muted-foreground"> <TooltipContent className="text-muted-foreground max-w-xs">
<Trans> <Trans>
Add a URL to redirect the user to once the document is signed Add a URL to redirect the user to once the document is signed
</Trans> </Trans>
@@ -69,8 +69,8 @@ export type AddSubjectFormProps = {
export const AddSubjectFormPartial = ({ export const AddSubjectFormPartial = ({
documentFlow, documentFlow,
recipients: recipients, recipients,
fields: fields, fields,
document, document,
onSubmit, onSubmit,
onAutoSave, onAutoSave,
@@ -323,7 +323,7 @@ export const AddSubjectFormPartial = ({
<TooltipTrigger> <TooltipTrigger>
<InfoIcon className="mx-2 h-4 w-4" /> <InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent className="p-4 text-muted-foreground"> <TooltipContent className="text-muted-foreground p-4">
<DocumentSendEmailMessageHelper /> <DocumentSendEmailMessageHelper />
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
@@ -331,7 +331,7 @@ export const AddSubjectFormPartial = ({
<FormControl> <FormControl>
<Textarea <Textarea
className="mt-2 h-16 resize-none bg-background" className="bg-background mt-2 h-16 resize-none"
{...field} {...field}
maxLength={5000} maxLength={5000}
/> />
@@ -360,7 +360,7 @@ export const AddSubjectFormPartial = ({
className="rounded-lg border" className="rounded-lg border"
> >
{document.status === DocumentStatus.DRAFT ? ( {document.status === DocumentStatus.DRAFT ? (
<div className="py-16 text-center text-sm text-muted-foreground"> <div className="text-muted-foreground py-16 text-center text-sm">
<p> <p>
<Trans>We won't send anything to notify recipients.</Trans> <Trans>We won't send anything to notify recipients.</Trans>
</p> </p>
@@ -373,7 +373,7 @@ export const AddSubjectFormPartial = ({
</p> </p>
</div> </div>
) : ( ) : (
<ul className="divide-y text-muted-foreground"> <ul className="text-muted-foreground divide-y">
{recipients.length === 0 && ( {recipients.length === 0 && (
<li className="flex flex-col items-center justify-center py-6 text-sm"> <li className="flex flex-col items-center justify-center py-6 text-sm">
<Trans>No recipients</Trans> <Trans>No recipients</Trans>
@@ -388,10 +388,10 @@ export const AddSubjectFormPartial = ({
<AvatarWithText <AvatarWithText
avatarFallback={recipient.email.slice(0, 1).toUpperCase()} avatarFallback={recipient.email.slice(0, 1).toUpperCase()}
primaryText={ primaryText={
<p className="text-sm text-muted-foreground">{recipient.email}</p> <p className="text-muted-foreground text-sm">{recipient.email}</p>
} }
secondaryText={ secondaryText={
<p className="text-xs text-muted-foreground/70"> <p className="text-muted-foreground/70 text-xs">
{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)} {_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
</p> </p>
} }
@@ -31,7 +31,7 @@ export type DocumentUploadButtonProps = {
}; };
export const DocumentUploadButton = ({ export const DocumentUploadButton = ({
className, className: _className,
loading, loading,
onDrop, onDrop,
onDropRejected, onDropRejected,
+4 -4
View File
@@ -23,7 +23,7 @@ export interface FieldSelectorProps {
} }
export const FieldSelector = ({ export const FieldSelector = ({
className, className: _className,
selectedField, selectedField,
onSelectedFieldChange, onSelectedFieldChange,
disabled = false, disabled = false,
@@ -104,10 +104,10 @@ export const FieldSelector = ({
)} )}
> >
<CardContent className="relative flex items-center justify-center gap-x-2 px-6 py-4"> <CardContent className="relative flex items-center justify-center gap-x-2 px-6 py-4">
{Icon && <Icon className="h-4 w-4 text-muted-foreground" />} {Icon && <Icon className="text-muted-foreground h-4 w-4" />}
<span <span
className={cn( className={cn(
'text-sm text-muted-foreground group-data-[selected]:text-foreground', 'text-muted-foreground group-data-[selected]:text-foreground text-sm',
field.type === FieldType.SIGNATURE && 'invisible', field.type === FieldType.SIGNATURE && 'invisible',
)} )}
> >
@@ -115,7 +115,7 @@ export const FieldSelector = ({
</span> </span>
{field.type === FieldType.SIGNATURE && ( {field.type === FieldType.SIGNATURE && (
<div className="absolute inset-0 flex items-center justify-center font-signature text-lg text-muted-foreground"> <div className="font-signature text-muted-foreground absolute inset-0 flex items-center justify-center text-lg">
<Trans>Signature</Trans> <Trans>Signature</Trans>
</div> </div>
)} )}
@@ -5,7 +5,7 @@ import { cn } from '../../lib/utils';
export type FormErrorMessageProps = { export type FormErrorMessageProps = {
className?: string; className?: string;
error: { message?: string } | undefined | unknown; error: unknown;
}; };
const isErrorWithMessage = (error: unknown): error is { message?: string } => { const isErrorWithMessage = (error: unknown): error is { message?: string } => {
+23 -10
View File
@@ -99,17 +99,24 @@ function transToGroupOption(options: Option[], groupBy?: string) {
const groupOption: GroupOption = {}; const groupOption: GroupOption = {};
options.forEach((option) => { options.forEach((option) => {
const key = (option[groupBy] as string) || ''; const groupValue = option[groupBy];
const key = typeof groupValue === 'string' ? groupValue : groupValue ? String(groupValue) : '';
if (!groupOption[key]) { if (!groupOption[key]) {
groupOption[key] = []; groupOption[key] = [];
} }
groupOption[key].push(option); groupOption[key].push(option);
}); });
return groupOption; return groupOption;
} }
function removePickedOption(groupOption: GroupOption, picked: Option[]) { function removePickedOption(groupOption: GroupOption, picked: Option[]) {
const cloneOption = JSON.parse(JSON.stringify(groupOption)) as GroupOption; const cloneOption: GroupOption = {};
for (const [key, value] of Object.entries(groupOption)) {
cloneOption[key] = [...value];
}
for (const [key, value] of Object.entries(cloneOption)) { for (const [key, value] of Object.entries(cloneOption)) {
cloneOption[key] = value.filter((val) => !picked.find((p) => p.value === val.value)); cloneOption[key] = value.filter((val) => !picked.find((p) => p.value === val.value));
@@ -186,11 +193,17 @@ const MultiSelect = ({
const debouncedSearchTerm = useDebounce(inputValue, delay || 500); const debouncedSearchTerm = useDebounce(inputValue, delay || 500);
const handleClickOutside = (event: MouseEvent | TouchEvent) => { const handleClickOutside = (event: MouseEvent | TouchEvent) => {
const target = event.target;
if (!(target instanceof Node)) {
return;
}
if ( if (
dropdownRef.current && dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node) && !dropdownRef.current.contains(target) &&
inputRef.current && inputRef.current &&
!inputRef.current.contains(event.target as Node) !inputRef.current.contains(target)
) { ) {
setOpen(false); setOpen(false);
inputRef.current.blur(); inputRef.current.blur();
@@ -408,7 +421,7 @@ const MultiSelect = ({
> >
<div <div
className={cn( className={cn(
'has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40 has-aria-invalid:border-destructive has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 relative min-h-[38px] rounded-md border border-input text-sm outline-none transition-[color,box-shadow] focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50', 'has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40 has-aria-invalid:border-destructive border-input focus-within:border-ring focus-within:ring-ring/50 relative min-h-[38px] rounded-md border text-sm transition-[color,box-shadow] outline-none focus-within:ring-[3px] has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50',
{ {
'p-1': selected.length !== 0, 'p-1': selected.length !== 0,
'cursor-text': !disabled && selected.length !== 0, 'cursor-text': !disabled && selected.length !== 0,
@@ -427,7 +440,7 @@ const MultiSelect = ({
<div <div
key={option.value} key={option.value}
className={cn( className={cn(
'animate-fadeIn data-fixed:pe-2 relative inline-flex h-7 cursor-default items-center rounded-md border bg-background pe-7 pl-2 ps-2 text-xs font-medium text-secondary-foreground transition-all hover:bg-background disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50', 'animate-fadeIn bg-background text-secondary-foreground hover:bg-background relative inline-flex h-7 cursor-default items-center rounded-md border ps-2 pe-7 pl-2 text-xs font-medium transition-all disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 data-fixed:pe-2',
badgeClassName, badgeClassName,
)} )}
data-fixed={option.fixed} data-fixed={option.fixed}
@@ -435,7 +448,7 @@ const MultiSelect = ({
> >
{option.label} {option.label}
<button <button
className="absolute -inset-y-px -end-px flex size-7 items-center justify-center rounded-e-md border border-transparent p-0 text-muted-foreground/80 outline-none transition-[color,box-shadow] hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50" className="text-muted-foreground/80 hover:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 absolute -inset-y-px -end-px flex size-7 items-center justify-center rounded-e-md border border-transparent p-0 transition-[color,box-shadow] outline-none focus-visible:ring-[3px]"
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
handleUnselect(option); handleUnselect(option);
@@ -478,7 +491,7 @@ const MultiSelect = ({
}} }}
placeholder={hidePlaceholderWhenSelected && selected.length !== 0 ? '' : placeholder} placeholder={hidePlaceholderWhenSelected && selected.length !== 0 ? '' : placeholder}
className={cn( className={cn(
'flex-1 bg-transparent outline-none placeholder:text-muted-foreground/70 disabled:cursor-not-allowed', 'placeholder:text-muted-foreground/70 flex-1 bg-transparent outline-none disabled:cursor-not-allowed',
{ {
'w-full': hidePlaceholderWhenSelected, 'w-full': hidePlaceholderWhenSelected,
'px-3 py-2': selected.length === 0, 'px-3 py-2': selected.length === 0,
@@ -494,7 +507,7 @@ const MultiSelect = ({
onChange?.(selected.filter((s) => s.fixed)); onChange?.(selected.filter((s) => s.fixed));
}} }}
className={cn( className={cn(
'absolute end-0 top-0 flex size-9 items-center justify-center rounded-md border border-transparent text-muted-foreground/80 outline-none transition-[color,box-shadow] hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50', 'text-muted-foreground/80 hover:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 absolute end-0 top-0 flex size-9 items-center justify-center rounded-md border border-transparent transition-[color,box-shadow] outline-none focus-visible:ring-[3px]',
(hideClearAllButton || (hideClearAllButton ||
disabled || disabled ||
selected.length < 1 || selected.length < 1 ||
@@ -510,7 +523,7 @@ const MultiSelect = ({
<div className="relative"> <div className="relative">
<div <div
className={cn( className={cn(
'absolute top-2 z-10 w-full overflow-hidden rounded-md border border-input', 'border-input absolute top-2 z-10 w-full overflow-hidden rounded-md border',
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95', 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
!open && 'hidden', !open && 'hidden',
)} )}
+18 -24
View File
@@ -51,24 +51,18 @@ export const RecipientSelector = ({
}, [recipients]); }, [recipients]);
const recipientsByRoleToDisplay = useMemo(() => { const recipientsByRoleToDisplay = useMemo(() => {
return Object.entries(recipientsByRole) return [RecipientRole.SIGNER, RecipientRole.APPROVER].map((role) => {
.filter( const roleRecipients = recipientsByRole[role];
([role]) =>
role !== RecipientRole.CC && return [
role !== RecipientRole.VIEWER && role,
role !== RecipientRole.ASSISTANT, sortBy(
) roleRecipients,
.map( [(r) => r.signingOrder || Number.MAX_SAFE_INTEGER, 'asc'],
([role, roleRecipients]) => [(r) => r.id, 'asc'],
[ ),
role, ] satisfies [RecipientRole, Recipient[]];
sortBy( });
roleRecipients,
[(r) => r.signingOrder || Number.MAX_SAFE_INTEGER, 'asc'],
[(r) => r.id, 'asc'],
),
] as [RecipientRole, Recipient[]],
);
}, [recipientsByRole]); }, [recipientsByRole]);
const getRecipientLabel = useCallback( const getRecipientLabel = useCallback(
@@ -101,7 +95,7 @@ export const RecipientSelector = ({
variant="outline" variant="outline"
role="combobox" role="combobox"
className={cn( className={cn(
'justify-between bg-background font-normal text-muted-foreground hover:text-foreground', 'bg-background text-muted-foreground hover:text-foreground justify-between font-normal',
getRecipientColorStyles( getRecipientColorStyles(
Math.max( Math.max(
recipients.findIndex((r) => r.id === selectedRecipient?.id), recipients.findIndex((r) => r.id === selectedRecipient?.id),
@@ -126,21 +120,21 @@ export const RecipientSelector = ({
<CommandInput /> <CommandInput />
<CommandEmpty> <CommandEmpty>
<span className="inline-block px-4 text-muted-foreground"> <span className="text-muted-foreground inline-block px-4">
<Trans>No recipient matching this description was found.</Trans> <Trans>No recipient matching this description was found.</Trans>
</span> </span>
</CommandEmpty> </CommandEmpty>
{recipientsByRoleToDisplay.map(([role, roleRecipients], roleIndex) => ( {recipientsByRoleToDisplay.map(([role, roleRecipients], roleIndex) => (
<CommandGroup key={roleIndex}> <CommandGroup key={roleIndex}>
<div className="mb-1 ml-2 mt-2 text-xs font-medium text-muted-foreground"> <div className="text-muted-foreground mt-2 mb-1 ml-2 text-xs font-medium">
{_(RECIPIENT_ROLES_DESCRIPTION[role].roleNamePlural)} {_(RECIPIENT_ROLES_DESCRIPTION[role].roleNamePlural)}
</div> </div>
{roleRecipients.length === 0 && ( {roleRecipients.length === 0 && (
<div <div
key={`${role}-empty`} key={`${role}-empty`}
className="px-4 pb-4 pt-2.5 text-center text-xs text-muted-foreground/80" className="text-muted-foreground/80 px-4 pt-2.5 pb-4 text-center text-xs"
> >
<Trans>No recipients with this role</Trans> <Trans>No recipients with this role</Trans>
</div> </div>
@@ -168,7 +162,7 @@ export const RecipientSelector = ({
disabled={recipient.signingStatus !== SigningStatus.NOT_SIGNED} disabled={recipient.signingStatus !== SigningStatus.NOT_SIGNED}
> >
<span <span
className={cn('truncate text-foreground/70', { className={cn('text-foreground/70 truncate', {
'text-foreground/80': recipient.id === selectedRecipient?.id, 'text-foreground/80': recipient.id === selectedRecipient?.id,
})} })}
> >
@@ -190,7 +184,7 @@ export const RecipientSelector = ({
<Info className="ml-2 h-4 w-4" /> <Info className="ml-2 h-4 w-4" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent className="max-w-xs text-muted-foreground"> <TooltipContent className="text-muted-foreground max-w-xs">
<Trans> <Trans>
This document has already been sent to this recipient. You can no longer This document has already been sent to this recipient. You can no longer
edit this recipient. edit this recipient.
@@ -172,7 +172,9 @@ export class Canvas {
event.preventDefault(); event.preventDefault();
} }
event.buttons === 1 && this.onMouseDown(event); if (event.buttons === 1) {
this.onMouseDown(event);
}
} }
private onMouseLeave(event: MouseEvent): void { private onMouseLeave(event: MouseEvent): void {
@@ -5,7 +5,6 @@ import type { MessageDescriptor } from '@lingui/core';
import { Trans, useLingui } from '@lingui/react/macro'; import { Trans, useLingui } from '@lingui/react/macro';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
import { Dialog, DialogClose, DialogContent, DialogFooter } from '@documenso/ui/primitives/dialog'; import { Dialog, DialogClose, DialogContent, DialogFooter } from '@documenso/ui/primitives/dialog';
import { cn } from '../../lib/utils'; import { cn } from '../../lib/utils';
@@ -45,7 +44,7 @@ export const SignaturePadDialog = ({
return ( return (
<div <div
className={cn( className={cn(
'relative block aspect-signature-pad w-full select-none rounded-lg border bg-background', 'aspect-signature-pad bg-background relative block w-full rounded-lg border select-none',
className, className,
{ {
'pointer-events-none opacity-50': disabled, 'pointer-events-none opacity-50': disabled,
@@ -140,7 +139,11 @@ export const SignaturePadDialog = ({
}} }}
> >
{dialogConfirmText ? ( {dialogConfirmText ? (
parseMessageDescriptor(i18n._, dialogConfirmText) typeof dialogConfirmText === 'string' ? (
dialogConfirmText
) : (
i18n._(dialogConfirmText)
)
) : ( ) : (
<Trans>Next</Trans> <Trans>Next</Trans>
)} )}
+2 -2
View File
@@ -64,7 +64,7 @@ export const Stepper: FC<StepperProps> = ({
const nextStep = () => { const nextStep = () => {
if (currentStep < totalSteps) { if (currentStep < totalSteps) {
void handleStepChange(currentStep + 1); handleStepChange(currentStep + 1);
} else { } else {
void handleComplete(); void handleComplete();
} }
@@ -72,7 +72,7 @@ export const Stepper: FC<StepperProps> = ({
const previousStep = () => { const previousStep = () => {
if (currentStep > 1) { if (currentStep > 1) {
void handleStepChange(currentStep - 1); handleStepChange(currentStep - 1);
} }
}; };
@@ -88,7 +88,7 @@ export const AddTemplateFieldsFormPartial = ({
fields, fields,
onSubmit, onSubmit,
onAutoSave, onAutoSave,
teamId, teamId: _teamId,
}: AddTemplateFieldsFormProps) => { }: AddTemplateFieldsFormProps) => {
const { _ } = useLingui(); const { _ } = useLingui();
const { toast } = useToast(); const { toast } = useToast();
@@ -581,10 +581,10 @@ export const AddTemplateFieldsFormPartial = ({
{selectedField && ( {selectedField && (
<div <div
className={cn( className={cn(
'dark:text-muted-background pointer-events-none fixed z-50 flex cursor-pointer flex-col items-center justify-center rounded-[2px] bg-white text-muted-foreground ring-2 transition duration-200 [container-type:size]', 'dark:text-muted-background text-muted-foreground [container-type:size] pointer-events-none fixed z-50 flex cursor-pointer flex-col items-center justify-center rounded-[2px] bg-white ring-2 transition duration-200',
selectedSignerStyles?.base, selectedSignerStyles?.base,
{ {
'-rotate-6 scale-90 opacity-50 dark:bg-black/20': !isFieldWithinBounds, 'scale-90 -rotate-6 opacity-50 dark:bg-black/20': !isFieldWithinBounds,
'dark:text-black/60': isFieldWithinBounds, 'dark:text-black/60': isFieldWithinBounds,
}, },
)} )}
@@ -650,7 +650,7 @@ export const AddTemplateFieldsFormPartial = ({
variant="outline" variant="outline"
role="combobox" role="combobox"
className={cn( className={cn(
'mb-12 mt-2 justify-between bg-background font-normal text-muted-foreground hover:text-foreground', 'bg-background text-muted-foreground hover:text-foreground mt-2 mb-12 justify-between font-normal',
selectedSignerStyles?.comboxBoxTrigger, selectedSignerStyles?.comboxBoxTrigger,
)} )}
> >
@@ -681,7 +681,7 @@ export const AddTemplateFieldsFormPartial = ({
<CommandInput /> <CommandInput />
<CommandEmpty> <CommandEmpty>
<span className="inline-block px-4 text-muted-foreground"> <span className="text-muted-foreground inline-block px-4">
<Trans>No recipient matching this description was found.</Trans> <Trans>No recipient matching this description was found.</Trans>
</span> </span>
</CommandEmpty> </CommandEmpty>
@@ -689,14 +689,14 @@ export const AddTemplateFieldsFormPartial = ({
{/* Note: This is duplicated in `add-fields.tsx` */} {/* Note: This is duplicated in `add-fields.tsx` */}
{recipientsByRoleToDisplay.map(([role, roleRecipients], roleIndex) => ( {recipientsByRoleToDisplay.map(([role, roleRecipients], roleIndex) => (
<CommandGroup key={roleIndex}> <CommandGroup key={roleIndex}>
<div className="mb-1 ml-2 mt-2 text-xs font-medium text-muted-foreground"> <div className="text-muted-foreground mt-2 mb-1 ml-2 text-xs font-medium">
{_(RECIPIENT_ROLES_DESCRIPTION[role].roleNamePlural)} {_(RECIPIENT_ROLES_DESCRIPTION[role].roleNamePlural)}
</div> </div>
{roleRecipients.length === 0 && ( {roleRecipients.length === 0 && (
<div <div
key={`${role}-empty`} key={`${role}-empty`}
className="px-4 pb-4 pt-2.5 text-center text-xs text-muted-foreground/80" className="text-muted-foreground/80 px-4 pt-2.5 pb-4 text-center text-xs"
> >
<Trans>No recipients with this role</Trans> <Trans>No recipients with this role</Trans>
</div> </div>
@@ -720,7 +720,7 @@ export const AddTemplateFieldsFormPartial = ({
}} }}
> >
<span <span
className={cn('truncate text-foreground/70', { className={cn('text-foreground/70 truncate', {
'text-foreground/80': recipient === selectedSigner, 'text-foreground/80': recipient === selectedSigner,
})} })}
> >
@@ -768,7 +768,7 @@ export const AddTemplateFieldsFormPartial = ({
<CardContent className="flex flex-col items-center justify-center px-6 py-4"> <CardContent className="flex flex-col items-center justify-center px-6 py-4">
<p <p
className={cn( className={cn(
'flex items-center justify-center gap-x-1.5 font-signature text-lg font-normal text-muted-foreground group-data-[selected]:text-foreground', 'font-signature text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-lg font-normal',
)} )}
> >
<Trans>Signature</Trans> <Trans>Signature</Trans>
@@ -793,7 +793,7 @@ export const AddTemplateFieldsFormPartial = ({
<CardContent className="flex flex-col items-center justify-center px-6 py-4"> <CardContent className="flex flex-col items-center justify-center px-6 py-4">
<p <p
className={cn( className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground', 'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
)} )}
> >
<Contact className="h-4 w-4" /> <Contact className="h-4 w-4" />
@@ -819,7 +819,7 @@ export const AddTemplateFieldsFormPartial = ({
<CardContent className="p-4"> <CardContent className="p-4">
<p <p
className={cn( className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground', 'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
)} )}
> >
<Mail className="h-4 w-4" /> <Mail className="h-4 w-4" />
@@ -845,7 +845,7 @@ export const AddTemplateFieldsFormPartial = ({
<CardContent className="p-4"> <CardContent className="p-4">
<p <p
className={cn( className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground', 'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
)} )}
> >
<User className="h-4 w-4" /> <User className="h-4 w-4" />
@@ -871,7 +871,7 @@ export const AddTemplateFieldsFormPartial = ({
<CardContent className="p-4"> <CardContent className="p-4">
<p <p
className={cn( className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground', 'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
)} )}
> >
<CalendarDays className="h-4 w-4" /> <CalendarDays className="h-4 w-4" />
@@ -897,7 +897,7 @@ export const AddTemplateFieldsFormPartial = ({
<CardContent className="p-4"> <CardContent className="p-4">
<p <p
className={cn( className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground', 'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
)} )}
> >
<Type className="h-4 w-4" /> <Type className="h-4 w-4" />
@@ -923,7 +923,7 @@ export const AddTemplateFieldsFormPartial = ({
<CardContent className="p-4"> <CardContent className="p-4">
<p <p
className={cn( className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground', 'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
)} )}
> >
<Hash className="h-4 w-4" /> <Hash className="h-4 w-4" />
@@ -949,7 +949,7 @@ export const AddTemplateFieldsFormPartial = ({
<CardContent className="p-4"> <CardContent className="p-4">
<p <p
className={cn( className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground', 'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
)} )}
> >
<Disc className="h-4 w-4" /> <Disc className="h-4 w-4" />
@@ -975,7 +975,7 @@ export const AddTemplateFieldsFormPartial = ({
<CardContent className="p-4"> <CardContent className="p-4">
<p <p
className={cn( className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground', 'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
)} )}
> >
<CheckSquare className="h-4 w-4" /> <CheckSquare className="h-4 w-4" />
@@ -1001,7 +1001,7 @@ export const AddTemplateFieldsFormPartial = ({
<CardContent className="p-4"> <CardContent className="p-4">
<p <p
className={cn( className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground', 'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
)} )}
> >
<ChevronDown className="h-4 w-4" /> <ChevronDown className="h-4 w-4" />
@@ -239,7 +239,7 @@ export const AddTemplateSettingsFormPartial = ({
<InfoIcon className="mx-2 h-4 w-4" /> <InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent className="max-w-md space-y-2 p-4 text-foreground"> <TooltipContent className="text-foreground max-w-md space-y-2 p-4">
Controls the language for the document, including the language to be used Controls the language for the document, including the language to be used
for email notifications, and the final certificate that is generated and for email notifications, and the final certificate that is generated and
attached to the document. attached to the document.
@@ -337,7 +337,7 @@ export const AddTemplateSettingsFormPartial = ({
<InfoIcon className="mx-2 h-4 w-4" /> <InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent className="max-w-md space-y-2 p-4 text-foreground"> <TooltipContent className="text-foreground max-w-md space-y-2 p-4">
<h2> <h2>
<strong> <strong>
<Trans>Document Distribution Method</Trans> <Trans>Document Distribution Method</Trans>
@@ -423,7 +423,7 @@ export const AddTemplateSettingsFormPartial = ({
field.onChange(value); field.onChange(value);
void handleAutoSave(); void handleAutoSave();
}} }}
className="w-full bg-background" className="bg-background w-full"
emptySelectionPlaceholder={t`Select signature types`} emptySelectionPlaceholder={t`Select signature types`}
/> />
</FormControl> </FormControl>
@@ -463,11 +463,11 @@ export const AddTemplateSettingsFormPartial = ({
{distributionMethod === DocumentDistributionMethod.EMAIL && ( {distributionMethod === DocumentDistributionMethod.EMAIL && (
<Accordion type="multiple"> <Accordion type="multiple">
<AccordionItem value="email-options" className="border-none"> <AccordionItem value="email-options" className="border-none">
<AccordionTrigger className="rounded border px-3 py-2 text-left text-foreground hover:bg-neutral-200/30 hover:no-underline"> <AccordionTrigger className="text-foreground rounded border px-3 py-2 text-left hover:bg-neutral-200/30 hover:no-underline">
<Trans>Email Options</Trans> <Trans>Email Options</Trans>
</AccordionTrigger> </AccordionTrigger>
<AccordionContent className="-mx-1 px-1 pt-4 text-sm leading-relaxed text-muted-foreground [&>div]:pb-0"> <AccordionContent className="text-muted-foreground -mx-1 px-1 pt-4 text-sm leading-relaxed [&>div]:pb-0">
<div className="flex flex-col space-y-6"> <div className="flex flex-col space-y-6">
{organisation.organisationClaim.flags.emailDomains && ( {organisation.organisationClaim.flags.emailDomains && (
<FormField <FormField
@@ -566,7 +566,7 @@ export const AddTemplateSettingsFormPartial = ({
<TooltipTrigger> <TooltipTrigger>
<InfoIcon className="mx-2 h-4 w-4" /> <InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent className="p-4 text-muted-foreground"> <TooltipContent className="text-muted-foreground p-4">
<DocumentSendEmailMessageHelper /> <DocumentSendEmailMessageHelper />
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
@@ -574,7 +574,7 @@ export const AddTemplateSettingsFormPartial = ({
<FormControl> <FormControl>
<Textarea <Textarea
className="h-16 resize-none bg-background" className="bg-background h-16 resize-none"
{...field} {...field}
maxLength={5000} maxLength={5000}
onBlur={handleAutoSave} onBlur={handleAutoSave}
@@ -603,11 +603,11 @@ export const AddTemplateSettingsFormPartial = ({
<Accordion type="multiple"> <Accordion type="multiple">
<AccordionItem value="advanced-options" className="border-none"> <AccordionItem value="advanced-options" className="border-none">
<AccordionTrigger className="rounded border px-3 py-2 text-left text-foreground hover:bg-neutral-200/30 hover:no-underline"> <AccordionTrigger className="text-foreground rounded border px-3 py-2 text-left hover:bg-neutral-200/30 hover:no-underline">
<Trans>Advanced Options</Trans> <Trans>Advanced Options</Trans>
</AccordionTrigger> </AccordionTrigger>
<AccordionContent className="-mx-1 px-1 pt-4 text-sm leading-relaxed text-muted-foreground"> <AccordionContent className="text-muted-foreground -mx-1 px-1 pt-4 text-sm leading-relaxed">
<div className="flex flex-col space-y-6"> <div className="flex flex-col space-y-6">
<FormField <FormField
control={form.control} control={form.control}
@@ -621,7 +621,7 @@ export const AddTemplateSettingsFormPartial = ({
<InfoIcon className="mx-2 h-4 w-4" /> <InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent className="max-w-xs text-muted-foreground"> <TooltipContent className="text-muted-foreground max-w-xs">
<Trans> <Trans>
Add an external ID to the template. This can be used to identify Add an external ID to the template. This can be used to identify
in external systems. in external systems.
@@ -695,7 +695,10 @@ export const AddTemplateSettingsFormPartial = ({
options={TIME_ZONES} options={TIME_ZONES}
{...field} {...field}
onChange={(value) => { onChange={(value) => {
value && field.onChange(value); if (value) {
field.onChange(value);
}
void handleAutoSave(); void handleAutoSave();
}} }}
/> />
@@ -718,7 +721,7 @@ export const AddTemplateSettingsFormPartial = ({
<InfoIcon className="mx-2 h-4 w-4" /> <InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent className="max-w-xs text-muted-foreground"> <TooltipContent className="text-muted-foreground max-w-xs">
<Trans> <Trans>
Add a URL to redirect the user to once the document is signed Add a URL to redirect the user to once the document is signed
</Trans> </Trans>