mirror of
https://github.com/documenso/documenso.git
synced 2025-11-21 04:01:45 +10:00
Compare commits
7 Commits
v2.0.12
...
docs/self-
| Author | SHA1 | Date | |
|---|---|---|---|
| 18ad50bf5a | |||
| 11d9bde8f8 | |||
| fa1680aaf1 | |||
| 798b6bd750 | |||
| 8fbace0f61 | |||
| 1bbd04be9b | |||
| 6aa56fe7e0 |
@ -275,7 +275,15 @@ The environment variables listed above are a subset of those available for confi
|
||||
| `NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY` | The secondary encryption key for symmetric encryption and decryption (at least 32 characters). |
|
||||
| `NEXT_PRIVATE_GOOGLE_CLIENT_ID` | The Google client ID for Google authentication (optional). |
|
||||
| `NEXT_PRIVATE_GOOGLE_CLIENT_SECRET` | The Google client secret for Google authentication (optional). |
|
||||
| `NEXT_PRIVATE_MICROSOFT_CLIENT_ID` | The Microsoft client ID for Microsoft authentication (optional). |
|
||||
| `NEXT_PRIVATE_MICROSOFT_CLIENT_SECRET` | The Microsoft client secret for Microsoft authentication (optional). |
|
||||
| `NEXT_PRIVATE_OIDC_CLIENT_ID` | The OIDC client ID for OIDC authentication (optional). |
|
||||
| `NEXT_PRIVATE_OIDC_CLIENT_SECRET` | The OIDC client secret for OIDC authentication (optional). |
|
||||
| `NEXT_PRIVATE_OIDC_WELL_KNOWN` | The well-known URL for the OIDC provider (optional). |
|
||||
| `NEXT_PRIVATE_OIDC_PROVIDER_LABEL` | The label to display for the OIDC provider button (optional). |
|
||||
| `NEXT_PRIVATE_OIDC_SKIP_VERIFY` | Whether to skip email verification for OIDC accounts (optional, default `false`). |
|
||||
| `NEXT_PUBLIC_WEBAPP_URL` | The URL for the web application. |
|
||||
| `NEXT_PUBLIC_SUPPORT_EMAIL` | The support email address displayed to users (default `support@documenso.com`). |
|
||||
| `NEXT_PRIVATE_DATABASE_URL` | The URL for the primary database connection (with connection pooling). |
|
||||
| `NEXT_PRIVATE_DIRECT_DATABASE_URL` | The URL for the direct database connection (without connection pooling). |
|
||||
| `NEXT_PRIVATE_SIGNING_TRANSPORT` | The signing transport to use. Available options: local (default) |
|
||||
@ -297,6 +305,7 @@ The environment variables listed above are a subset of those available for confi
|
||||
| `NEXT_PRIVATE_SMTP_APIKEY_USER` | The API key user for the SMTP server for the `smtp-api` transport. |
|
||||
| `NEXT_PRIVATE_SMTP_APIKEY` | The API key for the SMTP server for the `smtp-api` transport. |
|
||||
| `NEXT_PRIVATE_SMTP_SECURE` | Whether to force the use of TLS for the SMTP server for SMTP transports. |
|
||||
| `NEXT_PRIVATE_SMTP_UNSAFE_IGNORE_TLS` | Whether to ignore TLS errors for the SMTP server (useful for self-signed certificates). |
|
||||
| `NEXT_PRIVATE_SMTP_FROM_ADDRESS` | The email address for the "from" address. |
|
||||
| `NEXT_PRIVATE_SMTP_FROM_NAME` | The sender name for the "from" address. |
|
||||
| `NEXT_PRIVATE_RESEND_API_KEY` | The API key for Resend.com for the `resend` transport. |
|
||||
@ -308,6 +317,7 @@ The environment variables listed above are a subset of those available for confi
|
||||
| `NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT` | The maximum document upload limit displayed to the user (in MB). |
|
||||
| `NEXT_PUBLIC_POSTHOG_KEY` | The optional PostHog key for analytics and feature flags. |
|
||||
| `NEXT_PUBLIC_DISABLE_SIGNUP` | Whether to disable user signups through the /signup page. |
|
||||
| `NEXT_PRIVATE_BROWSERLESS_URL` | The URL for a Browserless.io instance to generate PDFs (optional). |
|
||||
|
||||
## Run as a Service
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import { msg } from '@lingui/core/macro';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { FieldType, RecipientRole } from '@prisma/client';
|
||||
import { FileTextIcon } from 'lucide-react';
|
||||
import { Link } from 'react-router';
|
||||
import { Link, useSearchParams } from 'react-router';
|
||||
import { isDeepEqual } from 'remeda';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
@ -65,6 +65,8 @@ const FieldSettingsTypeTranslations: Record<FieldType, MessageDescriptor> = {
|
||||
};
|
||||
|
||||
export const EnvelopeEditorFieldsPage = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const { envelope, editorFields, relativePath } = useCurrentEnvelopeEditor();
|
||||
|
||||
const { currentEnvelopeItem } = useCurrentEnvelopeRender();
|
||||
@ -208,6 +210,37 @@ export const EnvelopeEditorFieldsPage = () => {
|
||||
<section>
|
||||
<Separator className="my-4" />
|
||||
|
||||
{searchParams.get('devmode') && (
|
||||
<>
|
||||
<div className="px-4">
|
||||
<h3 className="text-foreground mb-3 text-sm font-semibold">
|
||||
<Trans>Developer Mode</Trans>
|
||||
</h3>
|
||||
|
||||
<div className="bg-muted/50 border-border text-foreground space-y-2 rounded-md border p-3 text-sm">
|
||||
<p>
|
||||
<span className="text-muted-foreground min-w-12">Pos X: </span>
|
||||
{selectedField.positionX.toFixed(2)}
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-muted-foreground min-w-12">Pos Y: </span>
|
||||
{selectedField.positionY.toFixed(2)}
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-muted-foreground min-w-12">Width: </span>
|
||||
{selectedField.width.toFixed(2)}
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-muted-foreground min-w-12">Height: </span>
|
||||
{selectedField.height.toFixed(2)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator className="my-4" />
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="[&_label]:text-foreground/70 px-4 [&_label]:text-xs">
|
||||
<h3 className="text-sm font-semibold">
|
||||
{t(FieldSettingsTypeTranslations[selectedField.type])}
|
||||
|
||||
@ -106,5 +106,5 @@
|
||||
"vite-plugin-babel-macros": "^1.0.6",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
},
|
||||
"version": "2.0.12"
|
||||
"version": "2.0.13"
|
||||
}
|
||||
|
||||
6
package-lock.json
generated
6
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@documenso/root",
|
||||
"version": "2.0.12",
|
||||
"version": "2.0.13",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@documenso/root",
|
||||
"version": "2.0.12",
|
||||
"version": "2.0.13",
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
"packages/*"
|
||||
@ -101,7 +101,7 @@
|
||||
},
|
||||
"apps/remix": {
|
||||
"name": "@documenso/remix",
|
||||
"version": "2.0.12",
|
||||
"version": "2.0.13",
|
||||
"dependencies": {
|
||||
"@cantoo/pdf-lib": "^2.5.2",
|
||||
"@documenso/api": "*",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "2.0.12",
|
||||
"version": "2.0.13",
|
||||
"scripts": {
|
||||
"build": "turbo run build",
|
||||
"dev": "turbo run dev --filter=@documenso/remix",
|
||||
|
||||
@ -1,6 +1,16 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const SUPPORTED_LANGUAGE_CODES = ['de', 'en', 'fr', 'es', 'it', 'pl'] as const;
|
||||
export const SUPPORTED_LANGUAGE_CODES = [
|
||||
'de',
|
||||
'en',
|
||||
'fr',
|
||||
'es',
|
||||
'it',
|
||||
'pl',
|
||||
'ja',
|
||||
'ko',
|
||||
'zh',
|
||||
] as const;
|
||||
|
||||
export const ZSupportedLanguageCodeSchema = z.enum(SUPPORTED_LANGUAGE_CODES).catch('en');
|
||||
|
||||
@ -54,6 +64,18 @@ export const SUPPORTED_LANGUAGES: Record<string, SupportedLanguage> = {
|
||||
short: 'pl',
|
||||
full: 'Polish',
|
||||
},
|
||||
ja: {
|
||||
short: 'ja',
|
||||
full: 'Japanese',
|
||||
},
|
||||
ko: {
|
||||
short: 'ko',
|
||||
full: 'Korean',
|
||||
},
|
||||
zh: {
|
||||
short: 'zh',
|
||||
full: 'Chinese',
|
||||
},
|
||||
} satisfies Record<SupportedLanguageCodes, SupportedLanguage>;
|
||||
|
||||
export const isValidLanguageCode = (code: unknown): code is SupportedLanguageCodes =>
|
||||
|
||||
@ -25,7 +25,6 @@ import { signPdf } from '@documenso/signing';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../../errors/app-error';
|
||||
import { sendCompletedEmail } from '../../../server-only/document/send-completed-email';
|
||||
import PostHogServerClient from '../../../server-only/feature-flags/get-post-hog-server-client';
|
||||
import { getAuditLogsPdf } from '../../../server-only/htmltopdf/get-audit-logs-pdf';
|
||||
import { getCertificatePdf } from '../../../server-only/htmltopdf/get-certificate-pdf';
|
||||
import { addRejectionStampToPdf } from '../../../server-only/pdf/add-rejection-stamp-to-pdf';
|
||||
@ -62,171 +61,120 @@ export const run = async ({
|
||||
}) => {
|
||||
const { documentId, sendEmail = true, isResealing = false, requestMetadata } = payload;
|
||||
|
||||
const envelope = await prisma.envelope.findFirstOrThrow({
|
||||
where: {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
secondaryId: mapDocumentIdToSecondaryId(documentId),
|
||||
},
|
||||
include: {
|
||||
documentMeta: true,
|
||||
recipients: true,
|
||||
envelopeItems: {
|
||||
include: {
|
||||
documentData: true,
|
||||
field: {
|
||||
include: {
|
||||
signature: true,
|
||||
const { envelopeId, envelopeStatus, isRejected } = await io.runTask('seal-document', async () => {
|
||||
const envelope = await prisma.envelope.findFirstOrThrow({
|
||||
where: {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
secondaryId: mapDocumentIdToSecondaryId(documentId),
|
||||
},
|
||||
include: {
|
||||
documentMeta: true,
|
||||
recipients: true,
|
||||
envelopeItems: {
|
||||
include: {
|
||||
documentData: true,
|
||||
field: {
|
||||
include: {
|
||||
signature: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (envelope.envelopeItems.length === 0) {
|
||||
throw new Error('At least one envelope item required');
|
||||
}
|
||||
|
||||
const settings = await getTeamSettings({
|
||||
userId: envelope.userId,
|
||||
teamId: envelope.teamId,
|
||||
});
|
||||
|
||||
const isComplete =
|
||||
envelope.recipients.some((recipient) => recipient.signingStatus === SigningStatus.REJECTED) ||
|
||||
envelope.recipients.every((recipient) => recipient.signingStatus === SigningStatus.SIGNED);
|
||||
|
||||
if (!isComplete) {
|
||||
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
||||
message: 'Document is not complete',
|
||||
});
|
||||
}
|
||||
|
||||
// Seems silly but we need to do this in case the job is re-ran
|
||||
// after it has already run through the update task further below.
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
const documentStatus = await io.runTask('get-document-status', async () => {
|
||||
return envelope.status;
|
||||
});
|
||||
if (envelope.envelopeItems.length === 0) {
|
||||
throw new Error('At least one envelope item required');
|
||||
}
|
||||
|
||||
// This is the same case as above.
|
||||
let envelopeItems = await io.runTask(
|
||||
'get-document-data-id',
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
async () => {
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
return envelope.envelopeItems.map(({ field, ...rest }) => ({
|
||||
...rest,
|
||||
}));
|
||||
},
|
||||
);
|
||||
const settings = await getTeamSettings({
|
||||
userId: envelope.userId,
|
||||
teamId: envelope.teamId,
|
||||
});
|
||||
|
||||
if (envelopeItems.length < 1) {
|
||||
throw new Error(`Document ${envelope.id} has no envelope items`);
|
||||
}
|
||||
const isComplete =
|
||||
envelope.recipients.some((recipient) => recipient.signingStatus === SigningStatus.REJECTED) ||
|
||||
envelope.recipients.every((recipient) => recipient.signingStatus === SigningStatus.SIGNED);
|
||||
|
||||
const recipients = await prisma.recipient.findMany({
|
||||
where: {
|
||||
envelopeId: envelope.id,
|
||||
role: {
|
||||
not: RecipientRole.CC,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!isComplete) {
|
||||
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
||||
message: 'Document is not complete',
|
||||
});
|
||||
}
|
||||
|
||||
// Determine if the document has been rejected by checking if any recipient has rejected it
|
||||
const rejectedRecipient = recipients.find(
|
||||
(recipient) => recipient.signingStatus === SigningStatus.REJECTED,
|
||||
);
|
||||
let envelopeItems = envelope.envelopeItems;
|
||||
|
||||
const isRejected = Boolean(rejectedRecipient);
|
||||
if (envelopeItems.length < 1) {
|
||||
throw new Error(`Document ${envelope.id} has no envelope items`);
|
||||
}
|
||||
|
||||
// Get the rejection reason from the rejected recipient
|
||||
const rejectionReason = rejectedRecipient?.rejectionReason ?? '';
|
||||
|
||||
const fields = await prisma.field.findMany({
|
||||
where: {
|
||||
envelopeId: envelope.id,
|
||||
},
|
||||
include: {
|
||||
signature: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Skip the field check if the document is rejected
|
||||
if (!isRejected && fieldsContainUnsignedRequiredField(fields)) {
|
||||
throw new Error(`Document ${envelope.id} has unsigned required fields`);
|
||||
}
|
||||
|
||||
if (isResealing) {
|
||||
// If we're resealing we want to use the initial data for the document
|
||||
// so we aren't placing fields on top of eachother.
|
||||
envelopeItems = envelopeItems.map((envelopeItem) => ({
|
||||
...envelopeItem,
|
||||
documentData: {
|
||||
...envelopeItem.documentData,
|
||||
data: envelopeItem.documentData.initialData,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
if (!envelope.qrToken) {
|
||||
await prisma.envelope.update({
|
||||
const recipients = await prisma.recipient.findMany({
|
||||
where: {
|
||||
id: envelope.id,
|
||||
},
|
||||
data: {
|
||||
qrToken: prefixedId('qr'),
|
||||
envelopeId: envelope.id,
|
||||
role: {
|
||||
not: RecipientRole.CC,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const legacyDocumentId = mapSecondaryIdToDocumentId(envelope.secondaryId);
|
||||
// Determine if the document has been rejected by checking if any recipient has rejected it
|
||||
const rejectedRecipient = recipients.find(
|
||||
(recipient) => recipient.signingStatus === SigningStatus.REJECTED,
|
||||
);
|
||||
|
||||
const { certificateData, auditLogData } = await getCertificateAndAuditLogData({
|
||||
legacyDocumentId,
|
||||
documentMeta: envelope.documentMeta,
|
||||
settings,
|
||||
});
|
||||
const isRejected = Boolean(rejectedRecipient);
|
||||
|
||||
// !: The commented out code is our desired implementation but we're seemingly
|
||||
// !: running into issues with inngest parallelism in production.
|
||||
// !: Until this is resolved we will do this sequentially which is slower but
|
||||
// !: will actually work.
|
||||
// const decoratePromises: Array<Promise<{ oldDocumentDataId: string; newDocumentDataId: string }>> =
|
||||
// [];
|
||||
// Get the rejection reason from the rejected recipient
|
||||
const rejectionReason = rejectedRecipient?.rejectionReason ?? '';
|
||||
|
||||
// for (const envelopeItem of envelopeItems) {
|
||||
// const task = io.runTask(`decorate-${envelopeItem.id}`, async () => {
|
||||
// const envelopeItemFields = envelope.envelopeItems.find(
|
||||
// (item) => item.id === envelopeItem.id,
|
||||
// )?.field;
|
||||
const fields = await prisma.field.findMany({
|
||||
where: {
|
||||
envelopeId: envelope.id,
|
||||
},
|
||||
include: {
|
||||
signature: true,
|
||||
},
|
||||
});
|
||||
|
||||
// if (!envelopeItemFields) {
|
||||
// throw new Error(`Envelope item fields not found for envelope item ${envelopeItem.id}`);
|
||||
// }
|
||||
// Skip the field check if the document is rejected
|
||||
if (!isRejected && fieldsContainUnsignedRequiredField(fields)) {
|
||||
throw new Error(`Document ${envelope.id} has unsigned required fields`);
|
||||
}
|
||||
|
||||
// return decorateAndSignPdf({
|
||||
// envelope,
|
||||
// envelopeItem,
|
||||
// envelopeItemFields,
|
||||
// isRejected,
|
||||
// rejectionReason,
|
||||
// certificateData,
|
||||
// auditLogData,
|
||||
// });
|
||||
// });
|
||||
if (isResealing) {
|
||||
// If we're resealing we want to use the initial data for the document
|
||||
// so we aren't placing fields on top of eachother.
|
||||
envelopeItems = envelopeItems.map((envelopeItem) => ({
|
||||
...envelopeItem,
|
||||
documentData: {
|
||||
...envelopeItem.documentData,
|
||||
data: envelopeItem.documentData.initialData,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
// decoratePromises.push(task);
|
||||
// }
|
||||
if (!envelope.qrToken) {
|
||||
await prisma.envelope.update({
|
||||
where: {
|
||||
id: envelope.id,
|
||||
},
|
||||
data: {
|
||||
qrToken: prefixedId('qr'),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// const newDocumentData = await Promise.all(decoratePromises);
|
||||
const legacyDocumentId = mapSecondaryIdToDocumentId(envelope.secondaryId);
|
||||
|
||||
// TODO: Remove once parallelization is working
|
||||
const newDocumentData: Array<{ oldDocumentDataId: string; newDocumentDataId: string }> = [];
|
||||
const { certificateData, auditLogData } = await getCertificateAndAuditLogData({
|
||||
legacyDocumentId,
|
||||
documentMeta: envelope.documentMeta,
|
||||
settings,
|
||||
});
|
||||
|
||||
for (const envelopeItem of envelopeItems) {
|
||||
const result = await io.runTask(`decorate-${envelopeItem.id}`, async () => {
|
||||
const newDocumentData: Array<{ oldDocumentDataId: string; newDocumentDataId: string }> = [];
|
||||
|
||||
for (const envelopeItem of envelopeItems) {
|
||||
const envelopeItemFields = envelope.envelopeItems.find(
|
||||
(item) => item.id === envelopeItem.id,
|
||||
)?.field;
|
||||
@ -235,7 +183,7 @@ export const run = async ({
|
||||
throw new Error(`Envelope item fields not found for envelope item ${envelopeItem.id}`);
|
||||
}
|
||||
|
||||
return decorateAndSignPdf({
|
||||
const result = await decorateAndSignPdf({
|
||||
envelope,
|
||||
envelopeItem,
|
||||
envelopeItemFields,
|
||||
@ -244,25 +192,10 @@ export const run = async ({
|
||||
certificateData,
|
||||
auditLogData,
|
||||
});
|
||||
});
|
||||
|
||||
newDocumentData.push(result);
|
||||
}
|
||||
newDocumentData.push(result);
|
||||
}
|
||||
|
||||
const postHog = PostHogServerClient();
|
||||
|
||||
if (postHog) {
|
||||
postHog.capture({
|
||||
distinctId: nanoid(),
|
||||
event: 'App: Document Sealed',
|
||||
properties: {
|
||||
documentId: envelope.id,
|
||||
isRejected,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await io.runTask('update-document', async () => {
|
||||
await prisma.$transaction(async (tx) => {
|
||||
for (const { oldDocumentDataId, newDocumentDataId } of newDocumentData) {
|
||||
const newData = await tx.documentData.findFirstOrThrow({
|
||||
@ -304,18 +237,24 @@ export const run = async ({
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
envelopeId: envelope.id,
|
||||
envelopeStatus: envelope.status,
|
||||
isRejected,
|
||||
};
|
||||
});
|
||||
|
||||
await io.runTask('send-completed-email', async () => {
|
||||
let shouldSendCompletedEmail = sendEmail && !isResealing && !isRejected;
|
||||
|
||||
if (isResealing && !isDocumentCompleted(envelope.status)) {
|
||||
if (isResealing && !isDocumentCompleted(envelopeStatus)) {
|
||||
shouldSendCompletedEmail = sendEmail;
|
||||
}
|
||||
|
||||
if (shouldSendCompletedEmail) {
|
||||
await sendCompletedEmail({
|
||||
id: { type: 'envelopeId', id: envelope.id },
|
||||
id: { type: 'envelopeId', id: envelopeId },
|
||||
requestMetadata,
|
||||
});
|
||||
}
|
||||
@ -323,7 +262,7 @@ export const run = async ({
|
||||
|
||||
const updatedEnvelope = await prisma.envelope.findFirstOrThrow({
|
||||
where: {
|
||||
id: envelope.id,
|
||||
id: envelopeId,
|
||||
},
|
||||
include: {
|
||||
documentMeta: true,
|
||||
|
||||
@ -31,26 +31,16 @@ export const viewedDocument = async ({
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
envelope: {
|
||||
include: {
|
||||
documentMeta: true,
|
||||
recipients: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!recipient) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { envelope } = recipient;
|
||||
|
||||
await prisma.documentAuditLog.create({
|
||||
data: createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_VIEWED,
|
||||
envelopeId: envelope.id,
|
||||
envelopeId: recipient.envelopeId,
|
||||
user: {
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
@ -86,7 +76,7 @@ export const viewedDocument = async ({
|
||||
await tx.documentAuditLog.create({
|
||||
data: createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED,
|
||||
envelopeId: envelope.id,
|
||||
envelopeId: recipient.envelopeId,
|
||||
user: {
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
@ -103,6 +93,16 @@ export const viewedDocument = async ({
|
||||
});
|
||||
});
|
||||
|
||||
const envelope = await prisma.envelope.findUniqueOrThrow({
|
||||
where: {
|
||||
id: recipient.envelopeId,
|
||||
},
|
||||
include: {
|
||||
documentMeta: true,
|
||||
recipients: true,
|
||||
},
|
||||
});
|
||||
|
||||
await triggerWebhook({
|
||||
event: WebhookTriggerEvents.DOCUMENT_OPENED,
|
||||
data: ZWebhookDocumentSchema.parse(mapEnvelopeToWebhookDocumentPayload(envelope)),
|
||||
|
||||
11043
packages/lib/translations/ja/web.po
Normal file
11043
packages/lib/translations/ja/web.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: pl\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2025-11-12 06:14\n"
|
||||
"PO-Revision-Date: 2025-11-17 02:33\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Polish\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
|
||||
@ -8943,13 +8943,13 @@ msgstr "Tytuł nie może być pusty"
|
||||
#. placeholder {2}: recipient.email
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-account.tsx
|
||||
msgid "To {0} this {1}, you need to be logged in as <0>{2}</0>"
|
||||
msgstr ""
|
||||
msgstr "Aby {0} ten {1}, musisz być zalogowany jako <0>{2}</0>"
|
||||
|
||||
#. placeholder {0}: actionVerb.toLowerCase()
|
||||
#. placeholder {1}: actionTarget.toLowerCase()
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-account.tsx
|
||||
msgid "To {0} this {1}, you need to be logged in."
|
||||
msgstr ""
|
||||
msgstr "Aby {0} ten {1}, musisz być zalogowany."
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx
|
||||
msgid "To accept this invitation you must create an account."
|
||||
@ -10532,7 +10532,7 @@ msgstr "Nie możesz przesyłać zaszyfrowanych plików PDF"
|
||||
#: apps/remix/app/components/general/envelope/envelope-upload-button.tsx
|
||||
#: apps/remix/app/components/general/envelope/envelope-drop-zone-wrapper.tsx
|
||||
msgid "You cannot upload more than {maximumEnvelopeItemCount} items per envelope."
|
||||
msgstr ""
|
||||
msgstr "Nie możesz przesłać więcej niż {maximumEnvelopeItemCount} elementów na kopertę."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.billing.tsx
|
||||
msgid "You currently have an inactive <0>{currentProductName}</0> subscription"
|
||||
|
||||
11043
packages/lib/translations/zh/web.po
Normal file
11043
packages/lib/translations/zh/web.po
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user