From 06c0a5040105d3706652fe28d43ed034b9c7fdda Mon Sep 17 00:00:00 2001 From: Ephraim Duncan <55143799+ephraimduncan@users.noreply.github.com> Date: Tue, 20 Aug 2024 03:32:53 +0000 Subject: [PATCH 01/19] feat: copy and paste fields (#1193) Adds keyboard shortcuts for copying and pasting fields, additionally adds the ability to duplicate a field via the UI. --- .vscode/settings.json | 12 ++- .../blog/announcing-vial-21-cfr-part-11.mdx | 30 +++----- .../(signing)/sign/[token]/number-field.tsx | 7 +- .../(signing)/sign/[token]/radio-field.tsx | 18 ++--- .../sign/[token]/signature-field.tsx | 2 +- .../app/(signing)/sign/[token]/text-field.tsx | 12 +-- packages/ui/components/field/field.tsx | 2 +- .../primitives/document-flow/add-fields.tsx | 76 ++++++++++++++++++- .../advanced-fields/checkbox.tsx | 4 +- .../document-flow/advanced-fields/radio.tsx | 2 +- .../primitives/document-flow/field-item.tsx | 51 ++++++++++--- .../single-player-mode-fields.tsx | 7 +- 12 files changed, 158 insertions(+), 65 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index f5542fbb5..e6ff5d1a0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,12 +5,7 @@ "editor.codeActionsOnSave": { "source.fixAll": "explicit" }, - "eslint.validate": [ - "typescript", - "typescriptreact", - "javascript", - "javascriptreact" - ], + "eslint.validate": ["typescript", "typescriptreact", "javascript", "javascriptreact"], "javascript.preferences.importModuleSpecifier": "non-relative", "javascript.preferences.useAliasesForRenames": false, "typescript.enablePromptUseWorkspaceTsdk": true, @@ -20,4 +15,7 @@ "[prisma]": { "editor.defaultFormatter": "Prisma.prisma" }, -} \ No newline at end of file + "[typescriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + } +} diff --git a/apps/marketing/content/blog/announcing-vial-21-cfr-part-11.mdx b/apps/marketing/content/blog/announcing-vial-21-cfr-part-11.mdx index 540f7a0a5..b64953c8c 100644 --- a/apps/marketing/content/blog/announcing-vial-21-cfr-part-11.mdx +++ b/apps/marketing/content/blog/announcing-vial-21-cfr-part-11.mdx @@ -11,14 +11,7 @@ tags: - Compliance --- - +
Vial.com uses Documenso for 21 CFR Part 11 compliant signing.
@@ -26,42 +19,40 @@ tags: > TLDR; We launched Vial.com on Documenso and are open for 21 CFR Part 11 business. # What is 21 CFR -You have never heard of 21 CFR Part 11? You are in good company since most people haven't. If you have, you probably work in an industry regulated by the U.S. Food and Drug Administration (FDA). Title 21 of the Code of Federal Regulations (CFR) is dedicated to detailing FDA-regulated business, and sub-part 11 sets out guidelines for using electronic signatures in this highly regulated field. Hence, 21 CFR Part 11 is highly relevant for regulated industries that aim to employ digital signatures. The guidelines set out in 21 CFR Part 11 aim to provide trustworthy, reliable, and equivalent to paper records and handwritten signatures. All Industries that fall under the FDA's regulation, e.g. pharmaceuticals, biotechnology, medical devices, and biologics, must comply with these rules when choosing or creating systems for electronic signatures. + +You have never heard of 21 CFR Part 11? You are in good company since most people haven't. If you have, you probably work in an industry regulated by the U.S. Food and Drug Administration (FDA). Title 21 of the Code of Federal Regulations (CFR) is dedicated to detailing FDA-regulated business, and sub-part 11 sets out guidelines for using electronic signatures in this highly regulated field. Hence, 21 CFR Part 11 is highly relevant for regulated industries that aim to employ digital signatures. The guidelines set out in 21 CFR Part 11 aim to provide trustworthy, reliable, and equivalent to paper records and handwritten signatures. All Industries that fall under the FDA's regulation, e.g. pharmaceuticals, biotechnology, medical devices, and biologics, must comply with these rules when choosing or creating systems for electronic signatures. Compliance with 21 CFR Part 11 is crucial for companies to use electronic records and signatures in their operations legally. It affects how companies manage documentation, conduct audits, and maintain regulatory submissions. Non-compliance can result in legal penalties, rejected submissions, and delays in product approvals, emphasizing the importance of adhering to these guidelines in FDA-regulated activities. # Vial.com + Vial is a technology company on a mission to advance programs to market through computationally designed therapeutics and cost-effective clinical trials. It is imperative that Vial manages this process securely, effectively, and highly compliant. By leveraging it's modern platform, Vial aims to accelerate drug development and, ultimately, time to market for new therapies. You can learn more about them [here](https://vial.com/about-us). [Together](https://documen.so/vial-documenso), Documenso and Vial set out to create the first open-source, 21 CFR Part 11 compliant signing solution. After iterating over the product together, Vial moved their operation from DocuSign, a known legacy signing provider, to a Documenso Enterprise plan. We are very happy to be able to support Vial’s mission by fulfilling our own: bringing open signing and all its innovation to where it's needed. # 21 CFR Part 11 on Documenso Highlights + 21 CFR Part 11 is a highly complex statute, and going into the all design rationales and the following implementation details, deserves its own article later. For now, I want to share a few notable highlights. ## The Full Experience + We implemented 21 CFR Part 11, keeping the main user experience of Documenso intact. Our 21 CFR module is not separate but natively integrated into all Documenso flows, thus not sacrificing usability for compliance. This also means most (if not all) advanced features we offer are usable in a compliant way. This prevents customers from being trapped in an anti-innovation bubble, not allowing access to new features for fear of non-compliance. ## Action Reauth Using Passkeys - + +
Using passkeys (used here via fingerprint scanner) is the smoothest way to re-authenticate.
- One of the requirements affecting day-to-day life the most is the requirement to actually reauthenticate every signature placed on a document. While we can't change that, we can help make the reauthentication as painless as possible. To this end, we opted for passkeys. While Documenso supports passkeys to log in, they are also supported to authenticate signing on a per-signature level as part of the Documenso Enterprise Plan. The user still has to authenticate every signature but can now do so from the comfort of their passkey provider, be that 1Password, their browser, or any other provider. ## Direct Links + We recently launched [Direct Template Links](https://documen.so/direct-links), a new way to let people sign and fill out forms. Links can be completed anytime, creating a new document in the process. Direct Links are also 21 CFR part 11 compliant, using action reauthentication, audit log, and all other compliance requirements. # Documenso Enterprise Plan + With the successful launch of Vial, we are now open for business. 21 CFR Part 11 compliance is part of the Documenso Enterprise plan, which includes all regulations we currently support and upcoming additions. While the pricing depends heavily on your needs and scale, we offer fixed-price plans for better predictability for both sides. In our experience, volume-based pricing is a legacy headache we want to avoid. If you are FDA-regulated and looking for a modern signing solution, we are happy to discuss your requirements in detail. You can write us (hi@documenso.com) or contact [our enterprise team](https://documen.so/21cfr) at any time or stage. @@ -70,4 +61,3 @@ If you have any questions or comments, please reach out on [Twitter / X](https:/ Best from Hamburg\ Timur - diff --git a/apps/web/src/app/(signing)/sign/[token]/number-field.tsx b/apps/web/src/app/(signing)/sign/[token]/number-field.tsx index 79a91d6b5..adf04015f 100644 --- a/apps/web/src/app/(signing)/sign/[token]/number-field.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/number-field.tsx @@ -216,7 +216,10 @@ export const NumberField = ({ field, recipient, onSignField, onUnsignField }: Nu let fieldDisplayName = 'Number'; if (parsedFieldMeta?.label) { - fieldDisplayName = parsedFieldMeta.label.length > 10 ? parsedFieldMeta.label.substring(0, 10) + '...' : parsedFieldMeta.label; + fieldDisplayName = + parsedFieldMeta.label.length > 10 + ? parsedFieldMeta.label.substring(0, 10) + '...' + : parsedFieldMeta.label; } const userInputHasErrors = Object.values(errors).some((error) => error.length > 0); @@ -246,7 +249,7 @@ export const NumberField = ({ field, recipient, onSignField, onUnsignField }: Nu )} > - {fieldDisplayName} + {fieldDisplayName}

)} diff --git a/apps/web/src/app/(signing)/sign/[token]/radio-field.tsx b/apps/web/src/app/(signing)/sign/[token]/radio-field.tsx index d09205ca0..cdae9c9af 100644 --- a/apps/web/src/app/(signing)/sign/[token]/radio-field.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/radio-field.tsx @@ -172,15 +172,15 @@ export const RadioField = ({ field, recipient, onSignField, onUnsignField }: Rad {values?.map((item, index) => (
- - + +
))}
diff --git a/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx b/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx index 6c1ef2bd2..fc973f77e 100644 --- a/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx @@ -190,7 +190,7 @@ export const SignatureField = ({ )} {state === 'empty' && ( -

+

Signature

)} diff --git a/apps/web/src/app/(signing)/sign/[token]/text-field.tsx b/apps/web/src/app/(signing)/sign/[token]/text-field.tsx index 6e2a7b405..d9f9edef3 100644 --- a/apps/web/src/app/(signing)/sign/[token]/text-field.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/text-field.tsx @@ -279,11 +279,13 @@ export const TextField = ({ field, recipient, onSignField, onUnsignField }: Text /> - {parsedFieldMeta?.characterLimit !== undefined && parsedFieldMeta?.characterLimit > 0 && !userInputHasErrors && ( -
- {charactersRemaining} characters remaining -
- )} + {parsedFieldMeta?.characterLimit !== undefined && + parsedFieldMeta?.characterLimit > 0 && + !userInputHasErrors && ( +
+ {charactersRemaining} characters remaining +
+ )} {userInputHasErrors && (
diff --git a/packages/ui/components/field/field.tsx b/packages/ui/components/field/field.tsx index d6553947c..ac67171de 100644 --- a/packages/ui/components/field/field.tsx +++ b/packages/ui/components/field/field.tsx @@ -84,7 +84,7 @@ export function FieldContainerPortal({ left: `${coords.x}px`, // height: `${coords.height}px`, // width: `${coords.width}px`, - ...((!isCheckboxOrRadioField) && { + ...(!isCheckboxOrRadioField && { height: `${coords.height}px`, width: `${coords.width}px`, }), diff --git a/packages/ui/primitives/document-flow/add-fields.tsx b/packages/ui/primitives/document-flow/add-fields.tsx index 597948fab..148cadc3c 100644 --- a/packages/ui/primitives/document-flow/add-fields.tsx +++ b/packages/ui/primitives/document-flow/add-fields.tsx @@ -19,6 +19,7 @@ import { User, } from 'lucide-react'; import { useFieldArray, useForm } from 'react-hook-form'; +import { useHotkeys } from 'react-hotkeys-hook'; import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect'; import { useDocumentElement } from '@documenso/lib/client-only/hooks/use-document-element'; @@ -40,6 +41,7 @@ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from ' import { Popover, PopoverContent, PopoverTrigger } from '../popover'; import { useStep } from '../stepper'; import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip'; +import { useToast } from '../use-toast'; import type { TAddFieldsFormSchema } from './add-fields.types'; import { DocumentFlowFormContainerActions, @@ -103,6 +105,8 @@ export const AddFieldsFormPartial = ({ isDocumentPdfLoaded, teamId, }: AddFieldsFormProps) => { + const { toast } = useToast(); + const [isMissingSignatureDialogVisible, setIsMissingSignatureDialogVisible] = useState(false); const { isWithinPageBounds, getFieldPosition, getPage } = useDocumentElement(); @@ -136,7 +140,12 @@ export const AddFieldsFormPartial = ({ }, }); + useHotkeys(['ctrl+c', 'meta+c'], (evt) => onFieldCopy(evt)); + useHotkeys(['ctrl+v', 'meta+v'], (evt) => onFieldPaste(evt)); + useHotkeys(['ctrl+d', 'meta+d'], (evt) => onFieldCopy(evt, { duplicate: true })); + const onFormSubmit = handleSubmit(onSubmit); + const handleSavedFieldSettings = (fieldState: FieldMeta) => { const initialValues = getValues(); @@ -169,6 +178,12 @@ export const AddFieldsFormPartial = ({ const [selectedField, setSelectedField] = useState(null); const [selectedSigner, setSelectedSigner] = useState(null); const [showRecipientsSelector, setShowRecipientsSelector] = useState(false); + const [lastActiveField, setLastActiveField] = useState( + null, + ); + const [fieldClipboard, setFieldClipboard] = useState( + null, + ); const selectedSignerIndex = recipients.findIndex((r) => r.id === selectedSigner?.id); const selectedSignerStyles = useSignerColors( selectedSignerIndex === -1 ? 0 : selectedSignerIndex, @@ -281,7 +296,7 @@ export const AddFieldsFormPartial = ({ pageX -= fieldPageWidth / 2; pageY -= fieldPageHeight / 2; - append({ + const field = { formId: nanoid(12), type: selectedField, pageNumber, @@ -291,7 +306,9 @@ export const AddFieldsFormPartial = ({ pageHeight: fieldPageHeight, signerEmail: selectedSigner.email, fieldMeta: undefined, - }); + }; + + append(field); setIsFieldWithinBounds(false); setSelectedField(null); @@ -352,6 +369,57 @@ export const AddFieldsFormPartial = ({ [getFieldPosition, localFields, update], ); + const onFieldCopy = useCallback( + (event?: KeyboardEvent | null, options?: { duplicate?: boolean }) => { + const { duplicate = false } = options ?? {}; + + if (lastActiveField) { + event?.preventDefault(); + + if (!duplicate) { + setFieldClipboard(lastActiveField); + + toast({ + title: 'Copied field', + description: 'Copied field to clipboard', + }); + + return; + } + + const newField: TAddFieldsFormSchema['fields'][0] = { + ...structuredClone(lastActiveField), + formId: nanoid(12), + signerEmail: selectedSigner?.email ?? lastActiveField.signerEmail, + pageX: lastActiveField.pageX + 3, + pageY: lastActiveField.pageY + 3, + }; + + append(newField); + } + }, + [append, lastActiveField, selectedSigner?.email, toast], + ); + + const onFieldPaste = useCallback( + (event: KeyboardEvent) => { + if (fieldClipboard) { + event.preventDefault(); + + const copiedField = structuredClone(fieldClipboard); + + append({ + ...copiedField, + formId: nanoid(12), + signerEmail: selectedSigner?.email ?? copiedField.signerEmail, + pageX: copiedField.pageX + 3, + pageY: copiedField.pageY + 3, + }); + } + }, + [append, fieldClipboard, selectedSigner?.email], + ); + useEffect(() => { if (selectedField) { window.addEventListener('mousemove', onMouseMove); @@ -464,6 +532,7 @@ export const AddFieldsFormPartial = ({ '-rotate-6 scale-90 opacity-50 dark:bg-black/20': !isFieldWithinBounds, 'dark:text-black/60': isFieldWithinBounds, }, + selectedField === FieldType.SIGNATURE && fontCaveat.className, )} style={{ top: coords.y, @@ -491,9 +560,12 @@ export const AddFieldsFormPartial = ({ minHeight={fieldBounds.current.height} minWidth={fieldBounds.current.width} passive={isFieldWithinBounds && !!selectedField} + onFocus={() => setLastActiveField(field)} + onBlur={() => setLastActiveField(null)} onResize={(options) => onFieldResize(options, index)} onMove={(options) => onFieldMove(options, index)} onRemove={() => remove(index)} + onDuplicate={() => onFieldCopy(null, { duplicate: true })} onAdvancedSettings={() => { setCurrentField(field); handleAdvancedSettings(); diff --git a/packages/ui/primitives/document-flow/advanced-fields/checkbox.tsx b/packages/ui/primitives/document-flow/advanced-fields/checkbox.tsx index 695cd1821..bd7e62c75 100644 --- a/packages/ui/primitives/document-flow/advanced-fields/checkbox.tsx +++ b/packages/ui/primitives/document-flow/advanced-fields/checkbox.tsx @@ -26,12 +26,12 @@ export const CheckboxField = ({ field }: CheckboxFieldProps) => { } return ( -
+
{!parsedFieldMeta?.values ? ( ) : ( parsedFieldMeta.values.map((item: { value: string; checked: boolean }, index: number) => ( -
+
{ } return ( -
+
{!parsedFieldMeta?.values ? ( ) : ( diff --git a/packages/ui/primitives/document-flow/field-item.tsx b/packages/ui/primitives/document-flow/field-item.tsx index 6e322d868..44788cabd 100644 --- a/packages/ui/primitives/document-flow/field-item.tsx +++ b/packages/ui/primitives/document-flow/field-item.tsx @@ -4,7 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Caveat } from 'next/font/google'; -import { Settings2, Trash } from 'lucide-react'; +import { CopyPlus, Settings2, Trash } from 'lucide-react'; import { createPortal } from 'react-dom'; import { Rnd } from 'react-rnd'; import { match } from 'ts-pattern'; @@ -38,7 +38,10 @@ export type FieldItemProps = { onResize?: (_node: HTMLElement) => void; onMove?: (_node: HTMLElement) => void; onRemove?: () => void; + onDuplicate?: () => void; onAdvancedSettings?: () => void; + onFocus?: () => void; + onBlur?: () => void; recipientIndex?: number; hideRecipients?: boolean; }; @@ -52,6 +55,9 @@ export const FieldItem = ({ onResize, onMove, onRemove, + onDuplicate, + onFocus, + onBlur, onAdvancedSettings, recipientIndex = 0, hideRecipients = false, @@ -115,18 +121,29 @@ export const FieldItem = ({ }; }, [calculateCoords]); - const handleClickOutsideField = (event: MouseEvent) => { - if (settingsActive && $el.current && !event.composedPath().includes($el.current)) { - setSettingsActive(false); - } - }; - useEffect(() => { - document.body.addEventListener('click', handleClickOutsideField); - return () => { - document.body.removeEventListener('click', handleClickOutsideField); + const onClickOutsideOfField = (event: MouseEvent) => { + const isOutsideOfField = $el.current && !event.composedPath().includes($el.current); + + setSettingsActive((active) => { + if (active && isOutsideOfField) { + return false; + } + + return active; + }); + + if (isOutsideOfField) { + onBlur?.(); + } }; - }, [settingsActive]); + + document.body.addEventListener('click', onClickOutsideOfField); + + return () => { + document.body.removeEventListener('click', onClickOutsideOfField); + }; + }, [onBlur]); const hasFieldMetaValues = ( fieldType: string, @@ -189,6 +206,7 @@ export const FieldItem = ({ )} onClick={() => { setSettingsActive((prev) => !prev); + onFocus?.(); }} ref={$el} > @@ -224,7 +242,7 @@ export const FieldItem = ({ {!disabled && settingsActive && (
-
+
{advancedField && ( )} + + + From 7a1341eb7448fce0d8bd41a3330c9880af09de37 Mon Sep 17 00:00:00 2001 From: Rene Steen Date: Tue, 20 Aug 2024 05:58:56 +0200 Subject: [PATCH 02/19] 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 03/19] 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 04/19] 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 05/19] 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 06/19] 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 ``` From 9223527b6fb055dea434fe50f22352b4b8b6bd95 Mon Sep 17 00:00:00 2001 From: Yigit Kerem Oktay Date: Wed, 21 Aug 2024 07:03:21 +0300 Subject: [PATCH 07/19] docs: fix documentation regarding the webhook secret header (#1278) --- apps/documentation/pages/developers/webhooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/documentation/pages/developers/webhooks.mdx b/apps/documentation/pages/developers/webhooks.mdx index 47cb9a9d6..024f4e493 100644 --- a/apps/documentation/pages/developers/webhooks.mdx +++ b/apps/documentation/pages/developers/webhooks.mdx @@ -37,7 +37,7 @@ To create a new webhook subscription, you need to provide the following informat - Enter the webhook URL that will receive the event payload. - Select the event(s) you want to subscribe to: `document.created`, `document.sent`, `document.opened`, `document.signed`, `document.completed`. -- Optionally, you can provide a secret key that will be used to sign the payload. This key will be included in the `X-Documenso-Signature` header of the request. +- Optionally, you can provide a secret key that will be used to sign the payload. This key will be included in the `X-Documenso-Secret` header of the request. ![A screenshot of the Create Webhook modal that shows the URL input field and the event checkboxes](/webhook-images/webhooks-page-create-webhook-modal.webp) From 082931121451af02b692d37d474e99902dde74b1 Mon Sep 17 00:00:00 2001 From: Catalin Pit <25515812+catalinpit@users.noreply.github.com> Date: Mon, 26 Aug 2024 12:37:56 +0300 Subject: [PATCH 08/19] feat: prefill fields via api (#1261) ## Description Configure the advanced field via API. ## Checklist - [x] I have tested these changes locally and they work as expected. - [ ] I have added/updated tests that prove the effectiveness of these changes. - [x] I have updated the documentation to reflect these changes, if applicable. - [x] I have followed the project's coding style guidelines. - [ ] I have addressed the code review feedback from the previous submission, if applicable. ## Summary by CodeRabbit - **New Features** - Enhanced API functionality to support field metadata during field creation. - Introduced validation checks for field metadata to ensure necessary information is provided for advanced field types. - **Bug Fixes** - Improved error handling during field creation to return properly formatted error responses. - **Documentation** - Updated API schemas to include field metadata, enhancing data validation and response structures. --- .gitignore | 2 + packages/api/v1/implementation.ts | 87 +++++++++++-------- packages/api/v1/schema.ts | 3 + .../lib/server-only/field/create-field.ts | 49 +++++++++++ 4 files changed, 106 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index 3b0569b15..b95fcc7d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +packages/prisma/generated/types.ts + # dependencies node_modules .pnp diff --git a/packages/api/v1/implementation.ts b/packages/api/v1/implementation.ts index 74afa80c0..ad9aaaac4 100644 --- a/packages/api/v1/implementation.ts +++ b/packages/api/v1/implementation.ts @@ -31,6 +31,7 @@ import { createDocumentFromTemplateLegacy } from '@documenso/lib/server-only/tem import { deleteTemplate } from '@documenso/lib/server-only/template/delete-template'; import { findTemplates } from '@documenso/lib/server-only/template/find-templates'; import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id'; +import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta'; import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata'; import { getFile } from '@documenso/lib/universal/upload/get-file'; import { putPdfFile } from '@documenso/lib/universal/upload/put-file'; @@ -869,7 +870,17 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { createField: authenticatedMiddleware(async (args, user, team) => { const { id: documentId } = args.params; - const { recipientId, type, pageNumber, pageWidth, pageHeight, pageX, pageY } = args.body; + const { recipientId, type, pageNumber, pageWidth, pageHeight, pageX, pageY, fieldMeta } = + args.body; + + if (pageNumber <= 0) { + return { + status: 400, + body: { + message: 'Invalid page number', + }, + }; + } const document = await getDocumentById({ id: Number(documentId), @@ -918,41 +929,47 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, { }; } - const field = await createField({ - documentId: Number(documentId), - recipientId: Number(recipientId), - userId: user.id, - teamId: team?.id, - type, - pageNumber, - pageX, - pageY, - pageWidth, - pageHeight, - requestMetadata: extractNextApiRequestMetadata(args.req), - }); - - const remappedField = { - id: field.id, - documentId: field.documentId, - recipientId: field.recipientId ?? -1, - type: field.type, - pageNumber: field.page, - pageX: Number(field.positionX), - pageY: Number(field.positionY), - pageWidth: Number(field.width), - pageHeight: Number(field.height), - customText: field.customText, - inserted: field.inserted, - }; - - return { - status: 200, - body: { - ...remappedField, + try { + const field = await createField({ documentId: Number(documentId), - }, - }; + recipientId: Number(recipientId), + userId: user.id, + teamId: team?.id, + type, + pageNumber, + pageX, + pageY, + pageWidth, + pageHeight, + fieldMeta, + requestMetadata: extractNextApiRequestMetadata(args.req), + }); + + const remappedField = { + id: field.id, + documentId: field.documentId, + recipientId: field.recipientId ?? -1, + type: field.type, + pageNumber: field.page, + pageX: Number(field.positionX), + pageY: Number(field.positionY), + pageWidth: Number(field.width), + pageHeight: Number(field.height), + customText: field.customText, + fieldMeta: ZFieldMetaSchema.parse(field.fieldMeta), + inserted: field.inserted, + }; + + return { + status: 200, + body: { + ...remappedField, + documentId: Number(documentId), + }, + }; + } catch (err) { + return AppError.toRestAPIError(err); + } }), updateField: authenticatedMiddleware(async (args, user, team) => { diff --git a/packages/api/v1/schema.ts b/packages/api/v1/schema.ts index 90d3f65bf..42205d540 100644 --- a/packages/api/v1/schema.ts +++ b/packages/api/v1/schema.ts @@ -5,6 +5,7 @@ import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/const 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 { ZFieldMetaSchema } from '@documenso/lib/types/field-meta'; import { DocumentDataType, FieldType, @@ -300,6 +301,7 @@ export const ZCreateFieldMutationSchema = z.object({ pageY: z.number(), pageWidth: z.number(), pageHeight: z.number(), + fieldMeta: ZFieldMetaSchema, }); export type TCreateFieldMutationSchema = z.infer; @@ -323,6 +325,7 @@ export const ZSuccessfulFieldResponseSchema = z.object({ pageWidth: z.number(), pageHeight: z.number(), customText: z.string(), + fieldMeta: ZFieldMetaSchema, inserted: z.boolean(), }); diff --git a/packages/lib/server-only/field/create-field.ts b/packages/lib/server-only/field/create-field.ts index 7a3aa3959..9aafc7ab8 100644 --- a/packages/lib/server-only/field/create-field.ts +++ b/packages/lib/server-only/field/create-field.ts @@ -1,6 +1,16 @@ +import { match } from 'ts-pattern'; + import { prisma } from '@documenso/prisma'; import type { FieldType, Team } from '@documenso/prisma/client'; +import { + ZCheckboxFieldMeta, + ZDropdownFieldMeta, + ZNumberFieldMeta, + ZRadioFieldMeta, + ZTextFieldMeta, +} from '../../types/field-meta'; +import type { TFieldMetaSchema as FieldMeta } from '../../types/field-meta'; import type { RequestMetadata } from '../../universal/extract-request-metadata'; import { createDocumentAuditLogData } from '../../utils/document-audit-logs'; @@ -15,6 +25,7 @@ export type CreateFieldOptions = { pageY: number; pageWidth: number; pageHeight: number; + fieldMeta?: FieldMeta; requestMetadata?: RequestMetadata; }; @@ -29,6 +40,7 @@ export const createField = async ({ pageY, pageWidth, pageHeight, + fieldMeta, requestMetadata, }: CreateFieldOptions) => { const document = await prisma.document.findFirst({ @@ -85,6 +97,42 @@ export const createField = async ({ }); } + const advancedField = ['NUMBER', 'RADIO', 'CHECKBOX', 'DROPDOWN', 'TEXT'].includes(type); + + if (advancedField && !fieldMeta) { + throw new Error( + 'Field meta is required for this type of field. Please provide the appropriate field meta object.', + ); + } + + if (fieldMeta && fieldMeta.type.toLowerCase() !== String(type).toLowerCase()) { + throw new Error('Field meta type does not match the field type'); + } + + const result = match(type) + .with('RADIO', () => { + return ZRadioFieldMeta.safeParse(fieldMeta); + }) + .with('CHECKBOX', () => { + return ZCheckboxFieldMeta.safeParse(fieldMeta); + }) + .with('DROPDOWN', () => { + return ZDropdownFieldMeta.safeParse(fieldMeta); + }) + .with('NUMBER', () => { + return ZNumberFieldMeta.safeParse(fieldMeta); + }) + .with('TEXT', () => { + return ZTextFieldMeta.safeParse(fieldMeta); + }) + .otherwise(() => { + return { success: false, data: {} }; + }); + + if (!result.success) { + throw new Error('Field meta parsing failed'); + } + const field = await prisma.field.create({ data: { documentId, @@ -97,6 +145,7 @@ export const createField = async ({ height: pageHeight, customText: '', inserted: false, + fieldMeta: advancedField ? result.data : undefined, }, include: { Recipient: true, From 75c8772a0236810e17fda16f26a67ffec594ec05 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Tue, 27 Aug 2024 20:34:39 +0900 Subject: [PATCH 09/19] feat: web i18n (#1286) --- apps/marketing/package.json | 1 - .../app/(marketing)/singleplayer/client.tsx | 10 +- apps/web/lingui.config.ts | 2 +- apps/web/package.json | 3 +- .../admin/documents/[id]/admin-actions.tsx | 24 +- .../(dashboard)/admin/documents/[id]/page.tsx | 20 +- .../admin/documents/[id]/recipient-item.tsx | 32 +- .../[id]/super-delete-document-dialog.tsx | 35 +- .../admin/documents/document-results.tsx | 14 +- .../app/(dashboard)/admin/documents/page.tsx | 10 +- apps/web/src/app/(dashboard)/admin/layout.tsx | 3 + apps/web/src/app/(dashboard)/admin/nav.tsx | 11 +- .../admin/site-settings/banner-form.tsx | 49 +- .../(dashboard)/admin/site-settings/page.tsx | 13 +- .../src/app/(dashboard)/admin/stats/page.tsx | 81 +- .../(dashboard)/admin/subscriptions/page.tsx | 25 +- .../admin/users/[id]/delete-user-dialog.tsx | 41 +- .../users/[id]/multiselect-role-combobox.tsx | 5 +- .../app/(dashboard)/admin/users/[id]/page.tsx | 30 +- .../admin/users/data-table-users.tsx | 16 +- .../src/app/(dashboard)/admin/users/page.tsx | 10 +- .../[id]/document-page-view-button.tsx | 19 +- .../[id]/document-page-view-dropdown.tsx | 27 +- .../[id]/document-page-view-information.tsx | 22 +- .../document-page-view-recent-activity.tsx | 21 +- .../[id]/document-page-view-recipients.tsx | 28 +- .../documents/[id]/document-page-view.tsx | 43 +- .../documents/[id]/edit-document.tsx | 40 +- .../[id]/edit/document-edit-page-view.tsx | 7 +- .../(dashboard)/documents/[id]/edit/page.tsx | 4 + .../(dashboard)/documents/[id]/loading.tsx | 12 +- .../[id]/logs/document-logs-data-table.tsx | 10 +- .../[id]/logs/document-logs-page-view.tsx | 27 +- .../[id]/logs/download-audit-log-button.tsx | 11 +- .../[id]/logs/download-certificate-button.tsx | 11 +- .../(dashboard)/documents/[id]/logs/page.tsx | 4 + .../app/(dashboard)/documents/[id]/page.tsx | 4 + .../(dashboard)/documents/[id]/sent/page.tsx | 9 +- .../_action-items/resend-document.tsx | 21 +- .../documents/data-table-action-button.tsx | 19 +- .../documents/data-table-action-dropdown.tsx | 33 +- .../documents/data-table-sender-filter.tsx | 11 +- .../app/(dashboard)/documents/data-table.tsx | 16 +- .../documents/delete-document-dialog.tsx | 76 +- .../documents/documents-page-view.tsx | 6 +- .../documents/duplicate-document-dialog.tsx | 23 +- .../app/(dashboard)/documents/empty-state.tsx | 28 +- .../documents/move-document-dialog.tsx | 32 +- .../src/app/(dashboard)/documents/page.tsx | 4 + .../upcoming-profile-claim-teaser.tsx | 11 +- .../(dashboard)/documents/upload-document.tsx | 32 +- apps/web/src/app/(dashboard)/layout.tsx | 3 + .../settings/billing/billing-plans.tsx | 22 +- .../billing/billing-portal-button.tsx | 18 +- .../app/(dashboard)/settings/billing/page.tsx | 19 +- .../src/app/(dashboard)/settings/layout.tsx | 10 +- .../profile/delete-account-dialog.tsx | 56 +- .../app/(dashboard)/settings/profile/page.tsx | 12 +- .../settings/public-profile/page.tsx | 3 + .../public-profile-page-view.tsx | 67 +- .../public-templates-data-table.tsx | 25 +- .../settings/security/activity/page.tsx | 13 +- .../user-security-activity-data-table.tsx | 12 +- .../(dashboard)/settings/security/page.tsx | 62 +- .../passkeys/create-passkey-dialog.tsx | 60 +- .../settings/security/passkeys/page.tsx | 13 +- .../user-passkeys-data-table-actions.tsx | 61 +- .../passkeys/user-passkeys-data-table.tsx | 12 +- .../teams/accept-team-invitation-button.tsx | 14 +- .../teams/decline-team-invitation-button.tsx | 14 +- .../app/(dashboard)/settings/teams/page.tsx | 9 +- .../settings/teams/team-email-usage.tsx | 59 +- .../settings/teams/team-invitations.tsx | 36 +- .../app/(dashboard)/settings/tokens/page.tsx | 52 +- .../settings/webhooks/[id]/page.tsx | 45 +- .../(dashboard)/settings/webhooks/page.tsx | 38 +- .../templates/[id]/edit-template.tsx | 33 +- .../app/(dashboard)/templates/[id]/page.tsx | 4 + .../template-direct-link-dialog-wrapper.tsx | 8 +- .../templates/[id]/template-page-view.tsx | 3 +- .../templates/data-table-action-dropdown.tsx | 11 +- .../templates/data-table-templates.tsx | 56 +- .../templates/delete-template-dialog.tsx | 26 +- .../templates/duplicate-template-dialog.tsx | 24 +- .../app/(dashboard)/templates/empty-state.tsx | 9 +- .../templates/move-template-dialog.tsx | 28 +- .../templates/new-template-dialog.tsx | 27 +- .../src/app/(dashboard)/templates/page.tsx | 4 + .../templates/template-direct-link-badge.tsx | 10 +- .../templates/template-direct-link-dialog.tsx | 143 +- .../templates/templates-page-view.tsx | 6 +- .../templates/use-template-dialog.tsx | 73 +- .../%5F%5Fhtmltopdf/audit-log/page.tsx | 7 +- .../%5F%5Fhtmltopdf/certificate/page.tsx | 13 +- apps/web/src/app/(profile)/layout.tsx | 3 + .../src/app/(profile)/p/[url]/not-found.tsx | 13 +- apps/web/src/app/(profile)/p/[url]/page.tsx | 34 +- apps/web/src/app/(profile)/profile-header.tsx | 13 +- .../d/[token]/configure-direct-template.tsx | 18 +- .../(recipient)/d/[token]/direct-template.tsx | 23 +- .../app/(recipient)/d/[token]/not-found.tsx | 17 +- .../src/app/(recipient)/d/[token]/page.tsx | 7 +- .../d/[token]/sign-direct-template.tsx | 22 +- .../d/[token]/signing-auth-page.tsx | 15 +- apps/web/src/app/(recipient)/layout.tsx | 3 + .../(signing)/sign/[token]/checkbox-field.tsx | 18 +- .../sign/[token]/complete/claim-account.tsx | 42 +- .../complete/document-preview-button.tsx | 3 +- .../sign/[token]/complete/layout.tsx | 4 + .../(signing)/sign/[token]/complete/page.tsx | 48 +- .../app/(signing)/sign/[token]/date-field.tsx | 17 +- .../sign/[token]/document-action-auth-2fa.tsx | 30 +- .../[token]/document-action-auth-account.tsx | 12 +- .../[token]/document-action-auth-dialog.tsx | 5 +- .../[token]/document-action-auth-passkey.tsx | 26 +- .../(signing)/sign/[token]/dropdown-field.tsx | 16 +- .../(signing)/sign/[token]/email-field.tsx | 13 +- .../src/app/(signing)/sign/[token]/form.tsx | 7 +- .../src/app/(signing)/sign/[token]/layout.tsx | 3 + .../app/(signing)/sign/[token]/name-field.tsx | 29 +- .../sign/[token]/no-longer-available.tsx | 31 +- .../(signing)/sign/[token]/number-field.tsx | 20 +- .../src/app/(signing)/sign/[token]/page.tsx | 3 + .../(signing)/sign/[token]/radio-field.tsx | 14 +- .../(signing)/sign/[token]/sign-dialog.tsx | 38 +- .../sign/[token]/signature-field.tsx | 29 +- .../sign/[token]/signing-auth-page.tsx | 17 +- .../sign/[token]/signing-field-container.tsx | 7 +- .../sign/[token]/signing-page-view.tsx | 16 +- .../app/(signing)/sign/[token]/text-field.tsx | 42 +- .../t/[teamUrl]/documents/[id]/edit/page.tsx | 3 + .../t/[teamUrl]/documents/[id]/logs/page.tsx | 3 + .../t/[teamUrl]/documents/[id]/page.tsx | 3 + .../(teams)/t/[teamUrl]/documents/page.tsx | 3 + .../web/src/app/(teams)/t/[teamUrl]/error.tsx | 27 +- .../t/[teamUrl]/layout-billing-banner.tsx | 34 +- .../src/app/(teams)/t/[teamUrl]/layout.tsx | 3 + .../src/app/(teams)/t/[teamUrl]/not-found.tsx | 15 +- .../t/[teamUrl]/settings/billing/page.tsx | 26 +- .../(teams)/t/[teamUrl]/settings/layout.tsx | 9 +- .../t/[teamUrl]/settings/members/page.tsx | 12 +- .../app/(teams)/t/[teamUrl]/settings/page.tsx | 44 +- .../settings/public-profile/page.tsx | 3 + .../settings/team-email-dropdown.tsx | 17 +- .../settings/team-transfer-status.tsx | 42 +- .../t/[teamUrl]/settings/tokens/page.tsx | 54 +- .../[teamUrl]/settings/webhooks/[id]/page.tsx | 38 +- .../t/[teamUrl]/settings/webhooks/page.tsx | 38 +- .../t/[teamUrl]/templates/[id]/page.tsx | 3 + .../(teams)/t/[teamUrl]/templates/page.tsx | 3 + .../articles/signature-disclosure/page.tsx | 9 +- .../(unauthenticated)/check-email/page.tsx | 19 +- .../forgot-password/page.tsx | 26 +- apps/web/src/app/(unauthenticated)/layout.tsx | 3 + .../reset-password/[token]/page.tsx | 23 +- .../(unauthenticated)/reset-password/page.tsx | 19 +- .../src/app/(unauthenticated)/signin/page.tsx | 20 +- .../src/app/(unauthenticated)/signup/page.tsx | 3 + .../team/decline/[token]/page.tsx | 44 +- .../team/invite/[token]/page.tsx | 46 +- .../team/verify/email/[token]/page.tsx | 40 +- .../team/verify/transfer/[token]/page.tsx | 42 +- .../unverified-account/page.tsx | 20 +- .../verify-email/[token]/page.tsx | 52 +- .../(unauthenticated)/verify-email/page.tsx | 16 +- apps/web/src/app/layout.tsx | 41 +- apps/web/src/app/not-found.tsx | 13 +- apps/web/src/app/page.tsx | 6 +- .../avatar/avatar-with-recipient.tsx | 17 +- .../avatar/stack-avatars-with-tooltip.tsx | 23 +- .../(dashboard)/common/command-menu.tsx | 56 +- .../(dashboard)/layout/desktop-nav.tsx | 12 +- .../(dashboard)/layout/menu-switcher.tsx | 38 +- .../(dashboard)/layout/mobile-navigation.tsx | 16 +- .../layout/verify-email-banner.tsx | 32 +- .../period-selector/period-selector.tsx | 18 +- .../settings/layout/activity-back.tsx | 4 +- .../settings/layout/desktop-nav.tsx | 15 +- .../settings/layout/mobile-nav.tsx | 15 +- .../settings/token/delete-token-dialog.tsx | 45 +- .../webhooks/create-webhook-dialog.tsx | 59 +- .../webhooks/delete-webhook-dialog.tsx | 45 +- .../webhooks/trigger-multiselect-combobox.tsx | 7 +- .../(teams)/dialogs/add-team-email-dialog.tsx | 36 +- .../dialogs/create-team-checkout-dialog.tsx | 30 +- .../(teams)/dialogs/create-team-dialog.tsx | 44 +- .../(teams)/dialogs/delete-team-dialog.tsx | 47 +- .../dialogs/delete-team-member-dialog.tsx | 35 +- .../dialogs/invite-team-member-dialog.tsx | 62 +- .../(teams)/dialogs/leave-team-dialog.tsx | 33 +- .../dialogs/remove-team-email-dialog.tsx | 41 +- .../(teams)/dialogs/transfer-team-dialog.tsx | 56 +- .../dialogs/update-team-email-dialog.tsx | 34 +- .../dialogs/update-team-member-dialog.tsx | 41 +- .../(teams)/forms/update-team-form.tsx | 36 +- .../(teams)/settings/layout/desktop-nav.tsx | 13 +- .../(teams)/settings/layout/mobile-nav.tsx | 13 +- .../tables/current-user-teams-data-table.tsx | 19 +- .../pending-user-teams-data-table-actions.tsx | 19 +- .../tables/pending-user-teams-data-table.tsx | 9 +- .../team-billing-invoices-data-table.tsx | 18 +- .../tables/team-member-invites-data-table.tsx | 37 +- .../tables/team-members-data-table.tsx | 24 +- .../tables/teams-member-page-data-table.tsx | 15 +- .../user-settings-teams-page-data-table.tsx | 13 +- .../(teams)/team-billing-portal-button.tsx | 13 +- .../document/document-history-sheet.tsx | 17 +- .../components/formatter/document-status.tsx | 25 +- .../components/formatter/template-type.tsx | 13 +- .../2fa/disable-authenticator-app-dialog.tsx | 33 +- .../2fa/enable-authenticator-app-dialog.tsx | 74 +- .../forms/2fa/recovery-code-list.tsx | 14 +- .../forms/2fa/view-recovery-codes-dialog.tsx | 44 +- .../web/src/components/forms/avatar-image.tsx | 33 +- .../src/components/forms/forgot-password.tsx | 19 +- apps/web/src/components/forms/password.tsx | 30 +- apps/web/src/components/forms/profile.tsx | 28 +- .../forms/public-profile-claim-dialog.tsx | 12 +- .../components/forms/public-profile-form.tsx | 57 +- .../src/components/forms/reset-password.tsx | 26 +- .../forms/send-confirmation-email.tsx | 21 +- apps/web/src/components/forms/signin.tsx | 80 +- apps/web/src/components/forms/signup.tsx | 64 +- apps/web/src/components/forms/token.tsx | 57 +- apps/web/src/components/forms/v2/signup.tsx | 115 +- apps/web/src/components/forms/webhook.tsx | 0 .../web/src/components/partials/not-found.tsx | 15 +- .../manage-public-template-dialog.tsx | 111 +- .../components/ui/user-profile-skeleton.tsx | 3 +- .../src/components/ui/user-profile-timur.tsx | 11 +- apps/web/src/middleware.ts | 16 +- package-lock.json | 4 +- package.json | 1 + .../e2e/templates/direct-templates.spec.ts | 9 +- .../template-document-invite.tsx | 4 +- .../document-created-from-direct-template.tsx | 4 +- packages/email/templates/document-invite.tsx | 4 +- .../lib/client-only/providers/i18n.server.tsx | 9 +- packages/lib/constants/app.ts | 1 + packages/lib/constants/direct-templates.ts | 3 + packages/lib/constants/recipient-roles.ts | 85 +- packages/lib/constants/teams.ts | 11 +- packages/lib/constants/template.ts | 25 +- packages/lib/constants/toast.ts | 13 - .../definitions/emails/send-signing-email.ts | 6 +- .../server-only/document/resend-document.tsx | 6 +- .../recipient/set-recipients-for-template.ts | 8 +- .../template/create-template-direct-link.ts | 8 +- packages/lib/translations/de/common.po | 726 +++ packages/lib/translations/de/marketing.js | 2 +- packages/lib/translations/de/marketing.po | 17 +- packages/lib/translations/de/web.js | 2 +- packages/lib/translations/de/web.po | 4358 ++++++++++++++++ packages/lib/translations/en/common.po | 727 +++ packages/lib/translations/en/marketing.js | 2 +- packages/lib/translations/en/marketing.po | 16 + packages/lib/translations/en/web.js | 2 +- packages/lib/translations/en/web.po | 4359 +++++++++++++++++ packages/lib/utils/document-audit-logs.ts | 5 +- packages/prisma/seed/templates.ts | 2 +- .../document/document-download-button.tsx | 12 +- .../document-global-auth-access-select.tsx | 61 +- .../document-global-auth-action-select.tsx | 80 +- .../document-send-email-message-helper.tsx | 10 +- .../document/document-share-button.tsx | 23 +- .../recipient-action-auth-select.tsx | 44 +- .../recipient/recipient-role-select.tsx | 33 +- packages/ui/package.json | 3 +- packages/ui/primitives/combobox.tsx | 10 +- .../ui/primitives/data-table-pagination.tsx | 21 +- packages/ui/primitives/data-table.tsx | 9 +- packages/ui/primitives/document-dropzone.tsx | 21 +- .../primitives/document-flow/add-fields.tsx | 62 +- .../primitives/document-flow/add-settings.tsx | 39 +- .../document-flow/add-signature.tsx | 25 +- .../primitives/document-flow/add-signers.tsx | 28 +- .../primitives/document-flow/add-subject.tsx | 17 +- .../document-flow/document-flow-root.tsx | 31 +- .../primitives/document-flow/field-icon.tsx | 3 +- .../field-item-advanced-settings.tsx | 17 +- .../checkbox-field.tsx | 26 +- .../dropdown-field.tsx | 24 +- .../number-field.tsx | 48 +- .../radio-field.tsx | 15 +- .../text-field.tsx | 37 +- .../missing-signature-field-dialog.tsx | 13 +- .../send-document-action-dialog.tsx | 17 +- packages/ui/primitives/document-flow/types.ts | 5 +- .../primitives/document-password-dialog.tsx | 20 +- .../ui/primitives/multi-select-combobox.tsx | 13 +- .../signature-pad/signature-pad.tsx | 3 +- .../template-flow/add-template-fields.tsx | 40 +- .../add-template-placeholder-recipients.tsx | 40 +- .../template-flow/add-template-settings.tsx | 52 +- 294 files changed, 14846 insertions(+), 2229 deletions(-) delete mode 100644 apps/web/src/components/forms/webhook.tsx create mode 100644 packages/lib/constants/direct-templates.ts delete mode 100644 packages/lib/constants/toast.ts diff --git a/apps/marketing/package.json b/apps/marketing/package.json index fb6478fca..a1c25d459 100644 --- a/apps/marketing/package.json +++ b/apps/marketing/package.json @@ -20,7 +20,6 @@ "@documenso/trpc": "*", "@documenso/ui": "*", "@hookform/resolvers": "^3.1.0", - "@lingui/macro": "^4.11.1", "@lingui/react": "^4.11.1", "@openstatus/react": "^0.0.3", "cmdk": "^0.2.1", diff --git a/apps/marketing/src/app/(marketing)/singleplayer/client.tsx b/apps/marketing/src/app/(marketing)/singleplayer/client.tsx index 2d8bbe9d9..a3df5af29 100644 --- a/apps/marketing/src/app/(marketing)/singleplayer/client.tsx +++ b/apps/marketing/src/app/(marketing)/singleplayer/client.tsx @@ -5,6 +5,8 @@ import { useEffect, useState } from 'react'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; +import { msg } from '@lingui/macro'; + import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics'; import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; import { base64 } from '@documenso/lib/universal/base64'; @@ -46,8 +48,8 @@ export const SinglePlayerClient = () => { const documentFlow: Record = { fields: { - title: 'Add document', - description: 'Upload a document and add fields.', + title: msg`Add document`, + description: msg`Upload a document and add fields.`, stepIndex: 1, onBackStep: uploadedFile ? () => { @@ -58,8 +60,8 @@ export const SinglePlayerClient = () => { onNextStep: () => setStep('sign'), }, sign: { - title: 'Sign', - description: 'Enter your details.', + title: msg`Sign`, + description: msg`Enter your details.`, stepIndex: 2, onBackStep: () => setStep('fields'), }, diff --git a/apps/web/lingui.config.ts b/apps/web/lingui.config.ts index f129b49ce..a5862eb98 100644 --- a/apps/web/lingui.config.ts +++ b/apps/web/lingui.config.ts @@ -8,7 +8,7 @@ const config: LinguiConfig = { locales: APP_I18N_OPTIONS.supportedLangs as unknown as string[], catalogs: [ { - path: '/../../packages/lib/translations/web/{locale}', + path: '/../../packages/lib/translations/{locale}/web', include: ['/apps/web/src'], }, { diff --git a/apps/web/package.json b/apps/web/package.json index f86e5769a..be73389c8 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -23,7 +23,6 @@ "@documenso/trpc": "*", "@documenso/ui": "*", "@hookform/resolvers": "^3.1.0", - "@lingui/macro": "^4.11.1", "@lingui/react": "^4.11.1", "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.3", @@ -60,9 +59,9 @@ "zod": "^3.22.4" }, "devDependencies": { + "@documenso/tailwind-config": "*", "@lingui/loader": "^4.11.1", "@lingui/swc-plugin": "4.0.6", - "@documenso/tailwind-config": "*", "@simplewebauthn/types": "^9.0.1", "@types/formidable": "^2.0.6", "@types/luxon": "^3.3.1", diff --git a/apps/web/src/app/(dashboard)/admin/documents/[id]/admin-actions.tsx b/apps/web/src/app/(dashboard)/admin/documents/[id]/admin-actions.tsx index f084b5db5..330f31eb1 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/admin-actions.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/[id]/admin-actions.tsx @@ -2,6 +2,9 @@ import Link from 'next/link'; +import { Trans, msg } from '@lingui/macro'; +import { useLingui } from '@lingui/react'; + import type { Recipient } from '@documenso/prisma/client'; import { type Document, SigningStatus } from '@documenso/prisma/client'; import { trpc } from '@documenso/trpc/react'; @@ -22,20 +25,21 @@ export type AdminActionsProps = { }; export const AdminActions = ({ className, document, recipients }: AdminActionsProps) => { + const { _ } = useLingui(); const { toast } = useToast(); const { mutate: resealDocument, isLoading: isResealDocumentLoading } = trpc.admin.resealDocument.useMutation({ onSuccess: () => { toast({ - title: 'Success', - description: 'Document resealed', + title: _(msg`Success`), + description: _(msg`Document resealed`), }); }, onError: () => { toast({ - title: 'Error', - description: 'Failed to reseal document', + title: _(msg`Error`), + description: _(msg`Failed to reseal document`), variant: 'destructive', }); }, @@ -54,19 +58,23 @@ export const AdminActions = ({ className, document, recipients }: AdminActionsPr )} onClick={() => resealDocument({ id: document.id })} > - Reseal document + Reseal document - Attempts sealing the document again, useful for after a code change has occurred to - resolve an erroneous document. + + Attempts sealing the document again, useful for after a code change has occurred to + resolve an erroneous document. +
    ); diff --git a/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx b/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx index 563db8d1b..48211fbc0 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx @@ -1,5 +1,7 @@ +import { Trans } from '@lingui/macro'; import { DateTime } from 'luxon'; +import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server'; import { getEntireDocument } from '@documenso/lib/server-only/admin/get-entire-document'; import { Accordion, @@ -23,6 +25,8 @@ type AdminDocumentDetailsPageProps = { }; export default async function AdminDocumentDetailsPage({ params }: AdminDocumentDetailsPageProps) { + setupI18nSSR(); + const document = await getEntireDocument({ id: Number(params.id) }); return ( @@ -35,28 +39,34 @@ export default async function AdminDocumentDetailsPage({ params }: AdminDocument {document.deletedAt && ( - Deleted + Deleted )}
    - Created on: + Created on:{' '} +
    - Last updated at: + Last updated at:{' '} +

    -

    Admin Actions

    +

    + Admin Actions +


    -

    Recipients

    +

    + Recipients +

    diff --git a/apps/web/src/app/(dashboard)/admin/documents/[id]/recipient-item.tsx b/apps/web/src/app/(dashboard)/admin/documents/[id]/recipient-item.tsx index 3bf8c78ab..92304a62e 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/recipient-item.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/[id]/recipient-item.tsx @@ -2,6 +2,8 @@ import { useRouter } from 'next/navigation'; +import { Trans, msg } from '@lingui/macro'; +import { useLingui } from '@lingui/react'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; @@ -43,7 +45,9 @@ export type RecipientItemProps = { }; export const RecipientItem = ({ recipient }: RecipientItemProps) => { + const { _ } = useLingui(); const { toast } = useToast(); + const router = useRouter(); const form = useForm({ @@ -64,14 +68,14 @@ export const RecipientItem = ({ recipient }: RecipientItemProps) => { }); toast({ - title: 'Recipient updated', - description: 'The recipient has been updated successfully', + title: _(msg`Recipient updated`), + description: _(msg`The recipient has been updated successfully`), }); router.refresh(); } catch (error) { toast({ - title: 'Failed to update recipient', + title: _(msg`Failed to update recipient`), description: error.message, variant: 'destructive', }); @@ -93,7 +97,9 @@ export const RecipientItem = ({ recipient }: RecipientItemProps) => { name="name" render={({ field }) => ( - Name + + Name + @@ -109,7 +115,9 @@ export const RecipientItem = ({ recipient }: RecipientItemProps) => { name="email" render={({ field }) => ( - Email + + Email + @@ -122,7 +130,7 @@ export const RecipientItem = ({ recipient }: RecipientItemProps) => {
    @@ -131,7 +139,9 @@ export const RecipientItem = ({ recipient }: RecipientItemProps) => {
    -

    Fields

    +

    + Fields +

    { cell: ({ row }) =>
    {row.original.id}
    , }, { - header: 'Type', + header: _(msg`Type`), accessorKey: 'type', cell: ({ row }) =>
    {row.original.type}
    , }, { - header: 'Inserted', + header: _(msg`Inserted`), accessorKey: 'inserted', cell: ({ row }) =>
    {row.original.inserted ? 'True' : 'False'}
    , }, { - header: 'Value', + header: _(msg`Value`), accessorKey: 'customText', cell: ({ row }) =>
    {row.original.customText}
    , }, { - header: 'Signature', + header: _(msg`Signature`), accessorKey: 'signature', cell: ({ row }) => (
    diff --git a/apps/web/src/app/(dashboard)/admin/documents/[id]/super-delete-document-dialog.tsx b/apps/web/src/app/(dashboard)/admin/documents/[id]/super-delete-document-dialog.tsx index 63ad88a3f..337796959 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/super-delete-document-dialog.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/[id]/super-delete-document-dialog.tsx @@ -4,6 +4,9 @@ import { useState } from 'react'; import { useRouter } from 'next/navigation'; +import { Trans, msg } from '@lingui/macro'; +import { useLingui } from '@lingui/react'; + import type { Document } from '@documenso/prisma/client'; import { TRPCClientError } from '@documenso/trpc/client'; import { trpc } from '@documenso/trpc/react'; @@ -26,7 +29,9 @@ export type SuperDeleteDocumentDialogProps = { }; export const SuperDeleteDocumentDialog = ({ document }: SuperDeleteDocumentDialogProps) => { + const { _ } = useLingui(); const { toast } = useToast(); + const router = useRouter(); const [reason, setReason] = useState(''); @@ -43,7 +48,7 @@ export const SuperDeleteDocumentDialog = ({ document }: SuperDeleteDocumentDialo await deleteDocument({ id: document.id, reason }); toast({ - title: 'Document deleted', + title: _(msg`Document deleted`), description: 'The Document has been deleted successfully.', duration: 5000, }); @@ -52,13 +57,13 @@ export const SuperDeleteDocumentDialog = ({ document }: SuperDeleteDocumentDialo } catch (err) { if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') { toast({ - title: 'An error occurred', + title: _(msg`An error occurred`), description: err.message, variant: 'destructive', }); } else { toast({ - title: 'An unknown error occurred', + title: _(msg`An unknown error occurred`), variant: 'destructive', description: err.message ?? @@ -76,31 +81,41 @@ export const SuperDeleteDocumentDialog = ({ document }: SuperDeleteDocumentDialo variant="neutral" >
    - Delete Document + + Delete Document + - Delete the document. This action is irreversible so proceed with caution. + + Delete the document. This action is irreversible so proceed with caution. +
    - + - Delete Document + + Delete Document + - This action is not reversible. Please be certain. + This action is not reversible. Please be certain.
    - To confirm, please enter the reason + + To confirm, please enter the reason + - {isDeletingDocument ? 'Deleting document...' : 'Delete Document'} + Delete document diff --git a/apps/web/src/app/(dashboard)/admin/documents/document-results.tsx b/apps/web/src/app/(dashboard)/admin/documents/document-results.tsx index b7e235981..3226ed2c7 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/document-results.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/document-results.tsx @@ -5,6 +5,8 @@ import { useState } from 'react'; import Link from 'next/link'; import { useSearchParams } from 'next/navigation'; +import { msg } from '@lingui/macro'; +import { useLingui } from '@lingui/react'; import { Loader } from 'lucide-react'; import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value'; @@ -23,6 +25,8 @@ import { LocaleDate } from '~/components/formatter/locale-date'; // export type AdminDocumentResultsProps = {}; export const AdminDocumentResults = () => { + const { _ } = useLingui(); + const searchParams = useSearchParams(); const updateSearchParams = useUpdateSearchParams(); @@ -56,7 +60,7 @@ export const AdminDocumentResults = () => {
    setTerm(e.target.value)} /> @@ -65,12 +69,12 @@ export const AdminDocumentResults = () => { , }, { - header: 'Title', + header: _(msg`Title`), accessorKey: 'title', cell: ({ row }) => { return ( @@ -84,12 +88,12 @@ export const AdminDocumentResults = () => { }, }, { - header: 'Status', + header: _(msg`Status`), accessorKey: 'status', cell: ({ row }) => , }, { - header: 'Owner', + header: _(msg`Owner`), accessorKey: 'owner', cell: ({ row }) => { const avatarFallbackText = row.original.User.name diff --git a/apps/web/src/app/(dashboard)/admin/documents/page.tsx b/apps/web/src/app/(dashboard)/admin/documents/page.tsx index 96e4dcef8..7f21bf5fa 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/page.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/page.tsx @@ -1,9 +1,17 @@ +import { Trans } from '@lingui/macro'; + +import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server'; + import { AdminDocumentResults } from './document-results'; export default function AdminDocumentsPage() { + setupI18nSSR(); + return (
    -

    Manage documents

    +

    + Manage documents +

    diff --git a/apps/web/src/app/(dashboard)/admin/layout.tsx b/apps/web/src/app/(dashboard)/admin/layout.tsx index 12330679d..c489c34a1 100644 --- a/apps/web/src/app/(dashboard)/admin/layout.tsx +++ b/apps/web/src/app/(dashboard)/admin/layout.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { redirect } from 'next/navigation'; +import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server'; import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session'; import { isAdmin } from '@documenso/lib/next-auth/guards/is-admin'; @@ -12,6 +13,8 @@ export type AdminSectionLayoutProps = { }; export default async function AdminSectionLayout({ children }: AdminSectionLayoutProps) { + setupI18nSSR(); + const { user } = await getRequiredServerComponentSession(); if (!isAdmin(user)) { diff --git a/apps/web/src/app/(dashboard)/admin/nav.tsx b/apps/web/src/app/(dashboard)/admin/nav.tsx index 080cb9741..cf0bb81f2 100644 --- a/apps/web/src/app/(dashboard)/admin/nav.tsx +++ b/apps/web/src/app/(dashboard)/admin/nav.tsx @@ -5,6 +5,7 @@ import type { HTMLAttributes } from 'react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; +import { Trans } from '@lingui/macro'; import { BarChart3, FileStack, Settings, Users, Wallet2 } from 'lucide-react'; import { cn } from '@documenso/ui/lib/utils'; @@ -33,7 +34,7 @@ export const AdminNav = ({ className, ...props }: AdminNavProps) => { > - Stats + Stats @@ -47,7 +48,7 @@ export const AdminNav = ({ className, ...props }: AdminNavProps) => { > - Users + Users @@ -61,7 +62,7 @@ export const AdminNav = ({ className, ...props }: AdminNavProps) => { > - Documents + Documents @@ -75,7 +76,7 @@ export const AdminNav = ({ className, ...props }: AdminNavProps) => { > - Subscriptions + Subscriptions @@ -89,7 +90,7 @@ export const AdminNav = ({ className, ...props }: AdminNavProps) => { > - Site Settings + Site Settings
    diff --git a/apps/web/src/app/(dashboard)/admin/site-settings/banner-form.tsx b/apps/web/src/app/(dashboard)/admin/site-settings/banner-form.tsx index 351e146ff..d68eed63b 100644 --- a/apps/web/src/app/(dashboard)/admin/site-settings/banner-form.tsx +++ b/apps/web/src/app/(dashboard)/admin/site-settings/banner-form.tsx @@ -3,6 +3,8 @@ import { useRouter } from 'next/navigation'; import { zodResolver } from '@hookform/resolvers/zod'; +import { Trans, msg } from '@lingui/macro'; +import { useLingui } from '@lingui/react'; import { useForm } from 'react-hook-form'; import type { z } from 'zod'; @@ -37,8 +39,10 @@ export type BannerFormProps = { }; export function BannerForm({ banner }: BannerFormProps) { - const router = useRouter(); const { toast } = useToast(); + const { _ } = useLingui(); + + const router = useRouter(); const form = useForm({ resolver: zodResolver(ZBannerFormSchema), @@ -67,8 +71,8 @@ export function BannerForm({ banner }: BannerFormProps) { }); toast({ - title: 'Banner Updated', - description: 'Your banner has been updated successfully.', + title: _(msg`Banner Updated`), + description: _(msg`Your banner has been updated successfully.`), duration: 5000, }); @@ -76,16 +80,17 @@ export function BannerForm({ banner }: BannerFormProps) { } catch (err) { if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') { toast({ - title: 'An error occurred', + title: _(msg`An error occurred`), description: err.message, variant: 'destructive', }); } else { toast({ - title: 'An unknown error occurred', + title: _(msg`An unknown error occurred`), variant: 'destructive', - description: - 'We encountered an unknown error while attempting to update the banner. Please try again later.', + description: _( + msg`We encountered an unknown error while attempting to update the banner. Please try again later.`, + ), }); } } @@ -93,10 +98,14 @@ export function BannerForm({ banner }: BannerFormProps) { return (
    -

    Site Banner

    +

    + Site Banner +

    - The site banner is a message that is shown at the top of the site. It can be used to display - important information to your users. + + The site banner is a message that is shown at the top of the site. It can be used to + display important information to your users. +

    @@ -110,7 +119,9 @@ export function BannerForm({ banner }: BannerFormProps) { name="enabled" render={({ field }) => ( - Enabled + + Enabled +
    @@ -131,7 +142,9 @@ export function BannerForm({ banner }: BannerFormProps) { name="data.bgColor" render={({ field }) => ( - Background Color + + Background Color +
    @@ -149,7 +162,9 @@ export function BannerForm({ banner }: BannerFormProps) { name="data.textColor" render={({ field }) => ( - Text Color + + Text Color +
    @@ -170,14 +185,16 @@ export function BannerForm({ banner }: BannerFormProps) { name="data.content" render={({ field }) => ( - Content + + Content +