mirror of
https://github.com/documenso/documenso.git
synced 2025-11-16 17:51:49 +10:00
Merge branch 'main' into feat/delete-archive
This commit is contained in:
@ -18,6 +18,8 @@ export const IS_OIDC_SSO_ENABLED = Boolean(
|
||||
process.env.NEXT_PRIVATE_OIDC_CLIENT_SECRET,
|
||||
);
|
||||
|
||||
export const OIDC_PROVIDER_LABEL = process.env.NEXT_PRIVATE_OIDC_PROVIDER_LABEL;
|
||||
|
||||
export const USER_SECURITY_AUDIT_LOG_MAP: { [key in UserSecurityAuditLogType]: string } = {
|
||||
[UserSecurityAuditLogType.ACCOUNT_SSO_LINK]: 'Linked account to SSO',
|
||||
[UserSecurityAuditLogType.ACCOUNT_PROFILE_UPDATE]: 'Profile updated',
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
export const URL_REGEX =
|
||||
/^(https?):\/\/(?:www\.)?(?:[a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z0-9()]{2,}(?:\/[a-zA-Z0-9-._?&=/]*)?$/i;
|
||||
@ -4,6 +4,7 @@ import { SEND_SIGNING_EMAIL_JOB_DEFINITION } from './definitions/emails/send-sig
|
||||
import { SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION } from './definitions/emails/send-team-deleted-email';
|
||||
import { SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION } from './definitions/emails/send-team-member-joined-email';
|
||||
import { SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION } from './definitions/emails/send-team-member-left-email';
|
||||
import { SEAL_DOCUMENT_JOB_DEFINITION } from './definitions/internal/seal-document';
|
||||
|
||||
/**
|
||||
* The `as const` assertion is load bearing as it provides the correct level of type inference for
|
||||
@ -15,6 +16,7 @@ export const jobsClient = new JobClient([
|
||||
SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION,
|
||||
SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION,
|
||||
SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION,
|
||||
SEAL_DOCUMENT_JOB_DEFINITION,
|
||||
] as const);
|
||||
|
||||
export const jobs = jobsClient;
|
||||
|
||||
@ -26,8 +26,8 @@ export class InngestJobProvider extends BaseJobProvider {
|
||||
static getInstance() {
|
||||
if (!this._instance) {
|
||||
const client = new InngestClient({
|
||||
id: 'documenso-app',
|
||||
eventKey: process.env.NEXT_PRIVATE_INNGEST_EVENT_KEY,
|
||||
id: process.env.NEXT_PRIVATE_INNGEST_APP_ID || 'documenso-app',
|
||||
eventKey: process.env.INNGEST_EVENT_KEY || process.env.NEXT_PRIVATE_INNGEST_EVENT_KEY,
|
||||
});
|
||||
|
||||
this._instance = new InngestJobProvider({ client });
|
||||
|
||||
@ -58,6 +58,12 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
||||
},
|
||||
include: {
|
||||
documentMeta: true,
|
||||
team: {
|
||||
select: {
|
||||
teamEmail: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.recipient.findFirstOrThrow({
|
||||
@ -67,7 +73,7 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
||||
}),
|
||||
]);
|
||||
|
||||
const { documentMeta } = document;
|
||||
const { documentMeta, team } = document;
|
||||
|
||||
if (recipient.role === RecipientRole.CC) {
|
||||
return;
|
||||
@ -75,6 +81,7 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
||||
|
||||
const customEmail = document?.documentMeta;
|
||||
const isDirectTemplate = document.source === DocumentSource.TEMPLATE_DIRECT_LINK;
|
||||
const isTeamDocument = document.teamId !== null;
|
||||
|
||||
const recipientEmailType = RECIPIENT_ROLE_TO_EMAIL_TYPE[recipient.role];
|
||||
|
||||
@ -96,6 +103,11 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
||||
emailSubject = `Please ${recipientActionVerb} this document created by your direct template`;
|
||||
}
|
||||
|
||||
if (isTeamDocument && team) {
|
||||
emailSubject = `${team.name} invited you to ${recipientActionVerb} a document`;
|
||||
emailMessage = `${user.name} on behalf of ${team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`;
|
||||
}
|
||||
|
||||
const customEmailTemplate = {
|
||||
'signer.name': name,
|
||||
'signer.email': email,
|
||||
@ -108,12 +120,15 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
||||
const template = createElement(DocumentInviteEmailTemplate, {
|
||||
documentName: document.title,
|
||||
inviterName: user.name || undefined,
|
||||
inviterEmail: user.email,
|
||||
inviterEmail: isTeamDocument ? team?.teamEmail?.email || user.email : user.email,
|
||||
assetBaseUrl,
|
||||
signDocumentLink,
|
||||
customBody: renderCustomEmailTemplate(emailMessage, customEmailTemplate),
|
||||
role: recipient.role,
|
||||
selfSigner,
|
||||
isTeamInvite: isTeamDocument,
|
||||
teamName: team?.name,
|
||||
teamEmail: team?.teamEmail?.email,
|
||||
});
|
||||
|
||||
await io.runTask('send-signing-email', async () => {
|
||||
|
||||
250
packages/lib/jobs/definitions/internal/seal-document.ts
Normal file
250
packages/lib/jobs/definitions/internal/seal-document.ts
Normal file
@ -0,0 +1,250 @@
|
||||
import { nanoid } from 'nanoid';
|
||||
import path from 'node:path';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import {
|
||||
DocumentStatus,
|
||||
RecipientRole,
|
||||
SigningStatus,
|
||||
WebhookTriggerEvents,
|
||||
} from '@documenso/prisma/client';
|
||||
import { signPdf } from '@documenso/signing';
|
||||
|
||||
import { sendCompletedEmail } from '../../../server-only/document/send-completed-email';
|
||||
import PostHogServerClient from '../../../server-only/feature-flags/get-post-hog-server-client';
|
||||
import { getCertificatePdf } from '../../../server-only/htmltopdf/get-certificate-pdf';
|
||||
import { flattenAnnotations } from '../../../server-only/pdf/flatten-annotations';
|
||||
import { flattenForm } from '../../../server-only/pdf/flatten-form';
|
||||
import { insertFieldInPDF } from '../../../server-only/pdf/insert-field-in-pdf';
|
||||
import { normalizeSignatureAppearances } from '../../../server-only/pdf/normalize-signature-appearances';
|
||||
import { triggerWebhook } from '../../../server-only/webhooks/trigger/trigger-webhook';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs';
|
||||
import { ZRequestMetadataSchema } from '../../../universal/extract-request-metadata';
|
||||
import { getFile } from '../../../universal/upload/get-file';
|
||||
import { putPdfFile } from '../../../universal/upload/put-file';
|
||||
import { createDocumentAuditLogData } from '../../../utils/document-audit-logs';
|
||||
import { type JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const SEAL_DOCUMENT_JOB_DEFINITION_ID = 'internal.seal-document';
|
||||
|
||||
const SEAL_DOCUMENT_JOB_DEFINITION_SCHEMA = z.object({
|
||||
documentId: z.number(),
|
||||
sendEmail: z.boolean().optional(),
|
||||
isResealing: z.boolean().optional(),
|
||||
requestMetadata: ZRequestMetadataSchema.optional(),
|
||||
});
|
||||
|
||||
export const SEAL_DOCUMENT_JOB_DEFINITION = {
|
||||
id: SEAL_DOCUMENT_JOB_DEFINITION_ID,
|
||||
name: 'Seal Document',
|
||||
version: '1.0.0',
|
||||
trigger: {
|
||||
name: SEAL_DOCUMENT_JOB_DEFINITION_ID,
|
||||
schema: SEAL_DOCUMENT_JOB_DEFINITION_SCHEMA,
|
||||
},
|
||||
handler: async ({ payload, io }) => {
|
||||
const { documentId, sendEmail = true, isResealing = false, requestMetadata } = payload;
|
||||
|
||||
const document = await prisma.document.findFirstOrThrow({
|
||||
where: {
|
||||
id: documentId,
|
||||
Recipient: {
|
||||
every: {
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
Recipient: true,
|
||||
},
|
||||
});
|
||||
|
||||
// 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 document.status;
|
||||
});
|
||||
|
||||
// This is the same case as above.
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
const documentDataId = await io.runTask('get-document-data-id', async () => {
|
||||
return document.documentDataId;
|
||||
});
|
||||
|
||||
const documentData = await prisma.documentData.findFirst({
|
||||
where: {
|
||||
id: documentDataId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!documentData) {
|
||||
throw new Error(`Document ${document.id} has no document data`);
|
||||
}
|
||||
|
||||
const recipients = await prisma.recipient.findMany({
|
||||
where: {
|
||||
documentId: document.id,
|
||||
role: {
|
||||
not: RecipientRole.CC,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (recipients.some((recipient) => recipient.signingStatus !== SigningStatus.SIGNED)) {
|
||||
throw new Error(`Document ${document.id} has unsigned recipients`);
|
||||
}
|
||||
|
||||
const fields = await prisma.field.findMany({
|
||||
where: {
|
||||
documentId: document.id,
|
||||
},
|
||||
include: {
|
||||
Signature: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (fields.some((field) => !field.inserted)) {
|
||||
throw new Error(`Document ${document.id} has unsigned 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.
|
||||
documentData.data = documentData.initialData;
|
||||
}
|
||||
|
||||
const pdfData = await getFile(documentData);
|
||||
const certificateData = await getCertificatePdf({ documentId }).catch(() => null);
|
||||
|
||||
const newDataId = await io.runTask('decorate-and-sign-pdf', async () => {
|
||||
const pdfDoc = await PDFDocument.load(pdfData);
|
||||
|
||||
// Normalize and flatten layers that could cause issues with the signature
|
||||
normalizeSignatureAppearances(pdfDoc);
|
||||
flattenForm(pdfDoc);
|
||||
flattenAnnotations(pdfDoc);
|
||||
|
||||
if (certificateData) {
|
||||
const certificateDoc = await PDFDocument.load(certificateData);
|
||||
|
||||
const certificatePages = await pdfDoc.copyPages(
|
||||
certificateDoc,
|
||||
certificateDoc.getPageIndices(),
|
||||
);
|
||||
|
||||
certificatePages.forEach((page) => {
|
||||
pdfDoc.addPage(page);
|
||||
});
|
||||
}
|
||||
|
||||
for (const field of fields) {
|
||||
await insertFieldInPDF(pdfDoc, field);
|
||||
}
|
||||
|
||||
// Re-flatten the form to handle our checkbox and radio fields that
|
||||
// create native arcoFields
|
||||
flattenForm(pdfDoc);
|
||||
|
||||
const pdfBytes = await pdfDoc.save();
|
||||
const pdfBuffer = await signPdf({ pdf: Buffer.from(pdfBytes) });
|
||||
|
||||
const { name, ext } = path.parse(document.title);
|
||||
|
||||
const documentData = await putPdfFile({
|
||||
name: `${name}_signed${ext}`,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(pdfBuffer),
|
||||
});
|
||||
|
||||
return documentData.id;
|
||||
});
|
||||
|
||||
const postHog = PostHogServerClient();
|
||||
|
||||
if (postHog) {
|
||||
postHog.capture({
|
||||
distinctId: nanoid(),
|
||||
event: 'App: Document Sealed',
|
||||
properties: {
|
||||
documentId: document.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await io.runTask('update-document', async () => {
|
||||
await prisma.$transaction(async (tx) => {
|
||||
const newData = await tx.documentData.findFirstOrThrow({
|
||||
where: {
|
||||
id: newDataId,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.document.update({
|
||||
where: {
|
||||
id: document.id,
|
||||
},
|
||||
data: {
|
||||
status: DocumentStatus.COMPLETED,
|
||||
completedAt: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
await tx.documentData.update({
|
||||
where: {
|
||||
id: documentData.id,
|
||||
},
|
||||
data: {
|
||||
data: newData.data,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.documentAuditLog.create({
|
||||
data: createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED,
|
||||
documentId: document.id,
|
||||
requestMetadata,
|
||||
user: null,
|
||||
data: {
|
||||
transactionId: nanoid(),
|
||||
},
|
||||
}),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await io.runTask('send-completed-email', async () => {
|
||||
let shouldSendCompletedEmail = sendEmail && !isResealing;
|
||||
|
||||
if (isResealing && documentStatus !== DocumentStatus.COMPLETED) {
|
||||
shouldSendCompletedEmail = sendEmail;
|
||||
}
|
||||
|
||||
if (shouldSendCompletedEmail) {
|
||||
await sendCompletedEmail({ documentId, requestMetadata });
|
||||
}
|
||||
});
|
||||
|
||||
const updatedDocument = await prisma.document.findFirstOrThrow({
|
||||
where: {
|
||||
id: document.id,
|
||||
},
|
||||
include: {
|
||||
documentData: true,
|
||||
Recipient: true,
|
||||
},
|
||||
});
|
||||
|
||||
await triggerWebhook({
|
||||
event: WebhookTriggerEvents.DOCUMENT_COMPLETED,
|
||||
data: updatedDocument,
|
||||
userId: updatedDocument.userId,
|
||||
teamId: updatedDocument.teamId ?? undefined,
|
||||
});
|
||||
},
|
||||
} as const satisfies JobDefinition<
|
||||
typeof SEAL_DOCUMENT_JOB_DEFINITION_ID,
|
||||
z.infer<typeof SEAL_DOCUMENT_JOB_DEFINITION_SCHEMA>
|
||||
>;
|
||||
@ -161,7 +161,10 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
||||
id: profile.sub,
|
||||
email: profile.email || profile.preferred_username,
|
||||
name: profile.name || `${profile.given_name} ${profile.family_name}`.trim(),
|
||||
emailVerified: profile.email_verified ? new Date().toISOString() : null,
|
||||
emailVerified:
|
||||
process.env.NEXT_PRIVATE_OIDC_SKIP_VERIFY === 'true' || profile.email_verified
|
||||
? new Date().toISOString()
|
||||
: null,
|
||||
};
|
||||
},
|
||||
},
|
||||
@ -361,6 +364,12 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
||||
},
|
||||
|
||||
async signIn({ user }) {
|
||||
// This statement appears above so we can stil allow `oidc` connections
|
||||
// while other signups are disabled.
|
||||
if (env('NEXT_PRIVATE_OIDC_ALLOW_SIGNUP') === 'true') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// We do this to stop OAuth providers from creating an account
|
||||
// when signups are disabled
|
||||
if (env('NEXT_PUBLIC_DISABLE_SIGNUP') === 'true') {
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { URL_REGEX } from '../constants/url-regex';
|
||||
import { isValidRedirectUrl } from '../utils/is-valid-redirect-url';
|
||||
|
||||
/**
|
||||
* Note this allows empty strings.
|
||||
*/
|
||||
export const ZUrlSchema = z
|
||||
.string()
|
||||
.refine((value) => value === undefined || value === '' || URL_REGEX.test(value), {
|
||||
message: 'Please enter a valid URL',
|
||||
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
|
||||
message: 'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
||||
});
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
'use server';
|
||||
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
@ -7,9 +5,9 @@ import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus, SigningStatus } from '@documenso/prisma/client';
|
||||
import { WebhookTriggerEvents } from '@documenso/prisma/client';
|
||||
|
||||
import { jobs } from '../../jobs/client';
|
||||
import type { TRecipientActionAuth } from '../../types/document-auth';
|
||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||
import { sealDocument } from './seal-document';
|
||||
import { sendPendingEmail } from './send-pending-email';
|
||||
|
||||
export type CompleteDocumentWithTokenOptions = {
|
||||
@ -45,8 +43,6 @@ export const completeDocumentWithToken = async ({
|
||||
documentId,
|
||||
requestMetadata,
|
||||
}: CompleteDocumentWithTokenOptions) => {
|
||||
'use server';
|
||||
|
||||
const document = await getDocument({ token, documentId });
|
||||
|
||||
if (document.status !== DocumentStatus.PENDING) {
|
||||
@ -149,7 +145,13 @@ export const completeDocumentWithToken = async ({
|
||||
});
|
||||
|
||||
if (haveAllRecipientsSigned) {
|
||||
await sealDocument({ documentId: document.id, requestMetadata });
|
||||
await jobs.triggerJob({
|
||||
name: 'internal.seal-document',
|
||||
payload: {
|
||||
documentId: document.id,
|
||||
requestMetadata,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const updatedDocument = await getDocument({ token, documentId });
|
||||
|
||||
@ -58,10 +58,17 @@ export const resendDocument = async ({
|
||||
},
|
||||
},
|
||||
documentMeta: true,
|
||||
team: {
|
||||
select: {
|
||||
teamEmail: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const customEmail = document?.documentMeta;
|
||||
const isTeamDocument = document?.team !== null;
|
||||
|
||||
if (!document) {
|
||||
throw new Error('Document not found');
|
||||
@ -90,9 +97,21 @@ export const resendDocument = async ({
|
||||
const { email, name } = recipient;
|
||||
const selfSigner = email === user.email;
|
||||
|
||||
const selfSignerCustomEmail = `You have initiated the document ${`"${document.title}"`} that requires you to ${RECIPIENT_ROLES_DESCRIPTION[
|
||||
recipient.role
|
||||
].actionVerb.toLowerCase()} it.`;
|
||||
const { actionVerb } = RECIPIENT_ROLES_DESCRIPTION[recipient.role];
|
||||
const recipientActionVerb = actionVerb.toLowerCase();
|
||||
|
||||
let emailMessage = customEmail?.message || '';
|
||||
let emailSubject = `Reminder: Please ${recipientActionVerb} this document`;
|
||||
|
||||
if (selfSigner) {
|
||||
emailMessage = `You have initiated the document ${`"${document.title}"`} that requires you to ${recipientActionVerb} it.`;
|
||||
emailSubject = `Reminder: Please ${recipientActionVerb} your document`;
|
||||
}
|
||||
|
||||
if (isTeamDocument && document.team) {
|
||||
emailSubject = `Reminder: ${document.team.name} invited you to ${recipientActionVerb} a document`;
|
||||
emailMessage = `${user.name} on behalf of ${document.team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`;
|
||||
}
|
||||
|
||||
const customEmailTemplate = {
|
||||
'signer.name': name,
|
||||
@ -106,23 +125,16 @@ export const resendDocument = async ({
|
||||
const template = createElement(DocumentInviteEmailTemplate, {
|
||||
documentName: document.title,
|
||||
inviterName: user.name || undefined,
|
||||
inviterEmail: user.email,
|
||||
inviterEmail: isTeamDocument ? document.team?.teamEmail?.email || user.email : user.email,
|
||||
assetBaseUrl,
|
||||
signDocumentLink,
|
||||
customBody: renderCustomEmailTemplate(
|
||||
selfSigner && !customEmail?.message ? selfSignerCustomEmail : customEmail?.message || '',
|
||||
customEmailTemplate,
|
||||
),
|
||||
customBody: renderCustomEmailTemplate(emailMessage, customEmailTemplate),
|
||||
role: recipient.role,
|
||||
selfSigner,
|
||||
isTeamInvite: isTeamDocument,
|
||||
teamName: document.team?.name,
|
||||
});
|
||||
|
||||
const { actionVerb } = RECIPIENT_ROLES_DESCRIPTION[recipient.role];
|
||||
|
||||
const emailSubject = selfSigner
|
||||
? `Reminder: Please ${actionVerb.toLowerCase()} your document`
|
||||
: `Reminder: Please ${actionVerb.toLowerCase()} this document`;
|
||||
|
||||
await prisma.$transaction(
|
||||
async (tx) => {
|
||||
await mailer.sendMail({
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
'use server';
|
||||
|
||||
import { nanoid } from 'nanoid';
|
||||
import path from 'node:path';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
@ -36,8 +34,6 @@ export const sealDocument = async ({
|
||||
isResealing = false,
|
||||
requestMetadata,
|
||||
}: SealDocumentOptions) => {
|
||||
'use server';
|
||||
|
||||
const document = await prisma.document.findFirstOrThrow({
|
||||
where: {
|
||||
id: documentId,
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { sealDocument } from '@documenso/lib/server-only/document/seal-document';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
|
||||
@ -7,7 +6,7 @@ import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus, RecipientRole, SendStatus, SigningStatus } from '@documenso/prisma/client';
|
||||
import { WebhookTriggerEvents } from '@documenso/prisma/client';
|
||||
|
||||
import { jobsClient } from '../../jobs/client';
|
||||
import { jobs } from '../../jobs/client';
|
||||
import { getFile } from '../../universal/upload/get-file';
|
||||
import { insertFormValuesInPdf } from '../pdf/insert-form-values-in-pdf';
|
||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||
@ -141,7 +140,7 @@ export const sendDocument = async ({
|
||||
return;
|
||||
}
|
||||
|
||||
await jobsClient.triggerJob({
|
||||
await jobs.triggerJob({
|
||||
name: 'send.signing.requested.email',
|
||||
payload: {
|
||||
userId,
|
||||
@ -160,7 +159,13 @@ export const sendDocument = async ({
|
||||
);
|
||||
|
||||
if (allRecipientsHaveNoActionToTake) {
|
||||
await sealDocument({ documentId, requestMetadata });
|
||||
await jobs.triggerJob({
|
||||
name: 'internal.seal-document',
|
||||
payload: {
|
||||
documentId,
|
||||
requestMetadata,
|
||||
},
|
||||
});
|
||||
|
||||
// Keep the return type the same for the `sendDocument` method
|
||||
return await prisma.document.findFirstOrThrow({
|
||||
|
||||
@ -231,10 +231,17 @@ export const signFieldWithToken = async ({
|
||||
type,
|
||||
data: signatureImageAsBase64 || typedSignature || '',
|
||||
}))
|
||||
.with(FieldType.DATE, FieldType.EMAIL, FieldType.NAME, FieldType.TEXT, (type) => ({
|
||||
type,
|
||||
data: updatedField.customText,
|
||||
}))
|
||||
.with(
|
||||
FieldType.DATE,
|
||||
FieldType.EMAIL,
|
||||
FieldType.NAME,
|
||||
FieldType.TEXT,
|
||||
FieldType.INITIALS,
|
||||
(type) => ({
|
||||
type,
|
||||
data: updatedField.customText,
|
||||
}),
|
||||
)
|
||||
.with(
|
||||
FieldType.NUMBER,
|
||||
FieldType.RADIO,
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import type { Browser } from 'playwright';
|
||||
import { chromium } from 'playwright';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { encryptSecondaryData } from '../crypto/encrypt';
|
||||
@ -10,6 +9,8 @@ export type GetCertificatePdfOptions = {
|
||||
};
|
||||
|
||||
export const getCertificatePdf = async ({ documentId }: GetCertificatePdfOptions) => {
|
||||
const { chromium } = await import('playwright');
|
||||
|
||||
const encryptedId = encryptSecondaryData({
|
||||
data: documentId.toString(),
|
||||
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
|
||||
|
||||
@ -133,9 +133,14 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
||||
throw new Error('Invalid checkbox field meta');
|
||||
}
|
||||
|
||||
const values = meta.data.values?.map((item) => ({
|
||||
...item,
|
||||
value: item.value.length > 0 ? item.value : `empty-value-${item.id}`,
|
||||
}));
|
||||
|
||||
const selected = field.customText.split(',');
|
||||
|
||||
for (const [index, item] of (meta.data.values ?? []).entries()) {
|
||||
for (const [index, item] of (values ?? []).entries()) {
|
||||
const offsetY = index * 16;
|
||||
|
||||
const checkbox = pdf.getForm().createCheckBox(`checkbox.${field.secondaryId}.${index}`);
|
||||
@ -169,9 +174,14 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
||||
throw new Error('Invalid radio field meta');
|
||||
}
|
||||
|
||||
const values = meta?.data.values?.map((item) => ({
|
||||
...item,
|
||||
value: item.value.length > 0 ? item.value : `empty-value-${item.id}`,
|
||||
}));
|
||||
|
||||
const selected = field.customText.split(',');
|
||||
|
||||
for (const [index, item] of (meta.data.values ?? []).entries()) {
|
||||
for (const [index, item] of (values ?? []).entries()) {
|
||||
const offsetY = index * 16;
|
||||
|
||||
const radio = pdf.getForm().createRadioGroup(`radio.${field.secondaryId}.${index}`);
|
||||
|
||||
@ -44,6 +44,7 @@ export type CreateDocumentFromDirectTemplateOptions = {
|
||||
directRecipientName?: string;
|
||||
directRecipientEmail: string;
|
||||
directTemplateToken: string;
|
||||
directTemplateExternalId?: string;
|
||||
signedFieldValues: TSignFieldWithTokenMutationSchema[];
|
||||
templateUpdatedAt: Date;
|
||||
requestMetadata: RequestMetadata;
|
||||
@ -63,6 +64,7 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
directRecipientName: initialDirectRecipientName,
|
||||
directRecipientEmail,
|
||||
directTemplateToken,
|
||||
directTemplateExternalId,
|
||||
signedFieldValues,
|
||||
templateUpdatedAt,
|
||||
requestMetadata,
|
||||
@ -227,6 +229,7 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
title: template.title,
|
||||
createdAt: initialRequestTime,
|
||||
status: DocumentStatus.PENDING,
|
||||
externalId: directTemplateExternalId,
|
||||
documentDataId: documentData.id,
|
||||
authOptions: createDocumentAuthOptions({
|
||||
globalAccessAuth: templateAuthOptions.globalAccessAuth,
|
||||
@ -465,6 +468,7 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
.with(
|
||||
FieldType.DATE,
|
||||
FieldType.EMAIL,
|
||||
FieldType.INITIALS,
|
||||
FieldType.NAME,
|
||||
FieldType.TEXT,
|
||||
FieldType.NUMBER,
|
||||
|
||||
@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: de\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2024-07-26 06:04\n"
|
||||
"PO-Revision-Date: 2024-08-20 14:03\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
@ -17,3 +17,4 @@ msgstr ""
|
||||
"X-Crowdin-Language: de\n"
|
||||
"X-Crowdin-File: common.po\n"
|
||||
"X-Crowdin-File-ID: 4\n"
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: de\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2024-07-26 06:04\n"
|
||||
"PO-Revision-Date: 2024-08-20 14:03\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
@ -30,7 +30,7 @@ msgstr "5 Standarddokumente pro Monat"
|
||||
msgid "5 Users Included"
|
||||
msgstr "5 Benutzer inbegriffen"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:30
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:34
|
||||
msgid "A 10x better signing experience."
|
||||
msgstr "Eine 10x bessere Signaturerfahrung."
|
||||
|
||||
@ -52,11 +52,11 @@ msgstr "Erhobener Betrag"
|
||||
msgid "API Access"
|
||||
msgstr "API-Zugriff"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:63
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:67
|
||||
msgid "Beautiful."
|
||||
msgstr "Schön."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:65
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:69
|
||||
msgid "Because signing should be celebrated. That’s why we care about the smallest detail in our product."
|
||||
msgstr "Weil Unterschriften gefeiert werden sollten. Deshalb kümmern wir uns um jedes kleinste Detail in unserem Produkt."
|
||||
|
||||
@ -66,7 +66,7 @@ msgstr "Weil Unterschriften gefeiert werden sollten. Deshalb kümmern wir uns um
|
||||
msgid "Blog"
|
||||
msgstr "Blog"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:60
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:64
|
||||
msgid "Build on top."
|
||||
msgstr "Aufbauen oben drauf."
|
||||
|
||||
@ -82,7 +82,7 @@ msgstr "Karrieren"
|
||||
msgid "Changelog"
|
||||
msgstr "Änderungsprotokoll"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:81
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:85
|
||||
msgid "Choose a template from the community app store. Or submit your own template for others to use."
|
||||
msgstr "Wählen Sie eine Vorlage aus dem Community-App-Store. Oder reichen Sie Ihre eigene Vorlage ein, damit andere sie benutzen können."
|
||||
|
||||
@ -98,7 +98,7 @@ msgstr "Fertige Dokumente"
|
||||
msgid "Completed Documents per Month"
|
||||
msgstr "Fertige Dokumente pro Monat"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:61
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:65
|
||||
msgid "Connections"
|
||||
msgstr "Verbindungen"
|
||||
|
||||
@ -106,7 +106,7 @@ msgstr "Verbindungen"
|
||||
msgid "Contact Us"
|
||||
msgstr "Kontaktiere uns"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:63
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:67
|
||||
msgid "Create connections and automations with Zapier and more to integrate with your favorite tools."
|
||||
msgstr "Erstellen Sie Verbindungen und Automatisierungen mit Zapier und mehr, um sich mit Ihren Lieblingstools zu integrieren."
|
||||
|
||||
@ -118,7 +118,7 @@ msgstr "Erstellen Sie Ihr Konto und beginnen Sie mit der Nutzung modernster Doku
|
||||
msgid "Customers with an Active Subscriptions."
|
||||
msgstr "Kunden mit einer aktiven Abonnements."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:29
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:33
|
||||
msgid "Customise and expand."
|
||||
msgstr "Anpassen und erweitern."
|
||||
|
||||
@ -130,7 +130,7 @@ msgstr "Design"
|
||||
msgid "Designed for every stage of your journey."
|
||||
msgstr "Entwickelt für jede Phase Ihrer Reise."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:37
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:40
|
||||
msgid "Direct Link"
|
||||
msgstr "Direktlink"
|
||||
|
||||
@ -142,7 +142,7 @@ msgstr "Documenso ist eine Gemeinschaftsanstrengung, um ein offenes und lebendig
|
||||
msgid "Documenso on X"
|
||||
msgstr "Documenso auf X"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/hero.tsx:100
|
||||
#: apps/marketing/src/components/(marketing)/hero.tsx:104
|
||||
msgid "Document signing,<0/>finally open source."
|
||||
msgstr "Unterschriften,<0/>endlich Open Source."
|
||||
|
||||
@ -152,13 +152,17 @@ msgstr "Unterschriften,<0/>endlich Open Source."
|
||||
msgid "Documentation"
|
||||
msgstr "Dokumentation"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:106
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:110
|
||||
msgid "Easily embed Documenso into your product. Simply copy and paste our react widget into your application."
|
||||
msgstr "Betten Sie Documenso ganz einfach in Ihr Produkt ein. Kopieren und fügen Sie einfach unser React-Widget in Ihre Anwendung ein."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:42
|
||||
msgid "Easy Sharing (Soon)."
|
||||
msgstr "Einfaches Teilen (Bald)."
|
||||
#~ msgid "Easy Sharing (Soon)."
|
||||
#~ msgstr "Easy Sharing (Soon)."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:46
|
||||
msgid "Easy Sharing."
|
||||
msgstr "Einfaches Teilen."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:148
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:192
|
||||
@ -177,11 +181,11 @@ msgstr "Enterprise-Konformität, Lizenz- oder technische Bedürfnisse?"
|
||||
msgid "Everything you need for a great signing experience."
|
||||
msgstr "Alles, was Sie für ein großartiges Signaturerlebnis benötigen."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:41
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:45
|
||||
msgid "Fast."
|
||||
msgstr "Schnell."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:32
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:36
|
||||
msgid "Faster, smarter and more beautiful."
|
||||
msgstr "Schneller, intelligenter und schöner."
|
||||
|
||||
@ -217,7 +221,7 @@ msgstr "Aus dem Blog"
|
||||
msgid "Full-Time"
|
||||
msgstr "Vollzeit"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:83
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:87
|
||||
msgid "Get paid (Soon)."
|
||||
msgstr "Lassen Sie sich bezahlen (Bald)."
|
||||
|
||||
@ -269,11 +273,11 @@ msgstr "Wie gehen Sie mit meinen Daten um?"
|
||||
msgid "Individual"
|
||||
msgstr "Einzelperson"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:85
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:89
|
||||
msgid "Integrated payments with Stripe so you don’t have to worry about getting paid."
|
||||
msgstr "Integrierte Zahlungen mit Stripe, sodass Sie sich keine Sorgen ums Bezahlen machen müssen."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:31
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:35
|
||||
msgid "Integrates with all your favourite tools."
|
||||
msgstr "Integriert sich mit all Ihren Lieblingstools."
|
||||
|
||||
@ -281,7 +285,7 @@ msgstr "Integriert sich mit all Ihren Lieblingstools."
|
||||
msgid "Is there more?"
|
||||
msgstr "Gibt es mehr?"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:40
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:44
|
||||
msgid "It’s up to you. Either clone our repository or rely on our easy to use hosting solution."
|
||||
msgstr "Es liegt an Ihnen. Entweder klonen Sie unser Repository oder nutzen unsere einfach zu bedienende Hosting-Lösung."
|
||||
|
||||
@ -297,7 +301,7 @@ msgstr "Treten Sie der Open Signing-Bewegung bei"
|
||||
msgid "Location"
|
||||
msgstr "Standort"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:62
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:66
|
||||
msgid "Make it your own through advanced customization and adjustability."
|
||||
msgstr "Machen Sie es zu Ihrem eigenen durch erweiterte Anpassung und Einstellbarkeit."
|
||||
|
||||
@ -328,7 +332,7 @@ msgid "No credit card required"
|
||||
msgstr "Keine Kreditkarte erforderlich"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/callout.tsx:29
|
||||
#: apps/marketing/src/components/(marketing)/hero.tsx:121
|
||||
#: apps/marketing/src/components/(marketing)/hero.tsx:125
|
||||
msgid "No Credit Card required"
|
||||
msgstr "Keine Kreditkarte erforderlich"
|
||||
|
||||
@ -341,7 +345,7 @@ msgstr "Keines dieser Angebote passt zu Ihnen? Versuchen Sie das Selbst-Hosting!
|
||||
msgid "Open Issues"
|
||||
msgstr "Offene Issues"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:38
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:42
|
||||
msgid "Open Source or Hosted."
|
||||
msgstr "Open Source oder Hosted."
|
||||
|
||||
@ -356,7 +360,7 @@ msgstr "Offenes Startup"
|
||||
msgid "OSS Friends"
|
||||
msgstr "OSS-Freunde"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:87
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:91
|
||||
msgid "Our custom templates come with smart rules that can help you save time and energy."
|
||||
msgstr "Unsere benutzerdefinierten Vorlagen verfügen über intelligente Regeln, die Ihnen Zeit und Energie sparen können."
|
||||
|
||||
@ -392,15 +396,15 @@ msgstr "Preise"
|
||||
msgid "Privacy"
|
||||
msgstr "Datenschutz"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:55
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:58
|
||||
msgid "Profile"
|
||||
msgstr "Profil"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:104
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:108
|
||||
msgid "React Widget (Soon)."
|
||||
msgstr "React Widget (Demnächst)."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:44
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:48
|
||||
msgid "Receive your personal link to share with everyone you care about."
|
||||
msgstr "Erhalten Sie Ihren persönlichen Link zum Teilen mit allen, die Ihnen wichtig sind."
|
||||
|
||||
@ -425,7 +429,7 @@ msgstr "Sprachen suchen..."
|
||||
msgid "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."
|
||||
msgstr "Sicher. Unsere Rechenzentren befinden sich in Frankfurt (Deutschland) und bieten uns die besten lokalen Datenschutzgesetze. Uns ist die sensible Natur unserer Daten sehr bewusst und wir folgen bewährten Praktiken, um die Sicherheit und Integrität der uns anvertrauten Daten zu gewährleisten."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:33
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:37
|
||||
msgid "Send, connect, receive and embed everywhere."
|
||||
msgstr "Überall senden, verbinden, empfangen und einbetten."
|
||||
|
||||
@ -447,7 +451,7 @@ msgstr "Anmelden"
|
||||
msgid "Sign up"
|
||||
msgstr "Registrieren"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:19
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:22
|
||||
msgid "Signing Process"
|
||||
msgstr "Signaturprozess"
|
||||
|
||||
@ -457,11 +461,11 @@ msgstr "Signaturprozess"
|
||||
msgid "Signup Now"
|
||||
msgstr "Jetzt registrieren"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:85
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:89
|
||||
msgid "Smart."
|
||||
msgstr "Intelligent."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/hero.tsx:128
|
||||
#: apps/marketing/src/components/(marketing)/hero.tsx:132
|
||||
msgid "Star on GitHub"
|
||||
msgstr "Auf GitHub favorisieren"
|
||||
|
||||
@ -487,12 +491,12 @@ msgstr "Team"
|
||||
msgid "Team Inbox"
|
||||
msgstr "Team-Posteingang"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:25
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:28
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:162
|
||||
msgid "Teams"
|
||||
msgstr "Teams"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:79
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:83
|
||||
msgid "Template Store (Soon)."
|
||||
msgstr "Vorlagen-Shop (Demnächst)."
|
||||
|
||||
@ -528,12 +532,12 @@ msgstr "Insgesamt Finanzierungsvolumen"
|
||||
msgid "Total Users"
|
||||
msgstr "Gesamtanzahl der Benutzer"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:27
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:31
|
||||
msgid "Truly your own."
|
||||
msgstr "Wirklich Ihr Eigenes."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/callout.tsx:27
|
||||
#: apps/marketing/src/components/(marketing)/hero.tsx:119
|
||||
#: apps/marketing/src/components/(marketing)/hero.tsx:123
|
||||
msgid "Try our Free Plan"
|
||||
msgstr "Probieren Sie unseren Gratisplan aus"
|
||||
|
||||
@ -566,7 +570,7 @@ msgstr "Wir helfen Ihnen gerne unter <0>support@documenso.com</0> oder <1>in uns
|
||||
msgid "What is the difference between the plans?"
|
||||
msgstr "Was ist der Unterschied zwischen den Plänen?"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:43
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:47
|
||||
msgid "When it comes to sending or receiving a contract, you can count on lightning-fast speeds."
|
||||
msgstr "Wenn es um das Senden oder Empfangen eines Vertrags geht, können Sie auf blitzschnelle Geschwindigkeiten zählen."
|
||||
|
||||
@ -594,6 +598,7 @@ msgstr "Ja! Documenso wird unter der GNU AGPL V3 Open-Source-Lizenz angeboten. D
|
||||
msgid "You can self-host Documenso for free or use our ready-to-use hosted version. The hosted version comes with additional support, painless scalability and more. Early adopters will get access to all features we build this year, for no additional cost! Forever! Yes, that includes multiple users per account later. If you want Documenso for your enterprise, we are happy to talk about your needs."
|
||||
msgstr "Sie können Documenso kostenlos selbst hosten oder unsere sofort einsatzbereite gehostete Version nutzen. Die gehostete Version bietet zusätzlichen Support, schmerzfreie Skalierbarkeit und mehr. Frühzeitige Anwender erhalten in diesem Jahr Zugriff auf alle Funktionen, die wir entwickeln, ohne zusätzliche Kosten! Für immer! Ja, das beinhaltet später mehrere Benutzer pro Konto. Wenn Sie Documenso für Ihr Unternehmen möchten, sprechen wir gerne über Ihre Bedürfnisse."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:258
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:265
|
||||
msgid "Your browser does not support the video tag."
|
||||
msgstr "Ihr Browser unterstützt das Video-Tag nicht."
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: de\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2024-07-26 06:04\n"
|
||||
"PO-Revision-Date: 2024-08-20 14:03\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
@ -17,3 +17,4 @@ msgstr ""
|
||||
"X-Crowdin-Language: de\n"
|
||||
"X-Crowdin-File: web.po\n"
|
||||
"X-Crowdin-File-ID: 8\n"
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -25,7 +25,7 @@ msgstr "5 standard documents per month"
|
||||
msgid "5 Users Included"
|
||||
msgstr "5 Users Included"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:30
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:34
|
||||
msgid "A 10x better signing experience."
|
||||
msgstr "A 10x better signing experience."
|
||||
|
||||
@ -47,11 +47,11 @@ msgstr "Amount Raised"
|
||||
msgid "API Access"
|
||||
msgstr "API Access"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:63
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:67
|
||||
msgid "Beautiful."
|
||||
msgstr "Beautiful."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:65
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:69
|
||||
msgid "Because signing should be celebrated. That’s why we care about the smallest detail in our product."
|
||||
msgstr "Because signing should be celebrated. That’s why we care about the smallest detail in our product."
|
||||
|
||||
@ -61,7 +61,7 @@ msgstr "Because signing should be celebrated. That’s why we care about the sma
|
||||
msgid "Blog"
|
||||
msgstr "Blog"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:60
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:64
|
||||
msgid "Build on top."
|
||||
msgstr "Build on top."
|
||||
|
||||
@ -77,7 +77,7 @@ msgstr "Careers"
|
||||
msgid "Changelog"
|
||||
msgstr "Changelog"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:81
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:85
|
||||
msgid "Choose a template from the community app store. Or submit your own template for others to use."
|
||||
msgstr "Choose a template from the community app store. Or submit your own template for others to use."
|
||||
|
||||
@ -93,7 +93,7 @@ msgstr "Completed Documents"
|
||||
msgid "Completed Documents per Month"
|
||||
msgstr "Completed Documents per Month"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:61
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:65
|
||||
msgid "Connections"
|
||||
msgstr "Connections"
|
||||
|
||||
@ -101,7 +101,7 @@ msgstr "Connections"
|
||||
msgid "Contact Us"
|
||||
msgstr "Contact Us"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:63
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:67
|
||||
msgid "Create connections and automations with Zapier and more to integrate with your favorite tools."
|
||||
msgstr "Create connections and automations with Zapier and more to integrate with your favorite tools."
|
||||
|
||||
@ -113,7 +113,7 @@ msgstr "Create your account and start using state-of-the-art document signing. O
|
||||
msgid "Customers with an Active Subscriptions."
|
||||
msgstr "Customers with an Active Subscriptions."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:29
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:33
|
||||
msgid "Customise and expand."
|
||||
msgstr "Customise and expand."
|
||||
|
||||
@ -125,7 +125,7 @@ msgstr "Design"
|
||||
msgid "Designed for every stage of your journey."
|
||||
msgstr "Designed for every stage of your journey."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:37
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:40
|
||||
msgid "Direct Link"
|
||||
msgstr "Direct Link"
|
||||
|
||||
@ -137,7 +137,7 @@ msgstr "Documenso is a community effort to create an open and vibrant ecosystem
|
||||
msgid "Documenso on X"
|
||||
msgstr "Documenso on X"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/hero.tsx:100
|
||||
#: apps/marketing/src/components/(marketing)/hero.tsx:104
|
||||
msgid "Document signing,<0/>finally open source."
|
||||
msgstr "Document signing,<0/>finally open source."
|
||||
|
||||
@ -147,13 +147,17 @@ msgstr "Document signing,<0/>finally open source."
|
||||
msgid "Documentation"
|
||||
msgstr "Documentation"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:106
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:110
|
||||
msgid "Easily embed Documenso into your product. Simply copy and paste our react widget into your application."
|
||||
msgstr "Easily embed Documenso into your product. Simply copy and paste our react widget into your application."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:42
|
||||
msgid "Easy Sharing (Soon)."
|
||||
msgstr "Easy Sharing (Soon)."
|
||||
#~ msgid "Easy Sharing (Soon)."
|
||||
#~ msgstr "Easy Sharing (Soon)."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:46
|
||||
msgid "Easy Sharing."
|
||||
msgstr "Easy Sharing."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:148
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:192
|
||||
@ -172,11 +176,11 @@ msgstr "Enterprise Compliance, License or Technical Needs?"
|
||||
msgid "Everything you need for a great signing experience."
|
||||
msgstr "Everything you need for a great signing experience."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:41
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:45
|
||||
msgid "Fast."
|
||||
msgstr "Fast."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:32
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:36
|
||||
msgid "Faster, smarter and more beautiful."
|
||||
msgstr "Faster, smarter and more beautiful."
|
||||
|
||||
@ -212,7 +216,7 @@ msgstr "From the blog"
|
||||
msgid "Full-Time"
|
||||
msgstr "Full-Time"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:83
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:87
|
||||
msgid "Get paid (Soon)."
|
||||
msgstr "Get paid (Soon)."
|
||||
|
||||
@ -264,11 +268,11 @@ msgstr "How do you handle my data?"
|
||||
msgid "Individual"
|
||||
msgstr "Individual"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:85
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:89
|
||||
msgid "Integrated payments with Stripe so you don’t have to worry about getting paid."
|
||||
msgstr "Integrated payments with Stripe so you don’t have to worry about getting paid."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:31
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:35
|
||||
msgid "Integrates with all your favourite tools."
|
||||
msgstr "Integrates with all your favourite tools."
|
||||
|
||||
@ -276,7 +280,7 @@ msgstr "Integrates with all your favourite tools."
|
||||
msgid "Is there more?"
|
||||
msgstr "Is there more?"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:40
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:44
|
||||
msgid "It’s up to you. Either clone our repository or rely on our easy to use hosting solution."
|
||||
msgstr "It’s up to you. Either clone our repository or rely on our easy to use hosting solution."
|
||||
|
||||
@ -292,7 +296,7 @@ msgstr "Join the Open Signing Movement"
|
||||
msgid "Location"
|
||||
msgstr "Location"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:62
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:66
|
||||
msgid "Make it your own through advanced customization and adjustability."
|
||||
msgstr "Make it your own through advanced customization and adjustability."
|
||||
|
||||
@ -323,7 +327,7 @@ msgid "No credit card required"
|
||||
msgstr "No credit card required"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/callout.tsx:29
|
||||
#: apps/marketing/src/components/(marketing)/hero.tsx:121
|
||||
#: apps/marketing/src/components/(marketing)/hero.tsx:125
|
||||
msgid "No Credit Card required"
|
||||
msgstr "No Credit Card required"
|
||||
|
||||
@ -336,7 +340,7 @@ msgstr "None of these work for you? Try self-hosting!"
|
||||
msgid "Open Issues"
|
||||
msgstr "Open Issues"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:38
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:42
|
||||
msgid "Open Source or Hosted."
|
||||
msgstr "Open Source or Hosted."
|
||||
|
||||
@ -351,7 +355,7 @@ msgstr "Open Startup"
|
||||
msgid "OSS Friends"
|
||||
msgstr "OSS Friends"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:87
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:91
|
||||
msgid "Our custom templates come with smart rules that can help you save time and energy."
|
||||
msgstr "Our custom templates come with smart rules that can help you save time and energy."
|
||||
|
||||
@ -387,15 +391,15 @@ msgstr "Pricing"
|
||||
msgid "Privacy"
|
||||
msgstr "Privacy"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:55
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:58
|
||||
msgid "Profile"
|
||||
msgstr "Profile"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:104
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:108
|
||||
msgid "React Widget (Soon)."
|
||||
msgstr "React Widget (Soon)."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:44
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:48
|
||||
msgid "Receive your personal link to share with everyone you care about."
|
||||
msgstr "Receive your personal link to share with everyone you care about."
|
||||
|
||||
@ -420,7 +424,7 @@ msgstr "Search languages..."
|
||||
msgid "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."
|
||||
msgstr "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:33
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:37
|
||||
msgid "Send, connect, receive and embed everywhere."
|
||||
msgstr "Send, connect, receive and embed everywhere."
|
||||
|
||||
@ -442,7 +446,7 @@ msgstr "Sign in"
|
||||
msgid "Sign up"
|
||||
msgstr "Sign up"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:19
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:22
|
||||
msgid "Signing Process"
|
||||
msgstr "Signing Process"
|
||||
|
||||
@ -452,11 +456,11 @@ msgstr "Signing Process"
|
||||
msgid "Signup Now"
|
||||
msgstr "Signup Now"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:85
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:89
|
||||
msgid "Smart."
|
||||
msgstr "Smart."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/hero.tsx:128
|
||||
#: apps/marketing/src/components/(marketing)/hero.tsx:132
|
||||
msgid "Star on GitHub"
|
||||
msgstr "Star on GitHub"
|
||||
|
||||
@ -482,12 +486,12 @@ msgstr "Team"
|
||||
msgid "Team Inbox"
|
||||
msgstr "Team Inbox"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:25
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:28
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:162
|
||||
msgid "Teams"
|
||||
msgstr "Teams"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:79
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:83
|
||||
msgid "Template Store (Soon)."
|
||||
msgstr "Template Store (Soon)."
|
||||
|
||||
@ -523,12 +527,12 @@ msgstr "Total Funding Raised"
|
||||
msgid "Total Users"
|
||||
msgstr "Total Users"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:27
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:31
|
||||
msgid "Truly your own."
|
||||
msgstr "Truly your own."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/callout.tsx:27
|
||||
#: apps/marketing/src/components/(marketing)/hero.tsx:119
|
||||
#: apps/marketing/src/components/(marketing)/hero.tsx:123
|
||||
msgid "Try our Free Plan"
|
||||
msgstr "Try our Free Plan"
|
||||
|
||||
@ -561,7 +565,7 @@ msgstr "We are happy to assist you at <0>support@documenso.com</0> or <1>in our
|
||||
msgid "What is the difference between the plans?"
|
||||
msgstr "What is the difference between the plans?"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:43
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:47
|
||||
msgid "When it comes to sending or receiving a contract, you can count on lightning-fast speeds."
|
||||
msgstr "When it comes to sending or receiving a contract, you can count on lightning-fast speeds."
|
||||
|
||||
@ -589,6 +593,6 @@ msgstr "Yes! Documenso is offered under the GNU AGPL V3 open source license. Thi
|
||||
msgid "You can self-host Documenso for free or use our ready-to-use hosted version. The hosted version comes with additional support, painless scalability and more. Early adopters will get access to all features we build this year, for no additional cost! Forever! Yes, that includes multiple users per account later. If you want Documenso for your enterprise, we are happy to talk about your needs."
|
||||
msgstr "You can self-host Documenso for free or use our ready-to-use hosted version. The hosted version comes with additional support, painless scalability and more. Early adopters will get access to all features we build this year, for no additional cost! Forever! Yes, that includes multiple users per account later. If you want Documenso for your enterprise, we are happy to talk about your needs."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:258
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:265
|
||||
msgid "Your browser does not support the video tag."
|
||||
msgstr "Your browser does not support the video tag."
|
||||
|
||||
@ -244,6 +244,10 @@ export const ZDocumentAuditLogEventDocumentFieldInsertedSchema = z.object({
|
||||
|
||||
// Organised into union to allow us to extend each field if required.
|
||||
field: z.union([
|
||||
z.object({
|
||||
type: z.literal(FieldType.INITIALS),
|
||||
data: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.EMAIL),
|
||||
data: z.string(),
|
||||
|
||||
16
packages/lib/utils/is-valid-redirect-url.ts
Normal file
16
packages/lib/utils/is-valid-redirect-url.ts
Normal file
@ -0,0 +1,16 @@
|
||||
const ALLOWED_PROTOCOLS = ['http', 'https'];
|
||||
|
||||
export const isValidRedirectUrl = (value: string) => {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
|
||||
console.log({ protocol: url.protocol });
|
||||
if (!ALLOWED_PROTOCOLS.includes(url.protocol.slice(0, -1).toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
3
packages/lib/utils/slugify.ts
Normal file
3
packages/lib/utils/slugify.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from '@sindresorhus/slugify';
|
||||
|
||||
export { default as slugify } from '@sindresorhus/slugify';
|
||||
Reference in New Issue
Block a user