Merge branch 'main' into feat/delete-archive

This commit is contained in:
Lucas Smith
2024-08-21 11:44:05 +10:00
committed by GitHub
124 changed files with 2129 additions and 772 deletions

View File

@ -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',

View File

@ -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;

View File

@ -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;

View File

@ -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 });

View File

@ -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 () => {

View 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>
>;

View File

@ -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') {

View File

@ -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.',
});

View File

@ -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 });

View File

@ -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({

View File

@ -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,

View File

@ -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({

View File

@ -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,

View File

@ -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(),

View File

@ -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}`);

View File

@ -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,

View File

@ -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

View File

@ -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. Thats 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 dont 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 "Its 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."

View File

@ -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

View File

@ -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. Thats why we care about the smallest detail in our product."
msgstr "Because signing should be celebrated. Thats why we care about the smallest detail in our product."
@ -61,7 +61,7 @@ msgstr "Because signing should be celebrated. Thats 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 dont have to worry about getting paid."
msgstr "Integrated payments with Stripe so you dont 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 "Its up to you. Either clone our repository or rely on our easy to use hosting solution."
msgstr "Its 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."

View File

@ -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(),

View 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;
}
};

View File

@ -0,0 +1,3 @@
export * from '@sindresorhus/slugify';
export { default as slugify } from '@sindresorhus/slugify';