From 4b485268ca96cc35b91cb41acab63749c0d129c8 Mon Sep 17 00:00:00 2001 From: Catalin Pit <25515812+catalinpit@users.noreply.github.com> Date: Tue, 30 Jul 2024 08:13:22 +0200 Subject: [PATCH 01/28] fix: dateformat api bug (#1238) Fixes support for date formats when using the API --- packages/api/v1/implementation.ts | 39 ++++++++++++++++++- packages/api/v1/schema.ts | 21 +++++++++- .../primitives/document-flow/add-settings.tsx | 22 +++++++++-- 3 files changed, 75 insertions(+), 7 deletions(-) diff --git a/packages/api/v1/implementation.ts b/packages/api/v1/implementation.ts index 6bcd767e9..74afa80c0 100644 --- a/packages/api/v1/implementation.ts +++ b/packages/api/v1/implementation.ts @@ -2,6 +2,9 @@ import { createNextRoute } from '@ts-rest/next'; import { getServerLimits } from '@documenso/ee/server-only/limits/server'; import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; +import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats'; +import '@documenso/lib/constants/time-zones'; +import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones'; import { AppError } from '@documenso/lib/errors/app-error'; import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data'; import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta'; @@ -222,6 +225,36 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { }; } + const dateFormat = body.meta.dateFormat + ? DATE_FORMATS.find((format) => format.label === body.meta.dateFormat) + : DATE_FORMATS.find((format) => format.value === DEFAULT_DOCUMENT_DATE_FORMAT); + const timezone = body.meta.timezone + ? TIME_ZONES.find((tz) => tz === body.meta.timezone) + : DEFAULT_DOCUMENT_TIME_ZONE; + + const isDateFormatValid = body.meta.dateFormat + ? DATE_FORMATS.some((format) => format.label === dateFormat?.label) + : true; + const isTimeZoneValid = body.meta.timezone ? TIME_ZONES.includes(String(timezone)) : true; + + if (!isDateFormatValid) { + return { + status: 400, + body: { + message: 'Invalid date format. Please provide a valid date format', + }, + }; + } + + if (!isTimeZoneValid) { + return { + status: 400, + body: { + message: 'Invalid timezone. Please provide a valid timezone', + }, + }; + } + const fileName = body.title.endsWith('.pdf') ? body.title : `${body.title}.pdf`; const { url, key } = await getPresignPostUrl(fileName, 'application/pdf'); @@ -244,7 +277,11 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { await upsertDocumentMeta({ documentId: document.id, userId: user.id, - ...body.meta, + subject: body.meta.subject, + message: body.meta.message, + timezone, + dateFormat: dateFormat?.value, + redirectUrl: body.meta.redirectUrl, requestMetadata: extractNextApiRequestMetadata(args.req), }); diff --git a/packages/api/v1/schema.ts b/packages/api/v1/schema.ts index dddc0a0e1..90d3f65bf 100644 --- a/packages/api/v1/schema.ts +++ b/packages/api/v1/schema.ts @@ -1,5 +1,9 @@ +import { extendZodWithOpenApi } from '@anatine/zod-openapi'; import { z } from 'zod'; +import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats'; +import '@documenso/lib/constants/time-zones'; +import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones'; import { ZUrlSchema } from '@documenso/lib/schemas/common'; import { DocumentDataType, @@ -11,6 +15,8 @@ import { TemplateType, } from '@documenso/prisma/client'; +extendZodWithOpenApi(z); + export const ZNoBodyMutationSchema = null; /** @@ -97,8 +103,19 @@ export const ZCreateDocumentMutationSchema = z.object({ .object({ subject: z.string(), message: z.string(), - timezone: z.string(), - dateFormat: z.string(), + timezone: z.string().default(DEFAULT_DOCUMENT_TIME_ZONE).openapi({ + description: + 'The timezone of the date. Must be one of the options listed in the list below.', + enum: TIME_ZONES, + }), + dateFormat: z + .string() + .default(DEFAULT_DOCUMENT_DATE_FORMAT) + .openapi({ + description: + 'The format of the date. Must be one of the options listed in the list below.', + enum: DATE_FORMATS.map((format) => format.value), + }), redirectUrl: z.string(), }) .partial(), diff --git a/packages/ui/primitives/document-flow/add-settings.tsx b/packages/ui/primitives/document-flow/add-settings.tsx index dddba289c..a95ca29db 100644 --- a/packages/ui/primitives/document-flow/add-settings.tsx +++ b/packages/ui/primitives/document-flow/add-settings.tsx @@ -82,8 +82,12 @@ export const AddSettingsFormPartial = ({ globalAccessAuth: documentAuthOption?.globalAccessAuth || undefined, globalActionAuth: documentAuthOption?.globalActionAuth || undefined, meta: { - timezone: document.documentMeta?.timezone ?? DEFAULT_DOCUMENT_TIME_ZONE, - dateFormat: document.documentMeta?.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT, + timezone: + TIME_ZONES.find((timezone) => timezone === document.documentMeta?.timezone) ?? + DEFAULT_DOCUMENT_TIME_ZONE, + dateFormat: + DATE_FORMATS.find((format) => format.label === document.documentMeta?.dateFormat) + ?.value ?? DEFAULT_DOCUMENT_DATE_FORMAT, redirectUrl: document.documentMeta?.redirectUrl ?? '', }, }, @@ -98,10 +102,20 @@ export const AddSettingsFormPartial = ({ // We almost always want to set the timezone to the user's local timezone to avoid confusion // when the document is signed. useEffect(() => { - if (!form.formState.touchedFields.meta?.timezone && !documentHasBeenSent) { + if ( + !form.formState.touchedFields.meta?.timezone && + !documentHasBeenSent && + !document.documentMeta?.timezone + ) { form.setValue('meta.timezone', Intl.DateTimeFormat().resolvedOptions().timeZone); } - }, [documentHasBeenSent, form, form.setValue, form.formState.touchedFields.meta?.timezone]); + }, [ + documentHasBeenSent, + form, + form.setValue, + form.formState.touchedFields.meta?.timezone, + document.documentMeta?.timezone, + ]); return ( <> From 0f86eed6acab2b09c9c862aed5eef78a4e0c35af Mon Sep 17 00:00:00 2001 From: Ephraim Duncan <55143799+ephraimduncan@users.noreply.github.com> Date: Tue, 30 Jul 2024 06:14:31 +0000 Subject: [PATCH 02/28] fix: add new environmental variables for render deployment (#1174) Updates the `render.yaml` configuration file by adding new environment variables --- render.yaml | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/render.yaml b/render.yaml index e3675f526..9e1565621 100644 --- a/render.yaml +++ b/render.yaml @@ -1,11 +1,11 @@ services: - type: web + runtime: node name: documenso-app - env: node plan: free buildCommand: npm i && npm run build:web - startCommand: npx prisma migrate deploy --schema packages/prisma/schema.prisma && npm run start - healthCheckPath: /api/trpc/health + startCommand: npx prisma migrate deploy --schema packages/prisma/schema.prisma && npx turbo run start --filter=@documenso/web + healthCheckPath: /api/health envVars: # Node Version @@ -98,6 +98,62 @@ services: - key: NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY sync: false + # Crypto + - key: NEXT_PRIVATE_ENCRYPTION_KEY + generateValue: true + - key: NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY + generateValue: true + + # Auth Optional + - key: NEXT_PRIVATE_GOOGLE_CLIENT_ID + sync: false + - key: NEXT_PRIVATE_GOOGLE_CLIENT_SECRET + sync: false + + # Signing + - key: NEXT_PRIVATE_SIGNING_TRANSPORT + sync: false + - key: NEXT_PRIVATE_SIGNING_PASSPHRASE + sync: false + - key: NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH + sync: false + - key: NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS + sync: false + - key: NEXT_PRIVATE_SIGNING_GCLOUD_HSM_KEY_PATH + sync: false + - key: NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_PATH + sync: false + - key: NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_CONTENTS + sync: false + - key: NEXT_PRIVATE_SIGNING_GCLOUD_APPLICATION_CREDENTIALS_CONTENTS + sync: false + + # SMTP Optional + - key: NEXT_PRIVATE_SMTP_APIKEY_USER + sync: false + - key: NEXT_PRIVATE_SMTP_APIKEY + sync: false + - key: NEXT_PRIVATE_SMTP_SECURE + sync: false + - key: NEXT_PRIVATE_RESEND_API_KEY + sync: false + - key: NEXT_PRIVATE_MAILCHANNELS_API_KEY + sync: false + - key: NEXT_PRIVATE_MAILCHANNELS_ENDPOINT + sync: false + - key: NEXT_PRIVATE_MAILCHANNELS_DKIM_DOMAIN + sync: false + - key: NEXT_PRIVATE_MAILCHANNELS_DKIM_SELECTOR + sync: false + - key: NEXT_PRIVATE_MAILCHANNELS_DKIM_PRIVATE_KEY + sync: false + - key: NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT + sync: false + + # Features Optional + - key: NEXT_PUBLIC_DISABLE_SIGNUP + sync: false + databases: - name: documenso-db plan: free From 0c744a11235a34262828786dfcf3ff2d3f018649 Mon Sep 17 00:00:00 2001 From: Timur Ercan Date: Tue, 30 Jul 2024 18:21:58 +0200 Subject: [PATCH 03/28] chore: last touches (#1263) fair use policy and support page ## Summary by CodeRabbit - **New Features** - Added a "Support" documentation page outlining various support options for users, including Community, Paid Account, and Enterprise support. - Introduced a "Fair Use Policy" documentation page, providing guidelines and reassurance regarding usage of the service. - Enhanced the account creation documentation with additional information about the Fair Use Policy. - **Improvements** - Updated the documentation structure with new sections for easier navigation, including "Support" and "Fair Use Policy." --- apps/documentation/pages/users/_meta.json | 2 + apps/documentation/pages/users/fair-use.mdx | 34 +++++++++++++++++ .../users/get-started/account-creation.mdx | 4 +- apps/documentation/pages/users/support.mdx | 38 +++++++++++++++++++ 4 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 apps/documentation/pages/users/fair-use.mdx create mode 100644 apps/documentation/pages/users/support.mdx diff --git a/apps/documentation/pages/users/_meta.json b/apps/documentation/pages/users/_meta.json index c5e3ce20a..3ccb8e7c6 100644 --- a/apps/documentation/pages/users/_meta.json +++ b/apps/documentation/pages/users/_meta.json @@ -1,5 +1,6 @@ { "index": "Introduction", + "support": "Support", "-- How To Use": { "type": "separator", "title": "How To Use" @@ -13,6 +14,7 @@ "type": "separator", "title": "Legal Overview" }, + "fair-use": "Fair Use Policy", "licenses": "Licenses", "compliance": "Compliance" } diff --git a/apps/documentation/pages/users/fair-use.mdx b/apps/documentation/pages/users/fair-use.mdx new file mode 100644 index 000000000..2b2eb605a --- /dev/null +++ b/apps/documentation/pages/users/fair-use.mdx @@ -0,0 +1,34 @@ +--- +title: Fair Use Policy +description: Learn about our fair use policy, which enables us to have unlimited plans. +--- + +import { Callout } from 'nextra/components'; + +# Fair Use Policy + +### Why + +We offer our plans without any limits on volume because we want our users and customers to make the most of their accounts. Estimating volume is incredibly hard, especially for shorter intervals like a quarter. We are not interested in selling volume packages our customers end up not using. This is why the individual plan and the team plan do not include a limit on signing or API volume. If you are a customer of these [plans](https://documen.so/pricing), we ask you to abide by this fair use policy: + +### Spirit of the Plan + +> Use the limitless accounts as much as you like (they are meant to offer a lot) while respecting the spirit and intended scope of the account. + + + What happens if I violate this policy? We will ask you to upgrade to a fitting plan or custom + pricing. We won’t block your account without reaching out. [Message + us](mailto:support@documenso.com) for questions. It's probably fine, though. + + +### DO + +- Sign as many documents with the individual plan for your single business or organization you are part of +- Use the API and Zapier to automate all your signing to sign as much as possible +- Experiment with the plans and integrations, testing what you want to build: When in doubt, do it. Especially if you are just starting. + +### DON'T + +- Use the individual account's API to power a platform +- Run a huge company, signing thousands of documents per day on a two-user team plan using the API +- Let this policy make you overthink. If you are a paying customer, we want you to win, and it's probably fine diff --git a/apps/documentation/pages/users/get-started/account-creation.mdx b/apps/documentation/pages/users/get-started/account-creation.mdx index 3a60cee98..bf1f67983 100644 --- a/apps/documentation/pages/users/get-started/account-creation.mdx +++ b/apps/documentation/pages/users/get-started/account-creation.mdx @@ -3,7 +3,7 @@ title: Create Your Account description: Learn how to create an account on Documenso. --- -import { Steps } from 'nextra/components'; +import { Callout, Steps } from 'nextra/components'; # Create Your Account @@ -14,6 +14,8 @@ The first step to start using Documenso is to pick a plan and create an account. Explore each plan's features and choose the one that best suits your needs. The [pricing page](https://documen.so/pricing) has more information about the plans. +All plans are subject to our [Fair Use Policy](/users/fair-use). + ### Create an account If you are unsure which plan to choose, you can start with the free plan and upgrade later. diff --git a/apps/documentation/pages/users/support.mdx b/apps/documentation/pages/users/support.mdx new file mode 100644 index 000000000..7eb00636f --- /dev/null +++ b/apps/documentation/pages/users/support.mdx @@ -0,0 +1,38 @@ +--- +title: Support +description: Learn what types of support we offer. +--- + +# Support + +## Community Support + +If you are a developer or free user, you can reach out to the community or raise an issue: + +### [Create Github Issues](https://github.com/documenso/documenso/issues) + +The community and the core team address GitHub issues. Be sure to check if a similar issue already exists. Please note that while we want to address everything immediately, we must prioritize. + +### [Join our Discord](https://documen.so/discord) + +You can ask for help in the [community help channel](https://discord.com/channels/1132216843537485854/1133419426524430376). + +## Paid Account Support + +### Email: support@documenso.com + +If you are paying customers facing issues, email our customer support, especially in urgent cases. + +### Private Discord channel + +If you prefer Discord, we can invite you to a private channel. Message support to make this happen. + +## Enterprise Support + +### Email: support@documenso.com + +If you are paying customers facing issues, email our customer support, especially in urgent cases. + +### Slack + +If your team is on Slack, we can create a private workspace to support you more closely. From a9025b5d97af7ab1115b65b2c7d4474ae8ba5e0d Mon Sep 17 00:00:00 2001 From: aeris <51246+aeris@users.noreply.github.com> Date: Wed, 31 Jul 2024 07:26:05 +0200 Subject: [PATCH 04/28] fix: use native URL parser instead of wrong regex (#1206) Updates the current regex based approach for validating redirect urls to instead use the native URL constructor which is available in browsers and Node.js and handles several valid cases that were previously not working. --- packages/lib/constants/url-regex.ts | 2 -- packages/lib/schemas/common.ts | 6 +++--- packages/lib/utils/is-valid-redirect-url.ts | 16 ++++++++++++++++ packages/trpc/server/document-router/schema.ts | 12 +++++++----- packages/trpc/server/template-router/schema.ts | 7 ++++--- .../document-flow/add-settings.types.ts | 7 ++++--- .../add-template-settings.types.tsx | 7 ++++--- 7 files changed, 38 insertions(+), 19 deletions(-) delete mode 100644 packages/lib/constants/url-regex.ts create mode 100644 packages/lib/utils/is-valid-redirect-url.ts diff --git a/packages/lib/constants/url-regex.ts b/packages/lib/constants/url-regex.ts deleted file mode 100644 index 1dfb70ad3..000000000 --- a/packages/lib/constants/url-regex.ts +++ /dev/null @@ -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; diff --git a/packages/lib/schemas/common.ts b/packages/lib/schemas/common.ts index 101aeeff5..c63b9b399 100644 --- a/packages/lib/schemas/common.ts +++ b/packages/lib/schemas/common.ts @@ -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.', }); diff --git a/packages/lib/utils/is-valid-redirect-url.ts b/packages/lib/utils/is-valid-redirect-url.ts new file mode 100644 index 000000000..e89818ac2 --- /dev/null +++ b/packages/lib/utils/is-valid-redirect-url.ts @@ -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; + } +}; diff --git a/packages/trpc/server/document-router/schema.ts b/packages/trpc/server/document-router/schema.ts index ac278b1d8..80efc7c15 100644 --- a/packages/trpc/server/document-router/schema.ts +++ b/packages/trpc/server/document-router/schema.ts @@ -1,11 +1,11 @@ import { z } from 'zod'; -import { URL_REGEX } from '@documenso/lib/constants/url-regex'; import { ZDocumentAccessAuthTypesSchema, ZDocumentActionAuthTypesSchema, } from '@documenso/lib/types/document-auth'; import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params'; +import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url'; import { FieldType, RecipientRole } from '@documenso/prisma/client'; export const ZFindDocumentAuditLogsQuerySchema = ZBaseTableSearchParamsSchema.extend({ @@ -65,8 +65,9 @@ export const ZSetSettingsForDocumentMutationSchema = z.object({ redirectUrl: z .string() .optional() - .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.', }), }), }); @@ -131,8 +132,9 @@ export const ZSendDocumentMutationSchema = z.object({ redirectUrl: z .string() .optional() - .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.', }), }), }); diff --git a/packages/trpc/server/template-router/schema.ts b/packages/trpc/server/template-router/schema.ts index 29f815f35..671cc50fd 100644 --- a/packages/trpc/server/template-router/schema.ts +++ b/packages/trpc/server/template-router/schema.ts @@ -1,11 +1,11 @@ import { z } from 'zod'; -import { URL_REGEX } from '@documenso/lib/constants/url-regex'; import { ZDocumentAccessAuthTypesSchema, ZDocumentActionAuthTypesSchema, } from '@documenso/lib/types/document-auth'; import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params'; +import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url'; import { TemplateType } from '@documenso/prisma/client'; import { ZSignFieldWithTokenMutationSchema } from '../field-router/schema'; @@ -96,8 +96,9 @@ export const ZUpdateTemplateSettingsMutationSchema = z.object({ redirectUrl: z .string() .optional() - .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.', }), }) .optional(), diff --git a/packages/ui/primitives/document-flow/add-settings.types.ts b/packages/ui/primitives/document-flow/add-settings.types.ts index a31e4a9bc..df4c6efcf 100644 --- a/packages/ui/primitives/document-flow/add-settings.types.ts +++ b/packages/ui/primitives/document-flow/add-settings.types.ts @@ -2,11 +2,11 @@ import { z } from 'zod'; import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats'; import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones'; -import { URL_REGEX } from '@documenso/lib/constants/url-regex'; import { ZDocumentAccessAuthTypesSchema, ZDocumentActionAuthTypesSchema, } from '@documenso/lib/types/document-auth'; +import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url'; export const ZMapNegativeOneToUndefinedSchema = z .string() @@ -34,8 +34,9 @@ export const ZAddSettingsFormSchema = z.object({ redirectUrl: z .string() .optional() - .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.', }), }), }); diff --git a/packages/ui/primitives/template-flow/add-template-settings.types.tsx b/packages/ui/primitives/template-flow/add-template-settings.types.tsx index 7e54d97b8..4f0d3f61a 100644 --- a/packages/ui/primitives/template-flow/add-template-settings.types.tsx +++ b/packages/ui/primitives/template-flow/add-template-settings.types.tsx @@ -2,11 +2,11 @@ import { z } from 'zod'; import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats'; import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones'; -import { URL_REGEX } from '@documenso/lib/constants/url-regex'; import { ZDocumentAccessAuthTypesSchema, ZDocumentActionAuthTypesSchema, } from '@documenso/lib/types/document-auth'; +import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url'; import { ZMapNegativeOneToUndefinedSchema } from '../document-flow/add-settings.types'; @@ -27,8 +27,9 @@ export const ZAddTemplateSettingsFormSchema = z.object({ redirectUrl: z .string() .optional() - .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.', }), }), }); From 7ed0a909eb5efecfc1383fd2692cb374d7dace67 Mon Sep 17 00:00:00 2001 From: Rene Steen Date: Wed, 31 Jul 2024 07:38:12 +0200 Subject: [PATCH 05/28] feat: allow oidc only signup and trust mail addresses (#1208) This change will allow for user registration when users are federated through oidc provider even if the general signup is disabled additionally the users email address can now be automatically set as trusted. This will force corporate users to signin using SSO instead of creating manual accounts. --- .env.example | 4 +++ apps/web/process-env.d.ts | 2 ++ apps/web/src/pages/api/auth/[...nextauth].ts | 14 ++++++++-- packages/lib/next-auth/auth-options.ts | 11 +++++++- packages/tsconfig/process-env.d.ts | 2 ++ turbo.json | 28 ++++++-------------- 6 files changed, 38 insertions(+), 23 deletions(-) diff --git a/.env.example b/.env.example index a07419aaa..19f056cd8 100644 --- a/.env.example +++ b/.env.example @@ -16,6 +16,10 @@ NEXT_PRIVATE_GOOGLE_CLIENT_SECRET="" NEXT_PRIVATE_OIDC_WELL_KNOWN="" NEXT_PRIVATE_OIDC_CLIENT_ID="" NEXT_PRIVATE_OIDC_CLIENT_SECRET="" +# This can be used to still allow signups for OIDC connections +# when signup is disabled via `NEXT_PUBLIC_DISABLE_SIGNUP` +NEXT_PRIVATE_OIDC_ALLOW_SIGNUP="" +NEXT_PRIVATE_OIDC_SKIP_VERIFY="" # [[URLS]] NEXT_PUBLIC_WEBAPP_URL="http://localhost:3000" diff --git a/apps/web/process-env.d.ts b/apps/web/process-env.d.ts index 63a341060..8bdfa41f9 100644 --- a/apps/web/process-env.d.ts +++ b/apps/web/process-env.d.ts @@ -16,5 +16,7 @@ declare namespace NodeJS { NEXT_PRIVATE_OIDC_WELL_KNOWN: string; NEXT_PRIVATE_OIDC_CLIENT_ID: string; NEXT_PRIVATE_OIDC_CLIENT_SECRET: string; + NEXT_PRIVATE_OIDC_ALLOW_SIGNUP?: string; + NEXT_PRIVATE_OIDC_SKIP_VERIFY?: string; } } diff --git a/apps/web/src/pages/api/auth/[...nextauth].ts b/apps/web/src/pages/api/auth/[...nextauth].ts index 31f6e9ea3..44c509c62 100644 --- a/apps/web/src/pages/api/auth/[...nextauth].ts +++ b/apps/web/src/pages/api/auth/[...nextauth].ts @@ -60,13 +60,23 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) { }, }); }, - linkAccount: async ({ user }) => { + linkAccount: async ({ user, account, profile }) => { const userId = typeof user.id === 'string' ? parseInt(user.id) : user.id; - if (isNaN(userId)) { + if (Number.isNaN(userId)) { return; } + // If the user is linking an OIDC account and the email verified date is set then update it in the db. + if (account.provider === 'oidc' && profile.emailVerified !== null) { + await prisma.user.update({ + where: { id: userId }, + data: { + emailVerified: profile.emailVerified, + }, + }); + } + await prisma.userSecurityAuditLog.create({ data: { userId, diff --git a/packages/lib/next-auth/auth-options.ts b/packages/lib/next-auth/auth-options.ts index b0d27589c..fa4413115 100644 --- a/packages/lib/next-auth/auth-options.ts +++ b/packages/lib/next-auth/auth-options.ts @@ -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') { diff --git a/packages/tsconfig/process-env.d.ts b/packages/tsconfig/process-env.d.ts index 58447155b..5ef087199 100644 --- a/packages/tsconfig/process-env.d.ts +++ b/packages/tsconfig/process-env.d.ts @@ -9,6 +9,8 @@ declare namespace NodeJS { NEXT_PRIVATE_OIDC_WELL_KNOWN?: string; NEXT_PRIVATE_OIDC_CLIENT_ID?: string; NEXT_PRIVATE_OIDC_CLIENT_SECRET?: string; + NEXT_PRIVATE_OIDC_ALLOW_SIGNUP?: string; + NEXT_PRIVATE_OIDC_SKIP_VERIFY?: string; NEXT_PRIVATE_DATABASE_URL: string; NEXT_PRIVATE_ENCRYPTION_KEY: string; diff --git a/turbo.json b/turbo.json index c2057dcf3..a28e2e3a5 100644 --- a/turbo.json +++ b/turbo.json @@ -2,20 +2,12 @@ "$schema": "https://turbo.build/schema.json", "pipeline": { "build": { - "dependsOn": [ - "prebuild", - "^build" - ], - "outputs": [ - ".next/**", - "!.next/cache/**" - ] + "dependsOn": ["prebuild", "^build"], + "outputs": [".next/**", "!.next/cache/**"] }, "prebuild": { "cache": false, - "dependsOn": [ - "^prebuild" - ] + "dependsOn": ["^prebuild"] }, "lint": { "cache": false @@ -31,9 +23,7 @@ "persistent": true }, "start": { - "dependsOn": [ - "^build" - ], + "dependsOn": ["^build"], "cache": false, "persistent": true }, @@ -41,18 +31,14 @@ "cache": false }, "test:e2e": { - "dependsOn": [ - "^build" - ], + "dependsOn": ["^build"], "cache": false }, "translate:compile": { "cache": false } }, - "globalDependencies": [ - "**/.env.*local" - ], + "globalDependencies": ["**/.env.*local"], "globalEnv": [ "APP_VERSION", "NEXT_PRIVATE_ENCRYPTION_KEY", @@ -83,6 +69,8 @@ "NEXT_PRIVATE_OIDC_WELL_KNOWN", "NEXT_PRIVATE_OIDC_CLIENT_ID", "NEXT_PRIVATE_OIDC_CLIENT_SECRET", + "NEXT_PRIVATE_OIDC_ALLOW_SIGNUP", + "NEXT_PRIVATE_OIDC_SKIP_VERIFY", "NEXT_PUBLIC_UPLOAD_TRANSPORT", "NEXT_PRIVATE_UPLOAD_ENDPOINT", "NEXT_PRIVATE_UPLOAD_FORCE_PATH_STYLE", From 5582f29bda3b859d94a9bd7ce4a7d0ade3f0d320 Mon Sep 17 00:00:00 2001 From: Rene Steen Date: Wed, 31 Jul 2024 14:22:52 +0200 Subject: [PATCH 06/28] feat: make oidc sign in button text configurable (#1209) Adds a configurable label for the OIDC connection's button. --- .env.example | 1 + apps/web/src/app/(unauthenticated)/signin/page.tsx | 7 ++++++- apps/web/src/components/forms/signin.tsx | 4 +++- packages/lib/constants/auth.ts | 2 ++ packages/tsconfig/process-env.d.ts | 1 + turbo.json | 1 + 6 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 19f056cd8..bba951b86 100644 --- a/.env.example +++ b/.env.example @@ -16,6 +16,7 @@ NEXT_PRIVATE_GOOGLE_CLIENT_SECRET="" NEXT_PRIVATE_OIDC_WELL_KNOWN="" NEXT_PRIVATE_OIDC_CLIENT_ID="" NEXT_PRIVATE_OIDC_CLIENT_SECRET="" +NEXT_PRIVATE_OIDC_PROVIDER_LABEL="OIDC" # This can be used to still allow signups for OIDC connections # when signup is disabled via `NEXT_PUBLIC_DISABLE_SIGNUP` NEXT_PRIVATE_OIDC_ALLOW_SIGNUP="" diff --git a/apps/web/src/app/(unauthenticated)/signin/page.tsx b/apps/web/src/app/(unauthenticated)/signin/page.tsx index a0599ac1a..8efa19abc 100644 --- a/apps/web/src/app/(unauthenticated)/signin/page.tsx +++ b/apps/web/src/app/(unauthenticated)/signin/page.tsx @@ -4,7 +4,11 @@ import { redirect } from 'next/navigation'; import { env } from 'next-runtime-env'; -import { IS_GOOGLE_SSO_ENABLED, IS_OIDC_SSO_ENABLED } from '@documenso/lib/constants/auth'; +import { + IS_GOOGLE_SSO_ENABLED, + IS_OIDC_SSO_ENABLED, + OIDC_PROVIDER_LABEL, +} from '@documenso/lib/constants/auth'; import { decryptSecondaryData } from '@documenso/lib/server-only/crypto/decrypt'; import { SignInForm } from '~/components/forms/signin'; @@ -43,6 +47,7 @@ export default function SignInPage({ searchParams }: SignInPageProps) { initialEmail={email || undefined} isGoogleSSOEnabled={IS_GOOGLE_SSO_ENABLED} isOIDCSSOEnabled={IS_OIDC_SSO_ENABLED} + oidcProviderLabel={OIDC_PROVIDER_LABEL} /> {NEXT_PUBLIC_DISABLE_SIGNUP !== 'true' && ( diff --git a/apps/web/src/components/forms/signin.tsx b/apps/web/src/components/forms/signin.tsx index e86ad492f..8edccfc45 100644 --- a/apps/web/src/components/forms/signin.tsx +++ b/apps/web/src/components/forms/signin.tsx @@ -71,6 +71,7 @@ export type SignInFormProps = { initialEmail?: string; isGoogleSSOEnabled?: boolean; isOIDCSSOEnabled?: boolean; + oidcProviderLabel?: string; }; export const SignInForm = ({ @@ -78,6 +79,7 @@ export const SignInForm = ({ initialEmail, isGoogleSSOEnabled, isOIDCSSOEnabled, + oidcProviderLabel, }: SignInFormProps) => { const { toast } = useToast(); const { getFlag } = useFeatureFlags(); @@ -369,7 +371,7 @@ export const SignInForm = ({ onClick={onSignInWithOIDCClick} > - OIDC + {oidcProviderLabel || 'OIDC'} )} diff --git a/packages/lib/constants/auth.ts b/packages/lib/constants/auth.ts index 4df19b407..a8b5f31a2 100644 --- a/packages/lib/constants/auth.ts +++ b/packages/lib/constants/auth.ts @@ -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', diff --git a/packages/tsconfig/process-env.d.ts b/packages/tsconfig/process-env.d.ts index 5ef087199..5eac76101 100644 --- a/packages/tsconfig/process-env.d.ts +++ b/packages/tsconfig/process-env.d.ts @@ -9,6 +9,7 @@ declare namespace NodeJS { NEXT_PRIVATE_OIDC_WELL_KNOWN?: string; NEXT_PRIVATE_OIDC_CLIENT_ID?: string; NEXT_PRIVATE_OIDC_CLIENT_SECRET?: string; + NEXT_PRIVATE_OIDC_PROVIDER_LABEL?: string; NEXT_PRIVATE_OIDC_ALLOW_SIGNUP?: string; NEXT_PRIVATE_OIDC_SKIP_VERIFY?: string; diff --git a/turbo.json b/turbo.json index a28e2e3a5..95acc04cf 100644 --- a/turbo.json +++ b/turbo.json @@ -69,6 +69,7 @@ "NEXT_PRIVATE_OIDC_WELL_KNOWN", "NEXT_PRIVATE_OIDC_CLIENT_ID", "NEXT_PRIVATE_OIDC_CLIENT_SECRET", + "NEXT_PRIVATE_OIDC_PROVIDER_LABEL", "NEXT_PRIVATE_OIDC_ALLOW_SIGNUP", "NEXT_PRIVATE_OIDC_SKIP_VERIFY", "NEXT_PUBLIC_UPLOAD_TRANSPORT", From 1beb434a729949ce291673b0294954f28682810e Mon Sep 17 00:00:00 2001 From: emmpaz <59887297+emmpaz@users.noreply.github.com> Date: Wed, 31 Jul 2024 07:57:27 -0500 Subject: [PATCH 07/28] fix: limits syncing issue (#1195) Exposes `refreshLimits()` to be able to keep the limit in sync when deleting/creating a document. --- .../documents/delete-document-dialog.tsx | 3 +++ .../(dashboard)/documents/upload-document.tsx | 4 ++- .../ee/server-only/limits/provider/client.tsx | 25 +++++++++++++------ .../ee/server-only/limits/provider/server.tsx | 3 +-- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/apps/web/src/app/(dashboard)/documents/delete-document-dialog.tsx b/apps/web/src/app/(dashboard)/documents/delete-document-dialog.tsx index 558d39558..042dc5e93 100644 --- a/apps/web/src/app/(dashboard)/documents/delete-document-dialog.tsx +++ b/apps/web/src/app/(dashboard)/documents/delete-document-dialog.tsx @@ -4,6 +4,7 @@ import { useRouter } from 'next/navigation'; import { match } from 'ts-pattern'; +import { useLimits } from '@documenso/ee/server-only/limits/provider/client'; import { DocumentStatus } from '@documenso/prisma/client'; import { trpc as trpcReact } from '@documenso/trpc/react'; import { Alert, AlertDescription } from '@documenso/ui/primitives/alert'; @@ -41,6 +42,7 @@ export const DeleteDocumentDialog = ({ const router = useRouter(); const { toast } = useToast(); + const { refreshLimits } = useLimits(); const [inputValue, setInputValue] = useState(''); const [isDeleteEnabled, setIsDeleteEnabled] = useState(status === DocumentStatus.DRAFT); @@ -48,6 +50,7 @@ export const DeleteDocumentDialog = ({ const { mutateAsync: deleteDocument, isLoading } = trpcReact.document.deleteDocument.useMutation({ onSuccess: () => { router.refresh(); + void refreshLimits(); toast({ title: 'Document deleted', diff --git a/apps/web/src/app/(dashboard)/documents/upload-document.tsx b/apps/web/src/app/(dashboard)/documents/upload-document.tsx index 0a0c029ab..f52aecb94 100644 --- a/apps/web/src/app/(dashboard)/documents/upload-document.tsx +++ b/apps/web/src/app/(dashboard)/documents/upload-document.tsx @@ -36,7 +36,7 @@ export const UploadDocument = ({ className, team }: UploadDocumentProps) => { const { toast } = useToast(); - const { quota, remaining } = useLimits(); + const { quota, remaining, refreshLimits } = useLimits(); const [isLoading, setIsLoading] = useState(false); @@ -71,6 +71,8 @@ export const UploadDocument = ({ className, team }: UploadDocumentProps) => { teamId: team?.id, }); + void refreshLimits(); + toast({ title: 'Document uploaded', description: 'Your document has been uploaded successfully.', diff --git a/packages/ee/server-only/limits/provider/client.tsx b/packages/ee/server-only/limits/provider/client.tsx index fdc00b439..624b61d98 100644 --- a/packages/ee/server-only/limits/provider/client.tsx +++ b/packages/ee/server-only/limits/provider/client.tsx @@ -1,6 +1,6 @@ 'use client'; -import { createContext, useContext, useEffect, useState } from 'react'; +import { createContext, useCallback, useContext, useEffect, useState } from 'react'; import { equals } from 'remeda'; @@ -8,7 +8,7 @@ import { getLimits } from '../client'; import { FREE_PLAN_LIMITS } from '../constants'; import type { TLimitsResponseSchema } from '../schema'; -export type LimitsContextValue = TLimitsResponseSchema; +export type LimitsContextValue = TLimitsResponseSchema & { refreshLimits: () => Promise }; const LimitsContext = createContext(null); @@ -23,7 +23,7 @@ export const useLimits = () => { }; export type LimitsProviderProps = { - initialValue?: LimitsContextValue; + initialValue?: TLimitsResponseSchema; teamId?: number; children?: React.ReactNode; }; @@ -38,7 +38,7 @@ export const LimitsProvider = ({ }: LimitsProviderProps) => { const [limits, setLimits] = useState(() => initialValue); - const refreshLimits = async () => { + const refreshLimits = useCallback(async () => { const newLimits = await getLimits({ teamId }); setLimits((oldLimits) => { @@ -48,11 +48,11 @@ export const LimitsProvider = ({ return newLimits; }); - }; + }, [teamId]); useEffect(() => { void refreshLimits(); - }, []); + }, [refreshLimits]); useEffect(() => { const onFocus = () => { @@ -64,7 +64,16 @@ export const LimitsProvider = ({ return () => { window.removeEventListener('focus', onFocus); }; - }, []); + }, [refreshLimits]); - return {children}; + return ( + + {children} + + ); }; diff --git a/packages/ee/server-only/limits/provider/server.tsx b/packages/ee/server-only/limits/provider/server.tsx index b7cde3573..969361060 100644 --- a/packages/ee/server-only/limits/provider/server.tsx +++ b/packages/ee/server-only/limits/provider/server.tsx @@ -3,7 +3,6 @@ import { headers } from 'next/headers'; import { getLimits } from '../client'; -import type { LimitsContextValue } from './client'; import { LimitsProvider as ClientLimitsProvider } from './client'; export type LimitsProviderProps = { @@ -14,7 +13,7 @@ export type LimitsProviderProps = { export const LimitsProvider = async ({ children, teamId }: LimitsProviderProps) => { const requestHeaders = Object.fromEntries(headers().entries()); - const limits: LimitsContextValue = await getLimits({ headers: requestHeaders, teamId }); + const limits = await getLimits({ headers: requestHeaders, teamId }); return ( From 909c38f47e9cc2e852d78ca7f6187d8659d866cf Mon Sep 17 00:00:00 2001 From: Catalin Pit <25515812+catalinpit@users.noreply.github.com> Date: Wed, 31 Jul 2024 15:07:39 +0200 Subject: [PATCH 08/28] fix: fields name/label on dark mode (#1242) Updates the dark mode styling for field editing to improve readability. --- packages/ui/primitives/document-flow/add-fields.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/ui/primitives/document-flow/add-fields.tsx b/packages/ui/primitives/document-flow/add-fields.tsx index 8fe8d66ed..5ae55b7b3 100644 --- a/packages/ui/primitives/document-flow/add-fields.tsx +++ b/packages/ui/primitives/document-flow/add-fields.tsx @@ -457,10 +457,11 @@ export const AddFieldsFormPartial = ({ {selectedField && (
Date: Wed, 7 Aug 2024 16:58:27 -0700 Subject: [PATCH 09/28] chore: Update .env.example commenting (#1257) Adds documentation on how to setup Google for OAuth when self hosting Documenso. --- .env.example | 2 ++ .../pages/developers/self-hosting/_meta.json | 5 ++-- .../setting-up-oauth-providers.mdx | 29 +++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 apps/documentation/pages/developers/self-hosting/setting-up-oauth-providers.mdx diff --git a/.env.example b/.env.example index bba951b86..ed77d048a 100644 --- a/.env.example +++ b/.env.example @@ -10,6 +10,8 @@ NEXT_PRIVATE_ENCRYPTION_KEY="CAFEBABE" NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY="DEADBEEF" # [[AUTH OPTIONAL]] +# Find documentation on setting up Google OAuth here: +# https://docs.documenso.com/developers/self-hosting/setting-up-oauth-providers#google-oauth-gmail NEXT_PRIVATE_GOOGLE_CLIENT_ID="" NEXT_PRIVATE_GOOGLE_CLIENT_SECRET="" diff --git a/apps/documentation/pages/developers/self-hosting/_meta.json b/apps/documentation/pages/developers/self-hosting/_meta.json index b0d771a3e..5f40bbbc2 100644 --- a/apps/documentation/pages/developers/self-hosting/_meta.json +++ b/apps/documentation/pages/developers/self-hosting/_meta.json @@ -1,5 +1,6 @@ { "index": "Getting Started", "signing-certificate": "Signing Certificate", - "how-to": "How To" -} + "how-to": "How To", + "setting-up-oauth-providers": "Setting up OAuth Providers" +} \ No newline at end of file diff --git a/apps/documentation/pages/developers/self-hosting/setting-up-oauth-providers.mdx b/apps/documentation/pages/developers/self-hosting/setting-up-oauth-providers.mdx new file mode 100644 index 000000000..0ba359142 --- /dev/null +++ b/apps/documentation/pages/developers/self-hosting/setting-up-oauth-providers.mdx @@ -0,0 +1,29 @@ +--- +title: Setting up OAuth Providers +description: Learn how to set up OAuth providers for your own instance of Documenso. +--- + +## Google OAuth (Gmail) + +To use Google OAuth, you will need to create a Google Cloud Platform project and enable the Google Identity and Access Management (IAM) API. You will also need to create a new OAuth client ID and download the client secret. + +### Create and configure a new OAuth client ID + +1. Go to the [Google Cloud Platform Console](https://console.cloud.google.com/) +2. From the projects list, select a project or create a new one +3. If the APIs & services page isn't already open, open the console left side menu and select APIs & services +4. On the left, click Credentials +5. Click New Credentials, then select OAuth client ID +6. When prompted to select an application type, select Web application +7. Enter a name for your client ID, and click Create +8. Click the download button to download the client secret +9. Set the authorized javascript origins to `https://` +10. Set the authorized redirect URIs to `https:///api/auth/callback/google` +11. In the Documenso environment variables, set the following: + +``` +NEXT_PRIVATE_GOOGLE_CLIENT_ID= +NEXT_PRIVATE_GOOGLE_CLIENT_SECRET= +``` + +Finally verify the signing in with Google works by signing in with your Google account and checking the email address in your profile. From e0065a8731eced30a06844e0efafc97225686986 Mon Sep 17 00:00:00 2001 From: Ephraim Duncan <55143799+ephraimduncan@users.noreply.github.com> Date: Thu, 8 Aug 2024 00:47:00 +0000 Subject: [PATCH 10/28] fix: show signup option only to users without existing accounts (#1221) Changes the signup CTA to only display if the recipient doesn't already have an account. --- apps/web/src/app/(signing)/sign/[token]/complete/page.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx b/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx index f505b0692..66310a6e8 100644 --- a/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx @@ -13,6 +13,7 @@ import { isRecipientAuthorized } from '@documenso/lib/server-only/document/is-re import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token'; import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token'; import { getRecipientSignatures } from '@documenso/lib/server-only/recipient/get-recipient-signatures'; +import { getUserByEmail } from '@documenso/lib/server-only/user/get-user-by-email'; import { DocumentStatus, FieldType, RecipientRole } from '@documenso/prisma/client'; import { DocumentDownloadButton } from '@documenso/ui/components/document/document-download-button'; import { DocumentShareButton } from '@documenso/ui/components/document/document-share-button'; @@ -77,6 +78,9 @@ export default async function CompletedSigningPage({ } const signatures = await getRecipientSignatures({ recipientId: recipient.id }); + const isExistingUser = await getUserByEmail({ email: recipient.email }) + .then((u) => !!u) + .catch(() => false); const recipientName = recipient.name || @@ -85,7 +89,7 @@ export default async function CompletedSigningPage({ const sessionData = await getServerSession(); const isLoggedIn = !!sessionData?.user; - const canSignUp = !isLoggedIn && NEXT_PUBLIC_DISABLE_SIGNUP !== 'true'; + const canSignUp = !isExistingUser && NEXT_PUBLIC_DISABLE_SIGNUP !== 'true'; return (
Date: Fri, 9 Aug 2024 02:46:07 +0200 Subject: [PATCH 11/28] fix: radio and checkbox fields issues with empty values (#1273) Since we allow checkboxes and radio fields without a label (which we use for the value) we had an issue where multiple checkboxes with no value would exist and items would not end up checked on the resulting document. This change fixes that by adding a placeholder value for these empty checkboxes and labels. --- .../lib/server-only/pdf/insert-field-in-pdf.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/lib/server-only/pdf/insert-field-in-pdf.ts b/packages/lib/server-only/pdf/insert-field-in-pdf.ts index 401e03b05..9a1441650 100644 --- a/packages/lib/server-only/pdf/insert-field-in-pdf.ts +++ b/packages/lib/server-only/pdf/insert-field-in-pdf.ts @@ -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}`); From e5f73452b325d41ca60fc2aaea3a62e032dc3004 Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Fri, 9 Aug 2024 11:06:17 +1000 Subject: [PATCH 12/28] fix: support dynamic external ids for direct templates (#1274) Adds support for an `externalId` query param to be passed when linking a user to a direct template. This external id will then be stored on the document upon signing completion. --- .../src/app/(recipient)/d/[token]/direct-template.tsx | 10 +++++++++- .../template/create-document-from-direct-template.ts | 3 +++ packages/trpc/server/template-router/router.ts | 2 ++ packages/trpc/server/template-router/schema.ts | 1 + 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/(recipient)/d/[token]/direct-template.tsx b/apps/web/src/app/(recipient)/d/[token]/direct-template.tsx index 14878dbdd..f35ae1e94 100644 --- a/apps/web/src/app/(recipient)/d/[token]/direct-template.tsx +++ b/apps/web/src/app/(recipient)/d/[token]/direct-template.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; -import { useRouter } from 'next/navigation'; +import { useRouter, useSearchParams } from 'next/navigation'; import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles'; import type { Field } from '@documenso/prisma/client'; @@ -39,6 +39,7 @@ export const DirectTemplatePageView = ({ directTemplateToken, }: TemplatesDirectPageViewProps) => { const router = useRouter(); + const searchParams = useSearchParams(); const { toast } = useToast(); @@ -82,8 +83,15 @@ export const DirectTemplatePageView = ({ const onSignDirectTemplateSubmit = async (fields: DirectTemplateLocalField[]) => { try { + let directTemplateExternalId = searchParams?.get('externalId') || undefined; + + if (directTemplateExternalId) { + directTemplateExternalId = decodeURIComponent(directTemplateExternalId); + } + const token = await createDocumentFromDirectTemplate({ directTemplateToken, + directTemplateExternalId, directRecipientName: fullName, directRecipientEmail: recipient.email, templateUpdatedAt: template.updatedAt, diff --git a/packages/lib/server-only/template/create-document-from-direct-template.ts b/packages/lib/server-only/template/create-document-from-direct-template.ts index e7f119dfa..8ec4e9f1c 100644 --- a/packages/lib/server-only/template/create-document-from-direct-template.ts +++ b/packages/lib/server-only/template/create-document-from-direct-template.ts @@ -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, diff --git a/packages/trpc/server/template-router/router.ts b/packages/trpc/server/template-router/router.ts index 2f2567d63..8513d52df 100644 --- a/packages/trpc/server/template-router/router.ts +++ b/packages/trpc/server/template-router/router.ts @@ -66,6 +66,7 @@ export const templateRouter = router({ directRecipientName, directRecipientEmail, directTemplateToken, + directTemplateExternalId, signedFieldValues, templateUpdatedAt, } = input; @@ -76,6 +77,7 @@ export const templateRouter = router({ directRecipientName, directRecipientEmail, directTemplateToken, + directTemplateExternalId, signedFieldValues, templateUpdatedAt, user: ctx.user diff --git a/packages/trpc/server/template-router/schema.ts b/packages/trpc/server/template-router/schema.ts index 671cc50fd..2d98f83d0 100644 --- a/packages/trpc/server/template-router/schema.ts +++ b/packages/trpc/server/template-router/schema.ts @@ -20,6 +20,7 @@ export const ZCreateDocumentFromDirectTemplateMutationSchema = z.object({ directRecipientName: z.string().optional(), directRecipientEmail: z.string().email(), directTemplateToken: z.string().min(1), + directTemplateExternalId: z.string().optional(), signedFieldValues: z.array(ZSignFieldWithTokenMutationSchema), templateUpdatedAt: z.date(), }); From 0244f021ab224c6718bc607ec88a4a8c87b38272 Mon Sep 17 00:00:00 2001 From: Catalin Pit <25515812+catalinpit@users.noreply.github.com> Date: Fri, 9 Aug 2024 04:19:48 +0200 Subject: [PATCH 13/28] fix: download audit log certificate (#1268) Previously, it wasn't possible to download an audit log of a document uploaded by another user because the function used the ID of the user making the request to retrieve the document. However, the document uploaded by another user has that user's ID, not the ID of the user making the request. --- .../documents/[id]/logs/document-logs-page-view.tsx | 2 +- .../[id]/logs/download-audit-log-button.tsx | 9 +++++++-- packages/trpc/server/document-router/router.ts | 12 ++++++++++-- packages/trpc/server/document-router/schema.ts | 5 +++++ 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/apps/web/src/app/(dashboard)/documents/[id]/logs/document-logs-page-view.tsx b/apps/web/src/app/(dashboard)/documents/[id]/logs/document-logs-page-view.tsx index cf68173dc..2069247fb 100644 --- a/apps/web/src/app/(dashboard)/documents/[id]/logs/document-logs-page-view.tsx +++ b/apps/web/src/app/(dashboard)/documents/[id]/logs/document-logs-page-view.tsx @@ -139,7 +139,7 @@ export const DocumentLogsPageView = async ({ params, team }: DocumentLogsPageVie documentStatus={document.status} /> - +
diff --git a/apps/web/src/app/(dashboard)/documents/[id]/logs/download-audit-log-button.tsx b/apps/web/src/app/(dashboard)/documents/[id]/logs/download-audit-log-button.tsx index 0847d63fa..8b662f5ce 100644 --- a/apps/web/src/app/(dashboard)/documents/[id]/logs/download-audit-log-button.tsx +++ b/apps/web/src/app/(dashboard)/documents/[id]/logs/download-audit-log-button.tsx @@ -9,10 +9,15 @@ import { useToast } from '@documenso/ui/primitives/use-toast'; export type DownloadAuditLogButtonProps = { className?: string; + teamId?: number; documentId: number; }; -export const DownloadAuditLogButton = ({ className, documentId }: DownloadAuditLogButtonProps) => { +export const DownloadAuditLogButton = ({ + className, + teamId, + documentId, +}: DownloadAuditLogButtonProps) => { const { toast } = useToast(); const { mutateAsync: downloadAuditLogs, isLoading } = @@ -20,7 +25,7 @@ export const DownloadAuditLogButton = ({ className, documentId }: DownloadAuditL const onDownloadAuditLogsClick = async () => { try { - const { url } = await downloadAuditLogs({ documentId }); + const { url } = await downloadAuditLogs({ teamId, documentId }); const iframe = Object.assign(document.createElement('iframe'), { src: url, diff --git a/packages/trpc/server/document-router/router.ts b/packages/trpc/server/document-router/router.ts index 07726b451..85c87e3b6 100644 --- a/packages/trpc/server/document-router/router.ts +++ b/packages/trpc/server/document-router/router.ts @@ -29,6 +29,7 @@ import { ZCreateDocumentMutationSchema, ZDeleteDraftDocumentMutationSchema as ZDeleteDocumentMutationSchema, ZDownloadAuditLogsMutationSchema, + ZDownloadCertificateMutationSchema, ZFindDocumentAuditLogsQuerySchema, ZGetDocumentByIdQuerySchema, ZGetDocumentByTokenQuerySchema, @@ -411,7 +412,14 @@ export const documentRouter = router({ id: documentId, userId: ctx.user.id, teamId, - }); + }).catch(() => null); + + if (!document || document.teamId !== teamId) { + throw new TRPCError({ + code: 'FORBIDDEN', + message: 'You do not have access to this document.', + }); + } const encrypted = encryptSecondaryData({ data: document.id.toString(), @@ -433,7 +441,7 @@ export const documentRouter = router({ }), downloadCertificate: authenticatedProcedure - .input(ZDownloadAuditLogsMutationSchema) + .input(ZDownloadCertificateMutationSchema) .mutation(async ({ input, ctx }) => { try { const { documentId, teamId } = input; diff --git a/packages/trpc/server/document-router/schema.ts b/packages/trpc/server/document-router/schema.ts index 80efc7c15..31e049173 100644 --- a/packages/trpc/server/document-router/schema.ts +++ b/packages/trpc/server/document-router/schema.ts @@ -172,6 +172,11 @@ export const ZDownloadAuditLogsMutationSchema = z.object({ teamId: z.number().optional(), }); +export const ZDownloadCertificateMutationSchema = z.object({ + documentId: z.number(), + teamId: z.number().optional(), +}); + export const ZMoveDocumentsToTeamSchema = z.object({ documentId: z.number(), teamId: z.number(), From ef3ecc33f1156f49d0287643c84f332bc7b7c3ea Mon Sep 17 00:00:00 2001 From: Mythie Date: Fri, 9 Aug 2024 15:49:41 +1000 Subject: [PATCH 14/28] v1.6.1-rc.0 --- apps/marketing/package.json | 4 ++-- apps/web/package.json | 4 ++-- package-lock.json | 8 ++++---- package.json | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/marketing/package.json b/apps/marketing/package.json index b11e39b08..19cd6912a 100644 --- a/apps/marketing/package.json +++ b/apps/marketing/package.json @@ -1,6 +1,6 @@ { "name": "@documenso/marketing", - "version": "1.6.0", + "version": "1.6.1-rc.0", "private": true, "license": "AGPL-3.0", "scripts": { @@ -64,4 +64,4 @@ "next": "$next" } } -} \ No newline at end of file +} diff --git a/apps/web/package.json b/apps/web/package.json index 8b72b5381..f0841a5c5 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@documenso/web", - "version": "1.6.0", + "version": "1.6.1-rc.0", "private": true, "license": "AGPL-3.0", "scripts": { @@ -81,4 +81,4 @@ "next": "$next" } } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index d4f8b058e..2eb6c1eec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@documenso/root", - "version": "1.6.0", + "version": "1.6.1-rc.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@documenso/root", - "version": "1.6.0", + "version": "1.6.1-rc.0", "workspaces": [ "apps/*", "packages/*" @@ -80,7 +80,7 @@ }, "apps/marketing": { "name": "@documenso/marketing", - "version": "1.6.0", + "version": "1.6.1-rc.0", "license": "AGPL-3.0", "dependencies": { "@documenso/assets": "*", @@ -424,7 +424,7 @@ }, "apps/web": { "name": "@documenso/web", - "version": "1.6.0", + "version": "1.6.1-rc.0", "license": "AGPL-3.0", "dependencies": { "@documenso/api": "*", diff --git a/package.json b/package.json index 3bbdac637..ed07b9826 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "1.6.0", + "version": "1.6.1-rc.0", "scripts": { "build": "turbo run build", "build:web": "turbo run build --filter=@documenso/web", @@ -80,4 +80,4 @@ "trigger.dev": { "endpointId": "documenso-app" } -} \ No newline at end of file +} From 29910ab2a7e6f6d650350e5d346667ccd548f35a Mon Sep 17 00:00:00 2001 From: Catalin Pit <25515812+catalinpit@users.noreply.github.com> Date: Mon, 12 Aug 2024 15:29:32 +0200 Subject: [PATCH 15/28] feat: add initials field type (#1279) Adds a new field type that enables document recipients to add their `initials` on the document. --- .../d/[token]/sign-direct-template.tsx | 10 + .../(signing)/sign/[token]/initials-field.tsx | 140 +++++ .../sign/[token]/signing-field-container.tsx | 11 +- .../sign/[token]/signing-page-view.tsx | 4 + .../document/document-read-only-fields.tsx | 1 + .../field/sign-field-with-token.ts | 15 +- .../create-document-from-direct-template.ts | 1 + packages/lib/types/document-audit-logs.ts | 4 + packages/prisma/generated/types.ts | 481 ------------------ .../migration.sql | 2 + packages/prisma/schema.prisma | 1 + .../trpc/server/document-router/router.ts | 2 +- .../primitives/document-flow/add-fields.tsx | 29 +- .../primitives/document-flow/field-icon.tsx | 21 +- packages/ui/primitives/document-flow/types.ts | 1 + .../template-flow/add-template-fields.tsx | 32 +- 16 files changed, 261 insertions(+), 494 deletions(-) create mode 100644 apps/web/src/app/(signing)/sign/[token]/initials-field.tsx delete mode 100644 packages/prisma/generated/types.ts create mode 100644 packages/prisma/migrations/20240812065352_add_initials_field_type/migration.sql diff --git a/apps/web/src/app/(recipient)/d/[token]/sign-direct-template.tsx b/apps/web/src/app/(recipient)/d/[token]/sign-direct-template.tsx index 47719014b..14922a326 100644 --- a/apps/web/src/app/(recipient)/d/[token]/sign-direct-template.tsx +++ b/apps/web/src/app/(recipient)/d/[token]/sign-direct-template.tsx @@ -41,6 +41,7 @@ import { CheckboxField } from '~/app/(signing)/sign/[token]/checkbox-field'; import { DateField } from '~/app/(signing)/sign/[token]/date-field'; import { DropdownField } from '~/app/(signing)/sign/[token]/dropdown-field'; import { EmailField } from '~/app/(signing)/sign/[token]/email-field'; +import { InitialsField } from '~/app/(signing)/sign/[token]/initials-field'; import { NameField } from '~/app/(signing)/sign/[token]/name-field'; import { NumberField } from '~/app/(signing)/sign/[token]/number-field'; import { useRequiredSigningContext } from '~/app/(signing)/sign/[token]/provider'; @@ -182,6 +183,15 @@ export const SignDirectTemplateForm = ({ onUnsignField={onUnsignField} /> )) + .with(FieldType.INITIALS, () => ( + + )) .with(FieldType.NAME, () => ( Promise | void; + onUnsignField?: (value: TRemovedSignedFieldWithTokenMutationSchema) => Promise | void; +}; + +export const InitialsField = ({ + field, + recipient, + onSignField, + onUnsignField, +}: InitialsFieldProps) => { + const router = useRouter(); + const { toast } = useToast(); + + const { fullName } = useRequiredSigningContext(); + const initials = extractInitials(fullName); + + const [isPending, startTransition] = useTransition(); + + const { mutateAsync: signFieldWithToken, isLoading: isSignFieldWithTokenLoading } = + trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION); + + const { + mutateAsync: removeSignedFieldWithToken, + isLoading: isRemoveSignedFieldWithTokenLoading, + } = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION); + + const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending; + + const onSign = async (authOptions?: TRecipientActionAuth) => { + try { + const value = initials ?? ''; + + const payload: TSignFieldWithTokenMutationSchema = { + token: recipient.token, + fieldId: field.id, + value, + isBase64: false, + authOptions, + }; + + if (onSignField) { + await onSignField(payload); + return; + } + + await signFieldWithToken(payload); + + startTransition(() => router.refresh()); + } catch (err) { + const error = AppError.parseError(err); + + if (error.code === AppErrorCode.UNAUTHORIZED) { + throw error; + } + + console.error(err); + + toast({ + title: 'Error', + description: 'An error occurred while signing the document.', + variant: 'destructive', + }); + } + }; + + const onRemove = async () => { + try { + const payload: TRemovedSignedFieldWithTokenMutationSchema = { + token: recipient.token, + fieldId: field.id, + }; + + if (onUnsignField) { + await onUnsignField(payload); + return; + } + + await removeSignedFieldWithToken(payload); + + startTransition(() => router.refresh()); + } catch (err) { + console.error(err); + + toast({ + title: 'Error', + description: 'An error occurred while removing the signature.', + variant: 'destructive', + }); + } + }; + + return ( + + {isLoading && ( +
+ +
+ )} + + {!field.inserted && ( +

+ Initials +

+ )} + + {field.inserted && ( +

+ {field.customText} +

+ )} +
+ ); +}; diff --git a/apps/web/src/app/(signing)/sign/[token]/signing-field-container.tsx b/apps/web/src/app/(signing)/sign/[token]/signing-field-container.tsx index c73e35306..103c5f9e5 100644 --- a/apps/web/src/app/(signing)/sign/[token]/signing-field-container.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/signing-field-container.tsx @@ -39,7 +39,16 @@ export type SignatureFieldProps = { */ onSign?: (documentAuthValue?: TRecipientActionAuth) => Promise | void; onRemove?: (fieldType?: string) => Promise | void; - type?: 'Date' | 'Email' | 'Name' | 'Signature' | 'Radio' | 'Dropdown' | 'Number' | 'Checkbox'; + type?: + | 'Date' + | 'Initials' + | 'Email' + | 'Name' + | 'Signature' + | 'Radio' + | 'Dropdown' + | 'Number' + | 'Checkbox'; tooltipText?: string | null; }; diff --git a/apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx b/apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx index d1382a278..ced07ef5b 100644 --- a/apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx @@ -26,6 +26,7 @@ import { DateField } from './date-field'; import { DropdownField } from './dropdown-field'; import { EmailField } from './email-field'; import { SigningForm } from './form'; +import { InitialsField } from './initials-field'; import { NameField } from './name-field'; import { NumberField } from './number-field'; import { RadioField } from './radio-field'; @@ -101,6 +102,9 @@ export const SigningPageView = ({ .with(FieldType.SIGNATURE, () => ( )) + .with(FieldType.INITIALS, () => ( + + )) .with(FieldType.NAME, () => ( )) diff --git a/apps/web/src/components/document/document-read-only-fields.tsx b/apps/web/src/components/document/document-read-only-fields.tsx index d07c0b26c..cbf566b25 100644 --- a/apps/web/src/components/document/document-read-only-fields.tsx +++ b/apps/web/src/components/document/document-read-only-fields.tsx @@ -98,6 +98,7 @@ export const DocumentReadOnlyFields = ({ documentMeta, fields }: DocumentReadOnl { type: P.union( FieldType.NAME, + FieldType.INITIALS, FieldType.EMAIL, FieldType.NUMBER, FieldType.RADIO, diff --git a/packages/lib/server-only/field/sign-field-with-token.ts b/packages/lib/server-only/field/sign-field-with-token.ts index aa596dc37..087de646b 100644 --- a/packages/lib/server-only/field/sign-field-with-token.ts +++ b/packages/lib/server-only/field/sign-field-with-token.ts @@ -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, diff --git a/packages/lib/server-only/template/create-document-from-direct-template.ts b/packages/lib/server-only/template/create-document-from-direct-template.ts index 8ec4e9f1c..5827fb76c 100644 --- a/packages/lib/server-only/template/create-document-from-direct-template.ts +++ b/packages/lib/server-only/template/create-document-from-direct-template.ts @@ -468,6 +468,7 @@ export const createDocumentFromDirectTemplate = async ({ .with( FieldType.DATE, FieldType.EMAIL, + FieldType.INITIALS, FieldType.NAME, FieldType.TEXT, FieldType.NUMBER, diff --git a/packages/lib/types/document-audit-logs.ts b/packages/lib/types/document-audit-logs.ts index 1d2bf7c53..5c00724cc 100644 --- a/packages/lib/types/document-audit-logs.ts +++ b/packages/lib/types/document-audit-logs.ts @@ -233,6 +233,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(), diff --git a/packages/prisma/generated/types.ts b/packages/prisma/generated/types.ts deleted file mode 100644 index 21144bd84..000000000 --- a/packages/prisma/generated/types.ts +++ /dev/null @@ -1,481 +0,0 @@ -import type { ColumnType } from 'kysely'; - -export type Generated = T extends ColumnType - ? ColumnType - : ColumnType; -export type Timestamp = ColumnType; - -export const IdentityProvider = { - DOCUMENSO: 'DOCUMENSO', - GOOGLE: 'GOOGLE', - OIDC: 'OIDC', -} as const; -export type IdentityProvider = (typeof IdentityProvider)[keyof typeof IdentityProvider]; -export const Role = { - ADMIN: 'ADMIN', - USER: 'USER', -} as const; -export type Role = (typeof Role)[keyof typeof Role]; -export const UserSecurityAuditLogType = { - ACCOUNT_PROFILE_UPDATE: 'ACCOUNT_PROFILE_UPDATE', - ACCOUNT_SSO_LINK: 'ACCOUNT_SSO_LINK', - AUTH_2FA_DISABLE: 'AUTH_2FA_DISABLE', - AUTH_2FA_ENABLE: 'AUTH_2FA_ENABLE', - PASSKEY_CREATED: 'PASSKEY_CREATED', - PASSKEY_DELETED: 'PASSKEY_DELETED', - PASSKEY_UPDATED: 'PASSKEY_UPDATED', - PASSWORD_RESET: 'PASSWORD_RESET', - PASSWORD_UPDATE: 'PASSWORD_UPDATE', - SIGN_OUT: 'SIGN_OUT', - SIGN_IN: 'SIGN_IN', - SIGN_IN_FAIL: 'SIGN_IN_FAIL', - SIGN_IN_2FA_FAIL: 'SIGN_IN_2FA_FAIL', - SIGN_IN_PASSKEY_FAIL: 'SIGN_IN_PASSKEY_FAIL', -} as const; -export type UserSecurityAuditLogType = - (typeof UserSecurityAuditLogType)[keyof typeof UserSecurityAuditLogType]; -export const WebhookTriggerEvents = { - DOCUMENT_CREATED: 'DOCUMENT_CREATED', - DOCUMENT_SENT: 'DOCUMENT_SENT', - DOCUMENT_OPENED: 'DOCUMENT_OPENED', - DOCUMENT_SIGNED: 'DOCUMENT_SIGNED', - DOCUMENT_COMPLETED: 'DOCUMENT_COMPLETED', -} as const; -export type WebhookTriggerEvents = (typeof WebhookTriggerEvents)[keyof typeof WebhookTriggerEvents]; -export const WebhookCallStatus = { - SUCCESS: 'SUCCESS', - FAILED: 'FAILED', -} as const; -export type WebhookCallStatus = (typeof WebhookCallStatus)[keyof typeof WebhookCallStatus]; -export const ApiTokenAlgorithm = { - SHA512: 'SHA512', -} as const; -export type ApiTokenAlgorithm = (typeof ApiTokenAlgorithm)[keyof typeof ApiTokenAlgorithm]; -export const SubscriptionStatus = { - ACTIVE: 'ACTIVE', - PAST_DUE: 'PAST_DUE', - INACTIVE: 'INACTIVE', -} as const; -export type SubscriptionStatus = (typeof SubscriptionStatus)[keyof typeof SubscriptionStatus]; -export const DocumentStatus = { - DRAFT: 'DRAFT', - PENDING: 'PENDING', - COMPLETED: 'COMPLETED', -} as const; -export type DocumentStatus = (typeof DocumentStatus)[keyof typeof DocumentStatus]; -export const DocumentSource = { - DOCUMENT: 'DOCUMENT', - TEMPLATE: 'TEMPLATE', - TEMPLATE_DIRECT_LINK: 'TEMPLATE_DIRECT_LINK', -} as const; -export type DocumentSource = (typeof DocumentSource)[keyof typeof DocumentSource]; -export const DocumentDataType = { - S3_PATH: 'S3_PATH', - BYTES: 'BYTES', - BYTES_64: 'BYTES_64', -} as const; -export type DocumentDataType = (typeof DocumentDataType)[keyof typeof DocumentDataType]; -export const ReadStatus = { - NOT_OPENED: 'NOT_OPENED', - OPENED: 'OPENED', -} as const; -export type ReadStatus = (typeof ReadStatus)[keyof typeof ReadStatus]; -export const SendStatus = { - NOT_SENT: 'NOT_SENT', - SENT: 'SENT', -} as const; -export type SendStatus = (typeof SendStatus)[keyof typeof SendStatus]; -export const SigningStatus = { - NOT_SIGNED: 'NOT_SIGNED', - SIGNED: 'SIGNED', -} as const; -export type SigningStatus = (typeof SigningStatus)[keyof typeof SigningStatus]; -export const RecipientRole = { - CC: 'CC', - SIGNER: 'SIGNER', - VIEWER: 'VIEWER', - APPROVER: 'APPROVER', -} as const; -export type RecipientRole = (typeof RecipientRole)[keyof typeof RecipientRole]; -export const FieldType = { - SIGNATURE: 'SIGNATURE', - FREE_SIGNATURE: 'FREE_SIGNATURE', - NAME: 'NAME', - EMAIL: 'EMAIL', - DATE: 'DATE', - TEXT: 'TEXT', - NUMBER: 'NUMBER', - RADIO: 'RADIO', - CHECKBOX: 'CHECKBOX', - DROPDOWN: 'DROPDOWN', -} as const; -export type FieldType = (typeof FieldType)[keyof typeof FieldType]; -export const TeamMemberRole = { - ADMIN: 'ADMIN', - MANAGER: 'MANAGER', - MEMBER: 'MEMBER', -} as const; -export type TeamMemberRole = (typeof TeamMemberRole)[keyof typeof TeamMemberRole]; -export const TeamMemberInviteStatus = { - ACCEPTED: 'ACCEPTED', - PENDING: 'PENDING', -} as const; -export type TeamMemberInviteStatus = - (typeof TeamMemberInviteStatus)[keyof typeof TeamMemberInviteStatus]; -export const TemplateType = { - PUBLIC: 'PUBLIC', - PRIVATE: 'PRIVATE', -} as const; -export type TemplateType = (typeof TemplateType)[keyof typeof TemplateType]; -export type Account = { - id: string; - userId: number; - type: string; - provider: string; - providerAccountId: string; - refresh_token: string | null; - access_token: string | null; - expires_at: number | null; - created_at: number | null; - ext_expires_in: number | null; - token_type: string | null; - scope: string | null; - id_token: string | null; - session_state: string | null; -}; -export type AnonymousVerificationToken = { - id: string; - token: string; - expiresAt: Timestamp; - createdAt: Generated; -}; -export type ApiToken = { - id: Generated; - name: string; - token: string; - algorithm: Generated; - expires: Timestamp | null; - createdAt: Generated; - userId: number | null; - teamId: number | null; -}; -export type Document = { - id: Generated; - userId: number; - authOptions: unknown | null; - formValues: unknown | null; - title: string; - status: Generated; - documentDataId: string; - createdAt: Generated; - updatedAt: Generated; - completedAt: Timestamp | null; - deletedAt: Timestamp | null; - teamId: number | null; - templateId: number | null; - source: DocumentSource; -}; -export type DocumentAuditLog = { - id: string; - documentId: number; - createdAt: Generated; - type: string; - data: unknown; - name: string | null; - email: string | null; - userId: number | null; - userAgent: string | null; - ipAddress: string | null; -}; -export type DocumentData = { - id: string; - type: DocumentDataType; - data: string; - initialData: string; -}; -export type DocumentMeta = { - id: string; - subject: string | null; - message: string | null; - timezone: Generated; - password: string | null; - dateFormat: Generated; - documentId: number; - redirectUrl: string | null; -}; -export type DocumentShareLink = { - id: Generated; - email: string; - slug: string; - documentId: number; - createdAt: Generated; - updatedAt: Timestamp; -}; -export type Field = { - id: Generated; - secondaryId: string; - documentId: number | null; - templateId: number | null; - recipientId: number; - type: FieldType; - page: number; - positionX: Generated; - positionY: Generated; - width: Generated; - height: Generated; - customText: string; - inserted: boolean; - fieldMeta: unknown | null; -}; -export type Passkey = { - id: string; - userId: number; - name: string; - createdAt: Generated; - updatedAt: Generated; - lastUsedAt: Timestamp | null; - credentialId: Buffer; - credentialPublicKey: Buffer; - counter: string; - credentialDeviceType: string; - credentialBackedUp: boolean; - transports: string[]; -}; -export type PasswordResetToken = { - id: Generated; - token: string; - createdAt: Generated; - expiry: Timestamp; - userId: number; -}; -export type Recipient = { - id: Generated; - documentId: number | null; - templateId: number | null; - email: string; - name: Generated; - token: string; - documentDeletedAt: Timestamp | null; - expired: Timestamp | null; - signedAt: Timestamp | null; - authOptions: unknown | null; - role: Generated; - readStatus: Generated; - signingStatus: Generated; - sendStatus: Generated; -}; -export type Session = { - id: string; - sessionToken: string; - userId: number; - expires: Timestamp; -}; -export type Signature = { - id: Generated; - created: Generated; - recipientId: number; - fieldId: number; - signatureImageAsBase64: string | null; - typedSignature: string | null; -}; -export type SiteSettings = { - id: string; - enabled: Generated; - data: unknown; - lastModifiedByUserId: number | null; - lastModifiedAt: Generated; -}; -export type Subscription = { - id: Generated; - status: Generated; - planId: string; - priceId: string; - periodEnd: Timestamp | null; - userId: number | null; - teamId: number | null; - createdAt: Generated; - updatedAt: Timestamp; - cancelAtPeriodEnd: Generated; -}; -export type Team = { - id: Generated; - name: string; - url: string; - createdAt: Generated; - customerId: string | null; - ownerUserId: number; -}; -export type TeamEmail = { - teamId: number; - createdAt: Generated; - name: string; - email: string; -}; -export type TeamEmailVerification = { - teamId: number; - name: string; - email: string; - token: string; - expiresAt: Timestamp; - createdAt: Generated; -}; -export type TeamMember = { - id: Generated; - teamId: number; - createdAt: Generated; - role: TeamMemberRole; - userId: number; -}; -export type TeamMemberInvite = { - id: Generated; - teamId: number; - createdAt: Generated; - email: string; - status: Generated; - role: TeamMemberRole; - token: string; -}; -export type TeamPending = { - id: Generated; - name: string; - url: string; - createdAt: Generated; - customerId: string; - ownerUserId: number; -}; -export type TeamTransferVerification = { - teamId: number; - userId: number; - name: string; - email: string; - token: string; - expiresAt: Timestamp; - createdAt: Generated; - clearPaymentMethods: Generated; -}; -export type Template = { - id: Generated; - type: Generated; - title: string; - userId: number; - teamId: number | null; - authOptions: unknown | null; - templateDocumentDataId: string; - createdAt: Generated; - updatedAt: Generated; -}; -export type TemplateDirectLink = { - id: string; - templateId: number; - token: string; - createdAt: Generated; - enabled: boolean; - directTemplateRecipientId: number; -}; -export type TemplateMeta = { - id: string; - subject: string | null; - message: string | null; - timezone: Generated; - password: string | null; - dateFormat: Generated; - templateId: number; - redirectUrl: string | null; -}; -export type User = { - id: Generated; - name: string | null; - customerId: string | null; - email: string; - emailVerified: Timestamp | null; - password: string | null; - source: string | null; - signature: string | null; - createdAt: Generated; - updatedAt: Generated; - lastSignedIn: Generated; - roles: Generated; - identityProvider: Generated; - twoFactorSecret: string | null; - twoFactorEnabled: Generated; - twoFactorBackupCodes: string | null; - url: string | null; -}; -export type UserProfile = { - id: number; - bio: string | null; -}; -export type UserSecurityAuditLog = { - id: Generated; - userId: number; - createdAt: Generated; - type: UserSecurityAuditLogType; - userAgent: string | null; - ipAddress: string | null; -}; -export type VerificationToken = { - id: Generated; - secondaryId: string; - identifier: string; - token: string; - expires: Timestamp; - createdAt: Generated; - userId: number; -}; -export type Webhook = { - id: string; - webhookUrl: string; - eventTriggers: WebhookTriggerEvents[]; - secret: string | null; - enabled: Generated; - createdAt: Generated; - updatedAt: Generated; - userId: number; - teamId: number | null; -}; -export type WebhookCall = { - id: string; - status: WebhookCallStatus; - url: string; - event: WebhookTriggerEvents; - requestBody: unknown; - responseCode: number; - responseHeaders: unknown | null; - responseBody: unknown | null; - createdAt: Generated; - webhookId: string; -}; -export type DB = { - Account: Account; - AnonymousVerificationToken: AnonymousVerificationToken; - ApiToken: ApiToken; - Document: Document; - DocumentAuditLog: DocumentAuditLog; - DocumentData: DocumentData; - DocumentMeta: DocumentMeta; - DocumentShareLink: DocumentShareLink; - Field: Field; - Passkey: Passkey; - PasswordResetToken: PasswordResetToken; - Recipient: Recipient; - Session: Session; - Signature: Signature; - SiteSettings: SiteSettings; - Subscription: Subscription; - Team: Team; - TeamEmail: TeamEmail; - TeamEmailVerification: TeamEmailVerification; - TeamMember: TeamMember; - TeamMemberInvite: TeamMemberInvite; - TeamPending: TeamPending; - TeamTransferVerification: TeamTransferVerification; - Template: Template; - TemplateDirectLink: TemplateDirectLink; - TemplateMeta: TemplateMeta; - User: User; - UserProfile: UserProfile; - UserSecurityAuditLog: UserSecurityAuditLog; - VerificationToken: VerificationToken; - Webhook: Webhook; - WebhookCall: WebhookCall; -}; diff --git a/packages/prisma/migrations/20240812065352_add_initials_field_type/migration.sql b/packages/prisma/migrations/20240812065352_add_initials_field_type/migration.sql new file mode 100644 index 000000000..b4c827ae7 --- /dev/null +++ b/packages/prisma/migrations/20240812065352_add_initials_field_type/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "FieldType" ADD VALUE 'INITIALS'; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 44cf9e157..9d8860f44 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -409,6 +409,7 @@ model Recipient { enum FieldType { SIGNATURE FREE_SIGNATURE + INITIALS NAME EMAIL DATE diff --git a/packages/trpc/server/document-router/router.ts b/packages/trpc/server/document-router/router.ts index 85c87e3b6..049035f89 100644 --- a/packages/trpc/server/document-router/router.ts +++ b/packages/trpc/server/document-router/router.ts @@ -414,7 +414,7 @@ export const documentRouter = router({ teamId, }).catch(() => null); - if (!document || document.teamId !== teamId) { + if (!document || (teamId && document.teamId !== teamId)) { throw new TRPCError({ code: 'FORBIDDEN', message: 'You do not have access to this document.', diff --git a/packages/ui/primitives/document-flow/add-fields.tsx b/packages/ui/primitives/document-flow/add-fields.tsx index 5ae55b7b3..597948fab 100644 --- a/packages/ui/primitives/document-flow/add-fields.tsx +++ b/packages/ui/primitives/document-flow/add-fields.tsx @@ -10,6 +10,7 @@ import { CheckSquare, ChevronDown, ChevronsUpDown, + Contact, Disc, Hash, Info, @@ -650,6 +651,32 @@ export const AddFieldsFormPartial = ({ + + + + )} + + + From 7a1341eb7448fce0d8bd41a3330c9880af09de37 Mon Sep 17 00:00:00 2001 From: Rene Steen Date: Tue, 20 Aug 2024 05:58:56 +0200 Subject: [PATCH 24/28] feat: automatically set public profile url for OIDC users (#1225) Adds a hook to automatically set profile urls for OIDC users --- apps/web/src/pages/api/auth/[...nextauth].ts | 19 +++++++++++++++++++ packages/lib/utils/slugify.ts | 3 +++ 2 files changed, 22 insertions(+) create mode 100644 packages/lib/utils/slugify.ts diff --git a/apps/web/src/pages/api/auth/[...nextauth].ts b/apps/web/src/pages/api/auth/[...nextauth].ts index 44c509c62..811ddbda0 100644 --- a/apps/web/src/pages/api/auth/[...nextauth].ts +++ b/apps/web/src/pages/api/auth/[...nextauth].ts @@ -6,6 +6,7 @@ import { getStripeCustomerByUser } from '@documenso/ee/server-only/stripe/get-cu import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; import { NEXT_AUTH_OPTIONS } from '@documenso/lib/next-auth/auth-options'; import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata'; +import { slugify } from '@documenso/lib/utils/slugify'; import { prisma } from '@documenso/prisma'; import { UserSecurityAuditLogType } from '@documenso/prisma/client'; @@ -77,6 +78,24 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) { }); } + // auto set public profile name + if (account.provider === 'oidc' && user.name && 'url' in user && !user.url) { + let counter = 1; + let url = slugify(user.name); + + while (await prisma.user.findFirst({ where: { url } })) { + url = `${slugify(user.name)}-${counter}`; + counter++; + } + + await prisma.user.update({ + where: { id: userId }, + data: { + url, + }, + }); + } + await prisma.userSecurityAuditLog.create({ data: { userId, diff --git a/packages/lib/utils/slugify.ts b/packages/lib/utils/slugify.ts new file mode 100644 index 000000000..e8f03f3ce --- /dev/null +++ b/packages/lib/utils/slugify.ts @@ -0,0 +1,3 @@ +export * from '@sindresorhus/slugify'; + +export { default as slugify } from '@sindresorhus/slugify'; From 2c9136498c4c85d4bf97ade3d3b0c8115ad1c19b Mon Sep 17 00:00:00 2001 From: Ephraim Duncan <55143799+ephraimduncan@users.noreply.github.com> Date: Tue, 20 Aug 2024 05:41:19 +0000 Subject: [PATCH 25/28] feat: update team email templates. (#1229) Updates the email templates to include team information when sent from a team context. --- .../(dashboard)/layout/menu-switcher.tsx | 1 + .../template-document-invite.tsx | 10 +++ .../email/templates/confirm-team-email.tsx | 3 + packages/email/templates/document-invite.tsx | 9 +++ .../definitions/emails/send-signing-email.ts | 19 +++++- .../server-only/document/resend-document.tsx | 40 +++++++----- packages/prisma/seed/initial-seed.ts | 62 ++++++++++++++++++- 7 files changed, 127 insertions(+), 17 deletions(-) diff --git a/apps/web/src/components/(dashboard)/layout/menu-switcher.tsx b/apps/web/src/components/(dashboard)/layout/menu-switcher.tsx index 760b9cad2..a14a3074a 100644 --- a/apps/web/src/components/(dashboard)/layout/menu-switcher.tsx +++ b/apps/web/src/components/(dashboard)/layout/menu-switcher.tsx @@ -196,6 +196,7 @@ export const MenuSwitcher = ({ user, teams: initialTeamsData }: MenuSwitcherProp } avatarFallback={formatAvatarFallback(team.name)} primaryText={team.name} + textSectionClassName="w-[200px]" secondaryText={
{ const { actionVerb, progressiveVerb } = RECIPIENT_ROLES_DESCRIPTION[role]; @@ -36,6 +40,12 @@ export const TemplateDocumentInvite = ({
{`"${documentName}"`} + ) : isTeamInvite ? ( + <> + {`${inviterName} on behalf of ${teamName} has invited you to ${actionVerb.toLowerCase()}`} +
+ {`"${documentName}"`} + ) : ( <> {`${inviterName} has invited you to ${actionVerb.toLowerCase()}`} diff --git a/packages/email/templates/confirm-team-email.tsx b/packages/email/templates/confirm-team-email.tsx index 5752f806d..552a079f8 100644 --- a/packages/email/templates/confirm-team-email.tsx +++ b/packages/email/templates/confirm-team-email.tsx @@ -91,6 +91,9 @@ export const ConfirmTeamEmailTemplate = ({
  • Allow document recipients to reply directly to this email address
  • +
  • + Send documents on behalf of the team using the email address +
  • diff --git a/packages/email/templates/document-invite.tsx b/packages/email/templates/document-invite.tsx index 52a40d804..7e845126b 100644 --- a/packages/email/templates/document-invite.tsx +++ b/packages/email/templates/document-invite.tsx @@ -23,6 +23,9 @@ export type DocumentInviteEmailTemplateProps = Partial { const action = RECIPIENT_ROLES_DESCRIPTION[role].actionVerb.toLowerCase(); const previewText = selfSigner ? `Please ${action} your document ${documentName}` + : isTeamInvite + ? `${inviterName} on behalf of ${teamName} has invited you to ${action} ${documentName}` : `${inviterName} has invited you to ${action} ${documentName}`; const getAssetUrl = (path: string) => { @@ -76,6 +83,8 @@ export const DocumentInviteEmailTemplate = ({ assetBaseUrl={assetBaseUrl} role={role} selfSigner={selfSigner} + isTeamInvite={isTeamInvite} + teamName={teamName} /> diff --git a/packages/lib/jobs/definitions/emails/send-signing-email.ts b/packages/lib/jobs/definitions/emails/send-signing-email.ts index 0244df34f..43ad730c6 100644 --- a/packages/lib/jobs/definitions/emails/send-signing-email.ts +++ b/packages/lib/jobs/definitions/emails/send-signing-email.ts @@ -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 () => { diff --git a/packages/lib/server-only/document/resend-document.tsx b/packages/lib/server-only/document/resend-document.tsx index c8a51cac4..8ea39445c 100644 --- a/packages/lib/server-only/document/resend-document.tsx +++ b/packages/lib/server-only/document/resend-document.tsx @@ -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({ diff --git a/packages/prisma/seed/initial-seed.ts b/packages/prisma/seed/initial-seed.ts index 66c944c9b..38b340a79 100644 --- a/packages/prisma/seed/initial-seed.ts +++ b/packages/prisma/seed/initial-seed.ts @@ -4,7 +4,7 @@ import path from 'node:path'; import { hashSync } from '@documenso/lib/server-only/auth/hash'; import { prisma } from '..'; -import { DocumentDataType, DocumentSource, Role } from '../client'; +import { DocumentDataType, DocumentSource, Role, TeamMemberRole } from '../client'; export const seedDatabase = async () => { const examplePdf = fs @@ -67,4 +67,64 @@ export const seedDatabase = async () => { }, }, }); + + const testUsers = [ + 'test@documenso.com', + 'test2@documenso.com', + 'test3@documenso.com', + 'test4@documenso.com', + ]; + + const createdUsers = []; + + for (const email of testUsers) { + const testUser = await prisma.user.upsert({ + where: { + email: email, + }, + create: { + name: 'Test User', + email: email, + emailVerified: new Date(), + password: hashSync('password'), + roles: [Role.USER], + }, + update: {}, + }); + + createdUsers.push(testUser); + } + + const team1 = await prisma.team.create({ + data: { + name: 'Team 1', + url: 'team1', + ownerUserId: createdUsers[0].id, + }, + }); + + const team2 = await prisma.team.create({ + data: { + name: 'Team 2', + url: 'team2', + ownerUserId: createdUsers[1].id, + }, + }); + + for (const team of [team1, team2]) { + await prisma.teamMember.createMany({ + data: [ + { + teamId: team.id, + userId: createdUsers[1].id, + role: TeamMemberRole.ADMIN, + }, + { + teamId: team.id, + userId: createdUsers[2].id, + role: TeamMemberRole.MEMBER, + }, + ], + }); + } }; From 9178dbd3c1d9826a8b01f0d8397c4c46c207a649 Mon Sep 17 00:00:00 2001 From: Mythie Date: Tue, 20 Aug 2024 23:23:36 +1000 Subject: [PATCH 26/28] chore: update marketing site --- .../src/app/(marketing)/oss-friends/page.tsx | 4 + apps/marketing/src/app/not-found.tsx | 4 + .../src/components/(marketing)/carousel.tsx | 22 +++++- .../faster-smarter-beautiful-bento.tsx | 4 + .../src/components/(marketing)/hero.tsx | 4 + .../(marketing)/open-build-template-bento.tsx | 4 + .../share-connect-paid-widget-bento.tsx | 6 +- apps/web/src/app/(unauthenticated)/layout.tsx | 4 + .../web/src/components/partials/not-found.tsx | 4 + packages/lib/translations/de/marketing.js | 2 +- packages/lib/translations/de/marketing.po | 76 ++++++++++--------- packages/lib/translations/en/marketing.js | 2 +- packages/lib/translations/en/marketing.po | 76 ++++++++++--------- 13 files changed, 136 insertions(+), 76 deletions(-) diff --git a/apps/marketing/src/app/(marketing)/oss-friends/page.tsx b/apps/marketing/src/app/(marketing)/oss-friends/page.tsx index 65a4a55f8..78b051eea 100644 --- a/apps/marketing/src/app/(marketing)/oss-friends/page.tsx +++ b/apps/marketing/src/app/(marketing)/oss-friends/page.tsx @@ -44,6 +44,10 @@ export default async function OSSFriendsPage() { src={backgroundPattern} alt="background pattern" className="-mr-[15vw] -mt-[15vh] h-full max-h-[150vh] scale-125 object-cover dark:contrast-[70%] dark:invert dark:sepia md:-mr-[50vw] md:scale-150 lg:scale-[175%]" + style={{ + mask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 67%)', + WebkitMask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 67%)', + }} />
    diff --git a/apps/marketing/src/app/not-found.tsx b/apps/marketing/src/app/not-found.tsx index d85cdb62f..044f7f6c7 100644 --- a/apps/marketing/src/app/not-found.tsx +++ b/apps/marketing/src/app/not-found.tsx @@ -26,6 +26,10 @@ export default function NotFound() { src={backgroundPattern} alt="background pattern" className="-mr-[50vw] -mt-[15vh] h-full scale-100 object-cover dark:contrast-[70%] dark:invert dark:sepia md:scale-100 lg:scale-[100%]" + style={{ + mask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 67%)', + WebkitMask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 67%)', + }} priority /> diff --git a/apps/marketing/src/components/(marketing)/carousel.tsx b/apps/marketing/src/components/(marketing)/carousel.tsx index 688f79b3a..07b089d33 100644 --- a/apps/marketing/src/components/(marketing)/carousel.tsx +++ b/apps/marketing/src/components/(marketing)/carousel.tsx @@ -2,11 +2,14 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; +import Link from 'next/link'; + import { Trans, msg } from '@lingui/macro'; import { useLingui } from '@lingui/react'; import type { AutoplayType } from 'embla-carousel-autoplay'; import Autoplay from 'embla-carousel-autoplay'; import useEmblaCarousel from 'embla-carousel-react'; +import { usePlausible } from 'next-plausible'; import { useTheme } from 'next-themes'; import { Card } from '@documenso/ui/primitives/card'; @@ -61,6 +64,7 @@ const SLIDES = [ export const Carousel = () => { const { _ } = useLingui(); + const event = usePlausible(); const slides = SLIDES; const [_isPlaying, setIsPlaying] = useState(false); @@ -238,7 +242,10 @@ export const Carousel = () => { if (!mounted) return null; return ( <> - +
    {slides.map((slide, index) => ( @@ -269,6 +276,19 @@ export const Carousel = () => {
    + + event('view-demo')} + > + Book a Demo + + Want to learn more about Documenso and how it works? Book a demo today! Our founders + will walk you through the application and answer any questions you may have regarding + usage, integration, and more. + +
    diff --git a/apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx b/apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx index 998f9ac75..6578a61ed 100644 --- a/apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx +++ b/apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx @@ -24,6 +24,10 @@ export const FasterSmarterBeautifulBento = ({ src={backgroundPattern} alt="background pattern" className="h-full scale-125 object-cover dark:contrast-[70%] dark:invert dark:sepia md:scale-150 lg:scale-[175%]" + style={{ + mask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 67%)', + WebkitMask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 67%)', + }} />

    diff --git a/apps/marketing/src/components/(marketing)/hero.tsx b/apps/marketing/src/components/(marketing)/hero.tsx index bc5818399..11f6ac790 100644 --- a/apps/marketing/src/components/(marketing)/hero.tsx +++ b/apps/marketing/src/components/(marketing)/hero.tsx @@ -86,6 +86,10 @@ export const Hero = ({ className, ...props }: HeroProps) => { src={backgroundPattern} alt="background pattern" className="-mr-[50vw] -mt-[15vh] h-full scale-125 object-cover dark:contrast-[70%] dark:invert dark:sepia md:scale-150 lg:scale-[175%]" + style={{ + mask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 80%)', + WebkitMask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 80%)', + }} />

    diff --git a/apps/marketing/src/components/(marketing)/open-build-template-bento.tsx b/apps/marketing/src/components/(marketing)/open-build-template-bento.tsx index a0708716a..a0b99c0f0 100644 --- a/apps/marketing/src/components/(marketing)/open-build-template-bento.tsx +++ b/apps/marketing/src/components/(marketing)/open-build-template-bento.tsx @@ -21,6 +21,10 @@ export const OpenBuildTemplateBento = ({ className, ...props }: OpenBuildTemplat src={backgroundPattern} alt="background pattern" className="h-full scale-125 object-cover dark:contrast-[70%] dark:invert dark:sepia md:scale-150 lg:scale-[175%]" + style={{ + mask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 80%)', + WebkitMask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 80%)', + }} />

    diff --git a/apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx b/apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx index 418e2f712..b7b07c62c 100644 --- a/apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx +++ b/apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx @@ -25,6 +25,10 @@ export const ShareConnectPaidWidgetBento = ({ src={backgroundPattern} alt="background pattern" className="h-full scale-125 object-cover dark:contrast-[70%] dark:invert dark:sepia md:scale-150 lg:scale-[175%]" + style={{ + mask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 80%)', + WebkitMask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 80%)', + }} />

    @@ -39,7 +43,7 @@ export const ShareConnectPaidWidgetBento = ({

    - Easy Sharing (Soon). + Easy Sharing. Receive your personal link to share with everyone you care about.

    diff --git a/apps/web/src/app/(unauthenticated)/layout.tsx b/apps/web/src/app/(unauthenticated)/layout.tsx index 05055d508..2016b2adb 100644 --- a/apps/web/src/app/(unauthenticated)/layout.tsx +++ b/apps/web/src/app/(unauthenticated)/layout.tsx @@ -17,6 +17,10 @@ export default function UnauthenticatedLayout({ children }: UnauthenticatedLayou src={backgroundPattern} alt="background pattern" className="dark:brightness-95 dark:contrast-[70%] dark:invert dark:sepia" + style={{ + mask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 80%)', + WebkitMask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 80%)', + }} /> diff --git a/apps/web/src/components/partials/not-found.tsx b/apps/web/src/components/partials/not-found.tsx index b80c6fea8..86f6eef56 100644 --- a/apps/web/src/components/partials/not-found.tsx +++ b/apps/web/src/components/partials/not-found.tsx @@ -29,6 +29,10 @@ export default function NotFoundPartial({ children }: NotFoundPartialProps) { src={backgroundPattern} alt="background pattern" className="-mr-[50vw] -mt-[15vh] h-full scale-100 object-cover dark:contrast-[70%] dark:invert dark:sepia md:scale-100 lg:scale-[100%]" + style={{ + mask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 80%)', + WebkitMask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 80%)', + }} priority /> diff --git a/packages/lib/translations/de/marketing.js b/packages/lib/translations/de/marketing.js index 35d08cae3..3830fbe4e 100644 --- a/packages/lib/translations/de/marketing.js +++ b/packages/lib/translations/de/marketing.js @@ -1 +1 @@ -/*eslint-disable*/module.exports={messages:JSON.parse("{\"J/hVSQ\":[[\"0\"]],\"u0zktA\":\"5 Standarddokumente pro Monat\",\"rKtmiD\":\"5 Benutzer inbegriffen\",\"vaHmll\":\"Eine 10x bessere Signaturerfahrung.\",\"gBefbz\":[\"Mehr Benutzer hinzufügen für \",[\"0\"]],\"XkF8tv\":\"Alle unsere Kennzahlen, Finanzen und Erkenntnisse sind öffentlich. Wir glauben an Transparenz und möchten unsere Reise mit Ihnen teilen. Mehr erfahren Sie hier: <0>Ankündigung Offene Kennzahlen\",\"tkQ/WI\":\"Erhobener Betrag\",\"qOMroC\":\"API-Zugriff\",\"FNv8t7\":\"Schön.\",\"W/TUoX\":\"Weil Unterschriften gefeiert werden sollten. Deshalb kümmern wir uns um jedes kleinste Detail in unserem Produkt.\",\"astDB+\":\"Blog\",\"7zGun7\":\"Aufbauen oben drauf.\",\"fxgcNV\":\"Kann ich Documenso kommerziell nutzen?\",\"V+D/YP\":\"Karrieren\",\"CWe7wB\":\"Änderungsprotokoll\",\"JZbmjL\":\"Wählen Sie eine Vorlage aus dem Community-App-Store. Oder reichen Sie Ihre eigene Vorlage ein, damit andere sie benutzen können.\",\"chL5IG\":\"Gemeinschaft\",\"p5+XQN\":\"Fertige Dokumente\",\"NApCXa\":\"Fertige Dokumente pro Monat\",\"z5kV0h\":\"Verbindungen\",\"YcfUZ9\":\"Kontaktiere uns\",\"1NJjIG\":\"Erstellen Sie Verbindungen und Automatisierungen mit Zapier und mehr, um sich mit Ihren Lieblingstools zu integrieren.\",\"rr83qK\":\"Erstellen Sie Ihr Konto und beginnen Sie mit der Nutzung modernster Dokumentensignaturen. Offene und schöne Signaturen sind zum Greifen nah.\",\"75ojt0\":\"Kunden mit einer aktiven Abonnements.\",\"pF9qTh\":\"Anpassen und erweitern.\",\"f8fH8W\":\"Design\",\"W6qD1T\":\"Entwickelt für jede Phase Ihrer Reise.\",\"K6KbY4\":\"Direktlink\",\"aLD+Td\":\"Documenso ist eine Gemeinschaftsanstrengung, um ein offenes und lebendiges Ökosystem um ein Werkzeug zu schaffen, das jeder frei nutzen und anpassen kann. Indem wir wirklich offen sind, wollen wir vertrauenswürdige Infrastruktur für die Zukunft des Internets schaffen.\",\"32yG8y\":\"Documenso auf X\",\"+1xAO7\":\"Unterschriften,<0/>endlich Open Source.\",\"TvY/XA\":\"Dokumentation\",\"tSS7hj\":\"Betten Sie Documenso ganz einfach in Ihr Produkt ein. Kopieren und fügen Sie einfach unser React-Widget in Ihre Anwendung ein.\",\"BWMGM4\":\"Einfaches Teilen (Bald).\",\"V6EY8B\":\"E-Mail- und Discord-Support\",\"C0/bri\":\"Beteiligung\",\"8Zy3YU\":\"Enterprise-Konformität, Lizenz- oder technische Bedürfnisse?\",\"ZSW8id\":\"Alles, was Sie für ein großartiges Signaturerlebnis benötigen.\",\"sXswT6\":\"Schnell.\",\"cT9Z9e\":\"Schneller, intelligenter und schöner.\",\"k/ANik\":\"Finanzen\",\"I7Exsw\":\"Folgen Sie uns auf X\",\"f3Botn\":\"Für Unternehmen, die über mehrere Teams skalieren möchten.\",\"y2DcZj\":\"Für kleine Teams und Einzelpersonen mit grundlegenden Bedürfnissen.\",\"2POOFK\":\"Kostenlos\",\"OdieZe\":\"Aus dem Blog\",\"IPgkVQ\":\"Vollzeit\",\"aSWzT9\":\"Lassen Sie sich bezahlen (Bald).\",\"ZDIydz\":\"Loslegen\",\"c3b0B0\":\"Loslegen\",\"pS8wej\":\"Fangen Sie heute an.\",\"7FPIvI\":\"Erhalten Sie die neuesten Nachrichten von Documenso, einschließlich Produkt-Updates, Team-Ankündigungen und mehr!\",\"kV0qBq\":\"GitHub: Gesamte PRs zusammengeführt\",\"652R6j\":\"GitHub: Gesamte offene Issues\",\"R1aJ0W\":\"GitHub: Gesamtanzahl Sterne\",\"P1ovAc\":\"Globale Gehaltsbänder\",\"IAq/yr\":\"Wachstum\",\"Xi7f+z\":\"Wie kann ich beitragen?\",\"9VGuMA\":\"Wie gehen Sie mit meinen Daten um?\",\"fByw/g\":\"Einzelperson\",\"Csm+TN\":\"Integrierte Zahlungen mit Stripe, sodass Sie sich keine Sorgen ums Bezahlen machen müssen.\",\"phSPy7\":\"Integriert sich mit all Ihren Lieblingstools.\",\"pfjrI2\":\"Gibt es mehr?\",\"LOyqaC\":\"Es liegt an Ihnen. Entweder klonen Sie unser Repository oder nutzen unsere einfach zu bedienende Hosting-Lösung.\",\"PCgMVa\":\"Eintrittsdatum\",\"TgL4dH\":\"Treten Sie der Open Signing-Bewegung bei\",\"wJijgU\":\"Standort\",\"OIowgO\":\"Machen Sie es zu Ihrem eigenen durch erweiterte Anpassung und Einstellbarkeit.\",\"GHelWd\":\"Zusammengeführte PRs\",\"vXBVQZ\":\"Zusammengeführte PRs\",\"+8Nek/\":\"Monatlich\",\"6YtxFj\":\"Name\",\"CtgXe4\":\"Neue Benutzer\",\"OpNhRn\":\"Keine Kreditkarte erforderlich\",\"6C9AxJ\":\"Keine Kreditkarte erforderlich\",\"igwAqT\":\"Keines dieser Angebote passt zu Ihnen? Versuchen Sie das Selbst-Hosting!\",\"jjAtjQ\":\"Offene Issues\",\"b76QYo\":\"Open Source oder Hosted.\",\"OWsQIe\":\"Offenes Startup\",\"Un80BR\":\"OSS-Freunde\",\"6zNyfI\":\"Unsere benutzerdefinierten Vorlagen verfügen über intelligente Regeln, die Ihnen Zeit und Energie sparen können.\",\"+OmhKD\":\"Unsere Enterprise-Lizenz ist ideal für große Organisationen, die auf Documenso für all ihre Signaturanforderungen umsteigen möchten. Sie ist sowohl für unser Cloud-Angebot als auch für selbstgehostete Setups verfügbar und bietet eine breite Palette an Compliance- und Verwaltungsfunktionen.\",\"eK0veR\":\"Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features.\",\"I2ufwS\":\"Unsere selbstgehostete Option ist ideal für kleine Teams und Einzelpersonen, die eine einfache Lösung benötigen. Sie können unser docker-basiertes Setup verwenden, um in wenigen Minuten loszulegen. Übernehmen Sie die Kontrolle mit vollständiger Anpassbarkeit und Datenhoheit.\",\"F9564X\":\"Teilzeit\",\"qJVkX+\":\"Premium Profilname\",\"aHCEmh\":\"Preise\",\"rjGI/Q\":\"Datenschutz\",\"vERlcd\":\"Profil\",\"77/8W2\":\"React Widget (Demnächst).\",\"OYoVNk\":\"Erhalten Sie Ihren persönlichen Link zum Teilen mit allen, die Ihnen wichtig sind.\",\"GDvlUT\":\"Rolle\",\"bUqwb8\":\"Gehalt\",\"GNfoAO\":\"Sparen Sie $60 oder $120\",\"StoBff\":\"Sprachen suchen...\",\"dhi4w4\":\"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.\",\"kZBxnz\":\"Überall senden, verbinden, empfangen und einbetten.\",\"eSfS30\":\"Dienstalter\",\"aoDa18\":\"Shop\",\"5lWFkC\":\"Anmelden\",\"e+RpCP\":\"Registrieren\",\"4yiZOB\":\"Signaturprozess\",\"RkUXMm\":\"Jetzt registrieren\",\"omz3DH\":\"Intelligent.\",\"AvYbUL\":\"Auf GitHub favorisieren\",\"y2dGtU\":\"Favoriten\",\"uAQUqI\":\"Status\",\"XYLcNv\":\"Support\",\"KM6m8p\":\"Team\",\"lm5v+6\":\"Team-Posteingang\",\"CAL6E9\":\"Teams\",\"w4nM1s\":\"Vorlagen-Shop (Demnächst).\",\"yFoQ27\":\"Das ist großartig. Sie können sich die aktuellen <0>Issues ansehen und unserer <1>Discord-Community beitreten, um auf dem neuesten Stand zu bleiben, was die aktuellen Prioritäten sind. In jedem Fall sind wir eine offene Gemeinschaft und begrüßen jegliche Beiträge, technische und nicht-technische ❤️\",\"GE1BlA\":\"Diese Seite entwickelt sich weiter, während wir lernen, was ein großartiges Signing-Unternehmen ausmacht. Wir werden sie aktualisieren, wenn wir mehr zu teilen haben.\",\"MHrjPM\":\"Titel\",\"2YvdxE\":\"Insgesamt Abgeschlossene Dokumente\",\"8e4lIo\":\"Insgesamt Kunden\",\"bPpoCb\":\"Insgesamt Finanzierungsvolumen\",\"vb0Q0/\":\"Gesamtanzahl der Benutzer\",\"mgQhDS\":\"Wirklich Ihr Eigenes.\",\"4McJfQ\":\"Probieren Sie unseren Gratisplan aus\",\"9mkNAn\":\"Twitter-Statistiken\",\"CHzOWB\":\"Unbegrenzte Dokumente pro Monat\",\"BOV7DD\":\"Bis zu 10 Empfänger pro Dokument\",\"vdAd7c\":\"Die Nutzung unserer gehosteten Version ist der einfachste Weg, um zu starten. Sie können einfach abonnieren und mit der Unterzeichnung Ihrer Dokumente beginnen. Wir kümmern uns um die Infrastruktur, damit Sie sich auf Ihr Geschäft konzentrieren können. Zudem profitieren Sie bei der Nutzung unserer gehosteten Version von unseren vertrauenswürdigen Signaturzertifikaten, die Ihnen helfen, Vertrauen bei Ihren Kunden aufzubauen.\",\"W2nDs0\":\"Alle Statistiken anzeigen\",\"WMfAK8\":\"Wir helfen Ihnen gerne unter <0>support@documenso.com oder <1>in unserem Discord-Support-Kanal. Bitte senden Sie Lucas oder Timur eine Nachricht, um dem Kanal beizutreten, falls Sie noch kein Mitglied sind.\",\"ZaMyxU\":\"Was ist der Unterschied zwischen den Plänen?\",\"8GpyFt\":\"Wenn es um das Senden oder Empfangen eines Vertrags geht, können Sie auf blitzschnelle Geschwindigkeiten zählen.\",\"HEDnID\":\"Wo kann ich Unterstützung bekommen?\",\"sib3h3\":\"Warum sollte ich Documenso gegenüber DocuSign oder einem anderen Signatur-Tool bevorzugen?\",\"cVPDPt\":\"Warum sollte ich Ihren Hosting-Service nutzen?\",\"zkWmBh\":\"Jährlich\",\"8AKApo\":\"Ja! Documenso wird unter der GNU AGPL V3 Open-Source-Lizenz angeboten. Das bedeutet, dass Sie es kostenlos nutzen und sogar an Ihre Bedürfnisse anpassen können, solange Sie Ihre Änderungen unter derselben Lizenz veröffentlichen.\",\"rzQpex\":\"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.\",\"1j9aoC\":\"Ihr Browser unterstützt das Video-Tag nicht.\"}")}; \ No newline at end of file +/*eslint-disable*/module.exports={messages:JSON.parse("{\"J/hVSQ\":[[\"0\"]],\"u0zktA\":\"5 Standarddokumente pro Monat\",\"rKtmiD\":\"5 Benutzer inbegriffen\",\"vaHmll\":\"Eine 10x bessere Signaturerfahrung.\",\"gBefbz\":[\"Mehr Benutzer hinzufügen für \",[\"0\"]],\"XkF8tv\":\"Alle unsere Kennzahlen, Finanzen und Erkenntnisse sind öffentlich. Wir glauben an Transparenz und möchten unsere Reise mit Ihnen teilen. Mehr erfahren Sie hier: <0>Ankündigung Offene Kennzahlen\",\"tkQ/WI\":\"Erhobener Betrag\",\"qOMroC\":\"API-Zugriff\",\"FNv8t7\":\"Schön.\",\"W/TUoX\":\"Weil Unterschriften gefeiert werden sollten. Deshalb kümmern wir uns um jedes kleinste Detail in unserem Produkt.\",\"astDB+\":\"Blog\",\"7zGun7\":\"Aufbauen oben drauf.\",\"fxgcNV\":\"Kann ich Documenso kommerziell nutzen?\",\"V+D/YP\":\"Karrieren\",\"CWe7wB\":\"Änderungsprotokoll\",\"JZbmjL\":\"Wählen Sie eine Vorlage aus dem Community-App-Store. Oder reichen Sie Ihre eigene Vorlage ein, damit andere sie benutzen können.\",\"chL5IG\":\"Gemeinschaft\",\"p5+XQN\":\"Fertige Dokumente\",\"NApCXa\":\"Fertige Dokumente pro Monat\",\"z5kV0h\":\"Verbindungen\",\"YcfUZ9\":\"Kontaktiere uns\",\"1NJjIG\":\"Erstellen Sie Verbindungen und Automatisierungen mit Zapier und mehr, um sich mit Ihren Lieblingstools zu integrieren.\",\"rr83qK\":\"Erstellen Sie Ihr Konto und beginnen Sie mit der Nutzung modernster Dokumentensignaturen. Offene und schöne Signaturen sind zum Greifen nah.\",\"75ojt0\":\"Kunden mit einer aktiven Abonnements.\",\"pF9qTh\":\"Anpassen und erweitern.\",\"f8fH8W\":\"Design\",\"W6qD1T\":\"Entwickelt für jede Phase Ihrer Reise.\",\"K6KbY4\":\"Direktlink\",\"aLD+Td\":\"Documenso ist eine Gemeinschaftsanstrengung, um ein offenes und lebendiges Ökosystem um ein Werkzeug zu schaffen, das jeder frei nutzen und anpassen kann. Indem wir wirklich offen sind, wollen wir vertrauenswürdige Infrastruktur für die Zukunft des Internets schaffen.\",\"32yG8y\":\"Documenso auf X\",\"+1xAO7\":\"Unterschriften,<0/>endlich Open Source.\",\"TvY/XA\":\"Dokumentation\",\"tSS7hj\":\"Betten Sie Documenso ganz einfach in Ihr Produkt ein. Kopieren und fügen Sie einfach unser React-Widget in Ihre Anwendung ein.\",\"BWMGM4\":\"Einfaches Teilen (Bald).\",\"LRAhFG\":\"Easy Sharing.\",\"V6EY8B\":\"E-Mail- und Discord-Support\",\"C0/bri\":\"Beteiligung\",\"8Zy3YU\":\"Enterprise-Konformität, Lizenz- oder technische Bedürfnisse?\",\"ZSW8id\":\"Alles, was Sie für ein großartiges Signaturerlebnis benötigen.\",\"sXswT6\":\"Schnell.\",\"cT9Z9e\":\"Schneller, intelligenter und schöner.\",\"k/ANik\":\"Finanzen\",\"I7Exsw\":\"Folgen Sie uns auf X\",\"f3Botn\":\"Für Unternehmen, die über mehrere Teams skalieren möchten.\",\"y2DcZj\":\"Für kleine Teams und Einzelpersonen mit grundlegenden Bedürfnissen.\",\"2POOFK\":\"Kostenlos\",\"OdieZe\":\"Aus dem Blog\",\"IPgkVQ\":\"Vollzeit\",\"aSWzT9\":\"Lassen Sie sich bezahlen (Bald).\",\"ZDIydz\":\"Loslegen\",\"c3b0B0\":\"Loslegen\",\"pS8wej\":\"Fangen Sie heute an.\",\"7FPIvI\":\"Erhalten Sie die neuesten Nachrichten von Documenso, einschließlich Produkt-Updates, Team-Ankündigungen und mehr!\",\"kV0qBq\":\"GitHub: Gesamte PRs zusammengeführt\",\"652R6j\":\"GitHub: Gesamte offene Issues\",\"R1aJ0W\":\"GitHub: Gesamtanzahl Sterne\",\"P1ovAc\":\"Globale Gehaltsbänder\",\"IAq/yr\":\"Wachstum\",\"Xi7f+z\":\"Wie kann ich beitragen?\",\"9VGuMA\":\"Wie gehen Sie mit meinen Daten um?\",\"fByw/g\":\"Einzelperson\",\"Csm+TN\":\"Integrierte Zahlungen mit Stripe, sodass Sie sich keine Sorgen ums Bezahlen machen müssen.\",\"phSPy7\":\"Integriert sich mit all Ihren Lieblingstools.\",\"pfjrI2\":\"Gibt es mehr?\",\"LOyqaC\":\"Es liegt an Ihnen. Entweder klonen Sie unser Repository oder nutzen unsere einfach zu bedienende Hosting-Lösung.\",\"PCgMVa\":\"Eintrittsdatum\",\"TgL4dH\":\"Treten Sie der Open Signing-Bewegung bei\",\"wJijgU\":\"Standort\",\"OIowgO\":\"Machen Sie es zu Ihrem eigenen durch erweiterte Anpassung und Einstellbarkeit.\",\"GHelWd\":\"Zusammengeführte PRs\",\"vXBVQZ\":\"Zusammengeführte PRs\",\"+8Nek/\":\"Monatlich\",\"6YtxFj\":\"Name\",\"CtgXe4\":\"Neue Benutzer\",\"OpNhRn\":\"Keine Kreditkarte erforderlich\",\"6C9AxJ\":\"Keine Kreditkarte erforderlich\",\"igwAqT\":\"Keines dieser Angebote passt zu Ihnen? Versuchen Sie das Selbst-Hosting!\",\"jjAtjQ\":\"Offene Issues\",\"b76QYo\":\"Open Source oder Hosted.\",\"OWsQIe\":\"Offenes Startup\",\"Un80BR\":\"OSS-Freunde\",\"6zNyfI\":\"Unsere benutzerdefinierten Vorlagen verfügen über intelligente Regeln, die Ihnen Zeit und Energie sparen können.\",\"+OmhKD\":\"Unsere Enterprise-Lizenz ist ideal für große Organisationen, die auf Documenso für all ihre Signaturanforderungen umsteigen möchten. Sie ist sowohl für unser Cloud-Angebot als auch für selbstgehostete Setups verfügbar und bietet eine breite Palette an Compliance- und Verwaltungsfunktionen.\",\"eK0veR\":\"Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features.\",\"I2ufwS\":\"Unsere selbstgehostete Option ist ideal für kleine Teams und Einzelpersonen, die eine einfache Lösung benötigen. Sie können unser docker-basiertes Setup verwenden, um in wenigen Minuten loszulegen. Übernehmen Sie die Kontrolle mit vollständiger Anpassbarkeit und Datenhoheit.\",\"F9564X\":\"Teilzeit\",\"qJVkX+\":\"Premium Profilname\",\"aHCEmh\":\"Preise\",\"rjGI/Q\":\"Datenschutz\",\"vERlcd\":\"Profil\",\"77/8W2\":\"React Widget (Demnächst).\",\"OYoVNk\":\"Erhalten Sie Ihren persönlichen Link zum Teilen mit allen, die Ihnen wichtig sind.\",\"GDvlUT\":\"Rolle\",\"bUqwb8\":\"Gehalt\",\"GNfoAO\":\"Sparen Sie $60 oder $120\",\"StoBff\":\"Sprachen suchen...\",\"dhi4w4\":\"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.\",\"kZBxnz\":\"Überall senden, verbinden, empfangen und einbetten.\",\"eSfS30\":\"Dienstalter\",\"aoDa18\":\"Shop\",\"5lWFkC\":\"Anmelden\",\"e+RpCP\":\"Registrieren\",\"4yiZOB\":\"Signaturprozess\",\"RkUXMm\":\"Jetzt registrieren\",\"omz3DH\":\"Intelligent.\",\"AvYbUL\":\"Auf GitHub favorisieren\",\"y2dGtU\":\"Favoriten\",\"uAQUqI\":\"Status\",\"XYLcNv\":\"Support\",\"KM6m8p\":\"Team\",\"lm5v+6\":\"Team-Posteingang\",\"CAL6E9\":\"Teams\",\"w4nM1s\":\"Vorlagen-Shop (Demnächst).\",\"yFoQ27\":\"Das ist großartig. Sie können sich die aktuellen <0>Issues ansehen und unserer <1>Discord-Community beitreten, um auf dem neuesten Stand zu bleiben, was die aktuellen Prioritäten sind. In jedem Fall sind wir eine offene Gemeinschaft und begrüßen jegliche Beiträge, technische und nicht-technische ❤️\",\"GE1BlA\":\"Diese Seite entwickelt sich weiter, während wir lernen, was ein großartiges Signing-Unternehmen ausmacht. Wir werden sie aktualisieren, wenn wir mehr zu teilen haben.\",\"MHrjPM\":\"Titel\",\"2YvdxE\":\"Insgesamt Abgeschlossene Dokumente\",\"8e4lIo\":\"Insgesamt Kunden\",\"bPpoCb\":\"Insgesamt Finanzierungsvolumen\",\"vb0Q0/\":\"Gesamtanzahl der Benutzer\",\"mgQhDS\":\"Wirklich Ihr Eigenes.\",\"4McJfQ\":\"Probieren Sie unseren Gratisplan aus\",\"9mkNAn\":\"Twitter-Statistiken\",\"CHzOWB\":\"Unbegrenzte Dokumente pro Monat\",\"BOV7DD\":\"Bis zu 10 Empfänger pro Dokument\",\"vdAd7c\":\"Die Nutzung unserer gehosteten Version ist der einfachste Weg, um zu starten. Sie können einfach abonnieren und mit der Unterzeichnung Ihrer Dokumente beginnen. Wir kümmern uns um die Infrastruktur, damit Sie sich auf Ihr Geschäft konzentrieren können. Zudem profitieren Sie bei der Nutzung unserer gehosteten Version von unseren vertrauenswürdigen Signaturzertifikaten, die Ihnen helfen, Vertrauen bei Ihren Kunden aufzubauen.\",\"W2nDs0\":\"Alle Statistiken anzeigen\",\"WMfAK8\":\"Wir helfen Ihnen gerne unter <0>support@documenso.com oder <1>in unserem Discord-Support-Kanal. Bitte senden Sie Lucas oder Timur eine Nachricht, um dem Kanal beizutreten, falls Sie noch kein Mitglied sind.\",\"ZaMyxU\":\"Was ist der Unterschied zwischen den Plänen?\",\"8GpyFt\":\"Wenn es um das Senden oder Empfangen eines Vertrags geht, können Sie auf blitzschnelle Geschwindigkeiten zählen.\",\"HEDnID\":\"Wo kann ich Unterstützung bekommen?\",\"sib3h3\":\"Warum sollte ich Documenso gegenüber DocuSign oder einem anderen Signatur-Tool bevorzugen?\",\"cVPDPt\":\"Warum sollte ich Ihren Hosting-Service nutzen?\",\"zkWmBh\":\"Jährlich\",\"8AKApo\":\"Ja! Documenso wird unter der GNU AGPL V3 Open-Source-Lizenz angeboten. Das bedeutet, dass Sie es kostenlos nutzen und sogar an Ihre Bedürfnisse anpassen können, solange Sie Ihre Änderungen unter derselben Lizenz veröffentlichen.\",\"rzQpex\":\"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.\",\"1j9aoC\":\"Ihr Browser unterstützt das Video-Tag nicht.\"}")}; \ No newline at end of file diff --git a/packages/lib/translations/de/marketing.po b/packages/lib/translations/de/marketing.po index 9fc0985ce..3c4641f75 100644 --- a/packages/lib/translations/de/marketing.po +++ b/packages/lib/translations/de/marketing.po @@ -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 "Einfaches Teilen (Bald)." + +#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:46 +msgid "Easy Sharing." +msgstr "" #: 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 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,6 @@ 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." diff --git a/packages/lib/translations/en/marketing.js b/packages/lib/translations/en/marketing.js index 89ef46516..7bca3ac53 100644 --- a/packages/lib/translations/en/marketing.js +++ b/packages/lib/translations/en/marketing.js @@ -1 +1 @@ -/*eslint-disable*/module.exports={messages:JSON.parse("{\"J/hVSQ\":[[\"0\"]],\"u0zktA\":\"5 standard documents per month\",\"rKtmiD\":\"5 Users Included\",\"vaHmll\":\"A 10x better signing experience.\",\"gBefbz\":[\"Add More Users for \",[\"0\"]],\"XkF8tv\":\"All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics\",\"tkQ/WI\":\"Amount Raised\",\"qOMroC\":\"API Access\",\"FNv8t7\":\"Beautiful.\",\"W/TUoX\":\"Because signing should be celebrated. That’s why we care about the smallest detail in our product.\",\"astDB+\":\"Blog\",\"7zGun7\":\"Build on top.\",\"fxgcNV\":\"Can I use Documenso commercially?\",\"V+D/YP\":\"Careers\",\"CWe7wB\":\"Changelog\",\"JZbmjL\":\"Choose a template from the community app store. Or submit your own template for others to use.\",\"chL5IG\":\"Community\",\"p5+XQN\":\"Completed Documents\",\"NApCXa\":\"Completed Documents per Month\",\"z5kV0h\":\"Connections\",\"YcfUZ9\":\"Contact Us\",\"1NJjIG\":\"Create connections and automations with Zapier and more to integrate with your favorite tools.\",\"rr83qK\":\"Create your account and start using state-of-the-art document signing. Open and beautiful signing is within your grasp.\",\"75ojt0\":\"Customers with an Active Subscriptions.\",\"pF9qTh\":\"Customise and expand.\",\"f8fH8W\":\"Design\",\"W6qD1T\":\"Designed for every stage of your journey.\",\"K6KbY4\":\"Direct Link\",\"aLD+Td\":\"Documenso is a community effort to create an open and vibrant ecosystem around a tool, everybody is free to use and adapt. By being truly open we want to create trusted infrastructure for the future of the internet.\",\"32yG8y\":\"Documenso on X\",\"+1xAO7\":\"Document signing,<0/>finally open source.\",\"TvY/XA\":\"Documentation\",\"tSS7hj\":\"Easily embed Documenso into your product. Simply copy and paste our react widget into your application.\",\"BWMGM4\":\"Easy Sharing (Soon).\",\"V6EY8B\":\"Email and Discord Support\",\"C0/bri\":\"Engagement\",\"8Zy3YU\":\"Enterprise Compliance, License or Technical Needs?\",\"ZSW8id\":\"Everything you need for a great signing experience.\",\"sXswT6\":\"Fast.\",\"cT9Z9e\":\"Faster, smarter and more beautiful.\",\"k/ANik\":\"Finances\",\"I7Exsw\":\"Follow us on X\",\"f3Botn\":\"For companies looking to scale across multiple teams.\",\"y2DcZj\":\"For small teams and individuals with basic needs.\",\"2POOFK\":\"Free\",\"OdieZe\":\"From the blog\",\"IPgkVQ\":\"Full-Time\",\"aSWzT9\":\"Get paid (Soon).\",\"ZDIydz\":\"Get started\",\"c3b0B0\":\"Get Started\",\"pS8wej\":\"Get started today.\",\"7FPIvI\":\"Get the latest news from Documenso, including product updates, team announcements and more!\",\"kV0qBq\":\"GitHub: Total Merged PRs\",\"652R6j\":\"GitHub: Total Open Issues\",\"R1aJ0W\":\"GitHub: Total Stars\",\"P1ovAc\":\"Global Salary Bands\",\"IAq/yr\":\"Growth\",\"Xi7f+z\":\"How can I contribute?\",\"9VGuMA\":\"How do you handle my data?\",\"fByw/g\":\"Individual\",\"Csm+TN\":\"Integrated payments with Stripe so you don’t have to worry about getting paid.\",\"phSPy7\":\"Integrates with all your favourite tools.\",\"pfjrI2\":\"Is there more?\",\"LOyqaC\":\"It’s up to you. Either clone our repository or rely on our easy to use hosting solution.\",\"PCgMVa\":\"Join Date\",\"TgL4dH\":\"Join the Open Signing Movement\",\"wJijgU\":\"Location\",\"OIowgO\":\"Make it your own through advanced customization and adjustability.\",\"GHelWd\":\"Merged PR's\",\"vXBVQZ\":\"Merged PRs\",\"+8Nek/\":\"Monthly\",\"6YtxFj\":\"Name\",\"CtgXe4\":\"New Users\",\"OpNhRn\":\"No credit card required\",\"6C9AxJ\":\"No Credit Card required\",\"igwAqT\":\"None of these work for you? Try self-hosting!\",\"jjAtjQ\":\"Open Issues\",\"b76QYo\":\"Open Source or Hosted.\",\"OWsQIe\":\"Open Startup\",\"Un80BR\":\"OSS Friends\",\"6zNyfI\":\"Our custom templates come with smart rules that can help you save time and energy.\",\"+OmhKD\":\"Our Enterprise License is great for large organizations looking to switch to Documenso for all their signing needs. It's available for our cloud offering as well as self-hosted setups and offers a wide range of compliance and Adminstration Features.\",\"eK0veR\":\"Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features.\",\"I2ufwS\":\"Our self-hosted option is great for small teams and individuals who need a simple solution. You can use our docker based setup to get started in minutes. Take control with full customizability and data ownership.\",\"F9564X\":\"Part-Time\",\"qJVkX+\":\"Premium Profile Name\",\"aHCEmh\":\"Pricing\",\"rjGI/Q\":\"Privacy\",\"vERlcd\":\"Profile\",\"77/8W2\":\"React Widget (Soon).\",\"OYoVNk\":\"Receive your personal link to share with everyone you care about.\",\"GDvlUT\":\"Role\",\"bUqwb8\":\"Salary\",\"GNfoAO\":\"Save $60 or $120\",\"StoBff\":\"Search languages...\",\"dhi4w4\":\"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.\",\"kZBxnz\":\"Send, connect, receive and embed everywhere.\",\"eSfS30\":\"Seniority\",\"aoDa18\":\"Shop\",\"5lWFkC\":\"Sign in\",\"e+RpCP\":\"Sign up\",\"4yiZOB\":\"Signing Process\",\"RkUXMm\":\"Signup Now\",\"omz3DH\":\"Smart.\",\"AvYbUL\":\"Star on GitHub\",\"y2dGtU\":\"Stars\",\"uAQUqI\":\"Status\",\"XYLcNv\":\"Support\",\"KM6m8p\":\"Team\",\"lm5v+6\":\"Team Inbox\",\"CAL6E9\":\"Teams\",\"w4nM1s\":\"Template Store (Soon).\",\"yFoQ27\":\"That's awesome. You can take a look at the current <0>Issues and join our <1>Discord Community to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️\",\"GE1BlA\":\"This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share.\",\"MHrjPM\":\"Title\",\"2YvdxE\":\"Total Completed Documents\",\"8e4lIo\":\"Total Customers\",\"bPpoCb\":\"Total Funding Raised\",\"vb0Q0/\":\"Total Users\",\"mgQhDS\":\"Truly your own.\",\"4McJfQ\":\"Try our Free Plan\",\"9mkNAn\":\"Twitter Stats\",\"CHzOWB\":\"Unlimited Documents per Month\",\"BOV7DD\":\"Up to 10 recipients per document\",\"vdAd7c\":\"Using our hosted version is the easiest way to get started, you can simply subscribe and start signing your documents. We take care of the infrastructure, so you can focus on your business. Additionally, when using our hosted version you benefit from our trusted signing certificates which helps you to build trust with your customers.\",\"W2nDs0\":\"View all stats\",\"WMfAK8\":\"We are happy to assist you at <0>support@documenso.com or <1>in our Discord-Support-Channel please message either Lucas or Timur to get added to the channel if you are not already a member.\",\"ZaMyxU\":\"What is the difference between the plans?\",\"8GpyFt\":\"When it comes to sending or receiving a contract, you can count on lightning-fast speeds.\",\"HEDnID\":\"Where can I get support?\",\"sib3h3\":\"Why should I prefer Documenso over DocuSign or some other signing tool?\",\"cVPDPt\":\"Why should I use your hosting service?\",\"zkWmBh\":\"Yearly\",\"8AKApo\":\"Yes! Documenso is offered under the GNU AGPL V3 open source license. This means you can use it for free and even modify it to fit your needs, as long as you publish your changes under the same license.\",\"rzQpex\":\"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.\",\"1j9aoC\":\"Your browser does not support the video tag.\"}")}; \ No newline at end of file +/*eslint-disable*/module.exports={messages:JSON.parse("{\"J/hVSQ\":[[\"0\"]],\"u0zktA\":\"5 standard documents per month\",\"rKtmiD\":\"5 Users Included\",\"vaHmll\":\"A 10x better signing experience.\",\"gBefbz\":[\"Add More Users for \",[\"0\"]],\"XkF8tv\":\"All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics\",\"tkQ/WI\":\"Amount Raised\",\"qOMroC\":\"API Access\",\"FNv8t7\":\"Beautiful.\",\"W/TUoX\":\"Because signing should be celebrated. That’s why we care about the smallest detail in our product.\",\"astDB+\":\"Blog\",\"7zGun7\":\"Build on top.\",\"fxgcNV\":\"Can I use Documenso commercially?\",\"V+D/YP\":\"Careers\",\"CWe7wB\":\"Changelog\",\"JZbmjL\":\"Choose a template from the community app store. Or submit your own template for others to use.\",\"chL5IG\":\"Community\",\"p5+XQN\":\"Completed Documents\",\"NApCXa\":\"Completed Documents per Month\",\"z5kV0h\":\"Connections\",\"YcfUZ9\":\"Contact Us\",\"1NJjIG\":\"Create connections and automations with Zapier and more to integrate with your favorite tools.\",\"rr83qK\":\"Create your account and start using state-of-the-art document signing. Open and beautiful signing is within your grasp.\",\"75ojt0\":\"Customers with an Active Subscriptions.\",\"pF9qTh\":\"Customise and expand.\",\"f8fH8W\":\"Design\",\"W6qD1T\":\"Designed for every stage of your journey.\",\"K6KbY4\":\"Direct Link\",\"aLD+Td\":\"Documenso is a community effort to create an open and vibrant ecosystem around a tool, everybody is free to use and adapt. By being truly open we want to create trusted infrastructure for the future of the internet.\",\"32yG8y\":\"Documenso on X\",\"+1xAO7\":\"Document signing,<0/>finally open source.\",\"TvY/XA\":\"Documentation\",\"tSS7hj\":\"Easily embed Documenso into your product. Simply copy and paste our react widget into your application.\",\"BWMGM4\":\"Easy Sharing (Soon).\",\"LRAhFG\":\"Easy Sharing.\",\"V6EY8B\":\"Email and Discord Support\",\"C0/bri\":\"Engagement\",\"8Zy3YU\":\"Enterprise Compliance, License or Technical Needs?\",\"ZSW8id\":\"Everything you need for a great signing experience.\",\"sXswT6\":\"Fast.\",\"cT9Z9e\":\"Faster, smarter and more beautiful.\",\"k/ANik\":\"Finances\",\"I7Exsw\":\"Follow us on X\",\"f3Botn\":\"For companies looking to scale across multiple teams.\",\"y2DcZj\":\"For small teams and individuals with basic needs.\",\"2POOFK\":\"Free\",\"OdieZe\":\"From the blog\",\"IPgkVQ\":\"Full-Time\",\"aSWzT9\":\"Get paid (Soon).\",\"ZDIydz\":\"Get started\",\"c3b0B0\":\"Get Started\",\"pS8wej\":\"Get started today.\",\"7FPIvI\":\"Get the latest news from Documenso, including product updates, team announcements and more!\",\"kV0qBq\":\"GitHub: Total Merged PRs\",\"652R6j\":\"GitHub: Total Open Issues\",\"R1aJ0W\":\"GitHub: Total Stars\",\"P1ovAc\":\"Global Salary Bands\",\"IAq/yr\":\"Growth\",\"Xi7f+z\":\"How can I contribute?\",\"9VGuMA\":\"How do you handle my data?\",\"fByw/g\":\"Individual\",\"Csm+TN\":\"Integrated payments with Stripe so you don’t have to worry about getting paid.\",\"phSPy7\":\"Integrates with all your favourite tools.\",\"pfjrI2\":\"Is there more?\",\"LOyqaC\":\"It’s up to you. Either clone our repository or rely on our easy to use hosting solution.\",\"PCgMVa\":\"Join Date\",\"TgL4dH\":\"Join the Open Signing Movement\",\"wJijgU\":\"Location\",\"OIowgO\":\"Make it your own through advanced customization and adjustability.\",\"GHelWd\":\"Merged PR's\",\"vXBVQZ\":\"Merged PRs\",\"+8Nek/\":\"Monthly\",\"6YtxFj\":\"Name\",\"CtgXe4\":\"New Users\",\"OpNhRn\":\"No credit card required\",\"6C9AxJ\":\"No Credit Card required\",\"igwAqT\":\"None of these work for you? Try self-hosting!\",\"jjAtjQ\":\"Open Issues\",\"b76QYo\":\"Open Source or Hosted.\",\"OWsQIe\":\"Open Startup\",\"Un80BR\":\"OSS Friends\",\"6zNyfI\":\"Our custom templates come with smart rules that can help you save time and energy.\",\"+OmhKD\":\"Our Enterprise License is great for large organizations looking to switch to Documenso for all their signing needs. It's available for our cloud offering as well as self-hosted setups and offers a wide range of compliance and Adminstration Features.\",\"eK0veR\":\"Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features.\",\"I2ufwS\":\"Our self-hosted option is great for small teams and individuals who need a simple solution. You can use our docker based setup to get started in minutes. Take control with full customizability and data ownership.\",\"F9564X\":\"Part-Time\",\"qJVkX+\":\"Premium Profile Name\",\"aHCEmh\":\"Pricing\",\"rjGI/Q\":\"Privacy\",\"vERlcd\":\"Profile\",\"77/8W2\":\"React Widget (Soon).\",\"OYoVNk\":\"Receive your personal link to share with everyone you care about.\",\"GDvlUT\":\"Role\",\"bUqwb8\":\"Salary\",\"GNfoAO\":\"Save $60 or $120\",\"StoBff\":\"Search languages...\",\"dhi4w4\":\"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.\",\"kZBxnz\":\"Send, connect, receive and embed everywhere.\",\"eSfS30\":\"Seniority\",\"aoDa18\":\"Shop\",\"5lWFkC\":\"Sign in\",\"e+RpCP\":\"Sign up\",\"4yiZOB\":\"Signing Process\",\"RkUXMm\":\"Signup Now\",\"omz3DH\":\"Smart.\",\"AvYbUL\":\"Star on GitHub\",\"y2dGtU\":\"Stars\",\"uAQUqI\":\"Status\",\"XYLcNv\":\"Support\",\"KM6m8p\":\"Team\",\"lm5v+6\":\"Team Inbox\",\"CAL6E9\":\"Teams\",\"w4nM1s\":\"Template Store (Soon).\",\"yFoQ27\":\"That's awesome. You can take a look at the current <0>Issues and join our <1>Discord Community to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️\",\"GE1BlA\":\"This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share.\",\"MHrjPM\":\"Title\",\"2YvdxE\":\"Total Completed Documents\",\"8e4lIo\":\"Total Customers\",\"bPpoCb\":\"Total Funding Raised\",\"vb0Q0/\":\"Total Users\",\"mgQhDS\":\"Truly your own.\",\"4McJfQ\":\"Try our Free Plan\",\"9mkNAn\":\"Twitter Stats\",\"CHzOWB\":\"Unlimited Documents per Month\",\"BOV7DD\":\"Up to 10 recipients per document\",\"vdAd7c\":\"Using our hosted version is the easiest way to get started, you can simply subscribe and start signing your documents. We take care of the infrastructure, so you can focus on your business. Additionally, when using our hosted version you benefit from our trusted signing certificates which helps you to build trust with your customers.\",\"W2nDs0\":\"View all stats\",\"WMfAK8\":\"We are happy to assist you at <0>support@documenso.com or <1>in our Discord-Support-Channel please message either Lucas or Timur to get added to the channel if you are not already a member.\",\"ZaMyxU\":\"What is the difference between the plans?\",\"8GpyFt\":\"When it comes to sending or receiving a contract, you can count on lightning-fast speeds.\",\"HEDnID\":\"Where can I get support?\",\"sib3h3\":\"Why should I prefer Documenso over DocuSign or some other signing tool?\",\"cVPDPt\":\"Why should I use your hosting service?\",\"zkWmBh\":\"Yearly\",\"8AKApo\":\"Yes! Documenso is offered under the GNU AGPL V3 open source license. This means you can use it for free and even modify it to fit your needs, as long as you publish your changes under the same license.\",\"rzQpex\":\"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.\",\"1j9aoC\":\"Your browser does not support the video tag.\"}")}; \ No newline at end of file diff --git a/packages/lib/translations/en/marketing.po b/packages/lib/translations/en/marketing.po index 913242f11..ade6cf9be 100644 --- a/packages/lib/translations/en/marketing.po +++ b/packages/lib/translations/en/marketing.po @@ -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 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." From 27066e202245b7534a4b5e6ba3790d0a4050140d Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Wed, 21 Aug 2024 11:06:26 +1000 Subject: [PATCH 27/28] chore: add translations (#1295) --- packages/lib/translations/de/common.po | 3 ++- packages/lib/translations/de/marketing.po | 7 ++++--- packages/lib/translations/de/web.po | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/lib/translations/de/common.po b/packages/lib/translations/de/common.po index cc0e99d1a..2ccc01506 100644 --- a/packages/lib/translations/de/common.po +++ b/packages/lib/translations/de/common.po @@ -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" + diff --git a/packages/lib/translations/de/marketing.po b/packages/lib/translations/de/marketing.po index 3c4641f75..8a7fec45b 100644 --- a/packages/lib/translations/de/marketing.po +++ b/packages/lib/translations/de/marketing.po @@ -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" @@ -158,11 +158,11 @@ msgstr "Betten Sie Documenso ganz einfach in Ihr Produkt ein. Kopieren und füge #: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:42 #~ msgid "Easy Sharing (Soon)." -#~ msgstr "Einfaches Teilen (Bald)." +#~ msgstr "Easy Sharing (Soon)." #: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:46 msgid "Easy Sharing." -msgstr "" +msgstr "Einfaches Teilen." #: apps/marketing/src/components/(marketing)/pricing-table.tsx:148 #: apps/marketing/src/components/(marketing)/pricing-table.tsx:192 @@ -601,3 +601,4 @@ msgstr "Sie können Documenso kostenlos selbst hosten oder unsere sofort einsatz #: 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." + diff --git a/packages/lib/translations/de/web.po b/packages/lib/translations/de/web.po index 2e315afce..c8c7cd6bb 100644 --- a/packages/lib/translations/de/web.po +++ b/packages/lib/translations/de/web.po @@ -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" + From 66fdc1d659edc2e2faa98643069c5fd4e2c1c289 Mon Sep 17 00:00:00 2001 From: TKB Studios <69647028+tkbstudios@users.noreply.github.com> Date: Wed, 21 Aug 2024 04:08:04 +0300 Subject: [PATCH 28/28] chore: update readme for manual self-hosting (#1270) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 74e3bddc5..f32438800 100644 --- a/README.md +++ b/README.md @@ -261,6 +261,7 @@ npm run prisma:migrate-deploy Finally, you can start it with: ``` +cd apps/web npm run start ```