Compare commits

...

49 Commits

Author SHA1 Message Date
Ephraim Atta-Duncan
5103477e7b fix: new get stats query 2024-12-11 00:40:42 +00:00
Ephraim Atta-Duncan
b19b57dbc9 fix: get stats query 2024-12-10 23:33:14 +00:00
Mythie
2b3ab9a3b7 fix: remove compiled translation files 2024-11-27 13:57:45 +11:00
Mythie
cac262fcea Merge branch 'main' into feat/delete-archive 2024-11-27 10:57:13 +11:00
Catalin Pit
e6d4005cd1 fix: document title truncation (#1467)
Truncates the document title across various instances where it previously hadn't been truncated.
2024-11-27 10:53:48 +11:00
Mythie
337bdb3553 v1.8.0-rc.1 2024-11-26 21:26:12 +11:00
Catalin Pit
ab654a63d8 chore: enable typed signature by default (#1436)
Enable typed signature by default and also add the option to set a typed
signature in the profile page.
2024-11-26 21:03:44 +11:00
Mythie
dcb7c2436f fix: update prettier and tailwind 2024-11-26 11:47:28 +11:00
Catalin Pit
fa33f83696 feat: download doc without signing certificate (#1477)
## Description

I added the option of downloading a document without the signing
certificate for teams. They can disable/enable the option in the
preferences tab.

The signing certificate can still be downloaded separately from the
`logs` page.
2024-11-25 15:47:26 +11:00
Lucas Smith
b15e1d6c47 feat: support whitelabelling in the embedding (#1491)
## Description

Adds support for customising the theme and CSS for the embedding
components which is restricted to platform customers and above.

Additionally adds proper support for the platform plan which will let us
update our stripe products.

<img width="1040" alt="image"
src="https://github.com/user-attachments/assets/f694cd1e-ac93-4dc0-9f78-92fa813f6404">
<img width="1015" alt="image"
src="https://github.com/user-attachments/assets/4209972a-b2bd-40c9-9049-0367382a4de5">
<img width="1065" alt="image"
src="https://github.com/user-attachments/assets/fdbaaaa5-a028-4b1d-a58a-ea6224e21abe">


## Related Issue

N/A

## Changes Made

- Added support for using CSS Vars and CSS within the embedding route
- Added a guard for platform and enterprise plans to activate the custom
css
- Added support for the platform plan

## Testing Performed
Yes
2024-11-25 15:47:00 +11:00
Ephraim Atta-Duncan
0cd7c25718 fix: avoid delete duplicate button 2024-11-22 11:26:36 +00:00
Ephraim Atta-Duncan
79eec5f451 chore: translations 2024-11-22 11:24:26 +00:00
Ephraim Atta-Duncan
7ca0975650 fix: build errrors 2024-11-22 11:22:25 +00:00
Ephraim Atta-Duncan
870b3fb3d7 fix: match translations with main 2024-11-22 11:18:32 +00:00
Ephraim Atta-Duncan
43ea76fae3 fix: merge conflicts 2024-11-22 11:12:49 +00:00
Ephraim Duncan
cd5adce7df fix: hardcode delete confirmation text to avoid translation mismatch (#1487) 2024-11-22 14:22:31 +07:00
Mythie
11e483f1c4 chore: update changelog 2024-11-21 13:10:31 +11:00
Mythie
2e2bc8382f v1.8.1-rc.0 2024-11-20 23:02:32 +11:00
Lucas Smith
1f3a9b578b chore: add translations (#1485)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

- **New Features**
- Enhanced German, Spanish, French, and Polish translations for various
document management and marketing phrases.

- **Improvements**
- Updated translations for clarity and accuracy across multiple
languages, including user notifications and document actions.
- Added new translation entries to improve user experience and
localization in the Documenso application.

- **Chores**
- Updated revision dates in translation files to reflect recent changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-11-20 23:00:59 +11:00
Lucas Smith
83e7a3c222 fix: improve field sizing (#1486)
## Description

Allows for smaller field sizing in addition to improving our styling
when displaying labels on smaller fields.

This is the minimum currently supported field size until we perform a
more extensive refactor of our current drag and drop system.

## Related Issue

Reported via support channels

## Changes Made

- Updated our minimum size constraints
- Attempted to add a general autosizing component for text and failed
- Updated styling in a bunch of places to use the css `clamp()` method
for dynamic sizing.
2024-11-20 22:49:30 +11:00
Lucas Smith
9ef8b1f0c3 feat: automatically sign fields in large documents (#1484)
## Description

Adds a dialog that will display when a certain field threshold is
reached asking the user if they would like to sign non-critical fields
such as name, date, initials, and email with information that is already
available.

This has not been added to direct templates since we would often not
have all the pre-requisite knowledge since users are mostly anonymous.
Additionally, this has not been added to the embedding view since it may
detract from the experience for some.

Will not prompt the user if there is action authentication on the
document.

See the below demo:


https://github.com/user-attachments/assets/71739b5c-1323-4da9-89fd-a1145c9714d5

## Related Issue

#1281 (Older PR relating to the feature)

## Changes Made

- Added a new auto-sign dialog that will automatically trigger once
certain criteria is met.

## Testing Performed

- Tested that the dialog displays when the threshold is met
- Tested that the dialog is hidden when the threshold is not met
- Tested that the messaging during errors is correct
- Tested that the dialog does not display when 2FA or Passkeys are
required
2024-11-20 10:59:09 +11:00
Mythie
0eff336175 v1.8.0-rc.4 2024-11-19 16:44:25 +11:00
Ephraim Duncan
9bdd5c31cc fix: sort recipients for template with signing order (#1468) 2024-11-18 15:54:51 +07:00
Lucas Smith
57ad7c150b chore: add translations (#1474) 2024-11-18 08:40:25 +11:00
Mythie
b0829e6cdf v1.8.0-rc.3 2024-11-16 09:23:05 +11:00
Lucas Smith
08a446fefd feat: support windows for 2fa tokens (#1478)
## Description

When using 2fa enabled authentication on direct templates we run into an
issue where a 2fa token has been attached to a field but it's submitted
at a later point.

To better facilitate this we have introduced the ability to have a
window of valid tokens.

This won't affect other signing methods since tokens are verified
immediately after they're entered.

## Related Issue

N/A

## Changes Made

- Updated our validate2FAToken method to use a window based approach
rather than the default verify method.

## Testing Performed

- Created a series of tokens and tested upon different intervals and
windows to confirm functionality works as expected.
2024-11-16 09:17:45 +11:00
David Nguyen
f15f9ecdd1 chore: update docs 2024-11-15 21:47:22 +07:00
David Nguyen
979e3f3e71 fix: always allow access to billing (#1476) 2024-11-15 21:34:39 +07:00
Mythie
876803b5db fix: handle team invites being accepted but not added 2024-11-15 13:27:36 +11:00
Ephraim Atta-Duncan
2dd122aed3 fix: merge conflicts
againnnn
2024-10-16 15:41:41 +00:00
Ephraim Atta-Duncan
d3872e86f1 fix: merge conflicts 2024-10-16 15:39:59 +00:00
Ephraim Atta-Duncan
171398ae2d fix: merge conflicts 2024-09-21 09:07:16 +00:00
Ephraim Atta-Duncan
492350612e fix: wrong count on user 2024-09-21 08:19:24 +00:00
Ephraim Atta-Duncan
f9935adb57 fix: incorrect counts and query for teams 2024-09-20 06:28:08 +00:00
Mythie
d2b99303f9 fix: simplify deleted query 2024-08-21 13:13:03 +10:00
github-actions
d33bbe71e7 chore: extract translations 2024-08-21 01:44:57 +00:00
Lucas Smith
6bf62e0ecb Merge branch 'main' into feat/delete-archive 2024-08-21 11:44:05 +10:00
Ephraim Atta-Duncan
16527f01e7 fix: build errors 2024-07-29 15:18:54 +10:00
Ephraim Atta-Duncan
7f25508c3c chore: remove unused email template 2024-07-29 15:18:54 +10:00
Ephraim Atta-Duncan
754e9e6428 chore: refactor find documents 2024-07-29 15:18:54 +10:00
Ephraim Atta-Duncan
2837b178fb fix: soft delete a document when the owner deletes it 2024-07-29 15:18:54 +10:00
Ephraim Atta-Duncan
26ccdc1b23 fix: filter on recipients 2024-07-29 15:18:54 +10:00
Ephraim Atta-Duncan
ea63b45a13 fix: add recipients filter for bin 2024-07-29 15:18:54 +10:00
Ephraim Atta-Duncan
feef4b1a12 feat: restore deleted document 2024-07-29 15:18:52 +10:00
Ephraim Atta-Duncan
1a55f4253b fix: tab count for teams 2024-07-29 14:41:02 +10:00
Ephraim Atta-Duncan
8311e0cc29 fix: show correct tab count 2024-07-29 14:41:02 +10:00
Ephraim Atta-Duncan
a9adc36732 fix: date filter for deleted documents 2024-07-29 14:41:02 +10:00
Ephraim Atta-Duncan
73e375938c fix: show deleted counts on tab 2024-07-29 14:41:02 +10:00
Ephraim Atta-Duncan
c6393b7a9e feat: add bin tab to show soft deleted documents 2024-07-29 14:41:02 +10:00
139 changed files with 5191 additions and 2900 deletions

View File

@@ -27,9 +27,6 @@
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18", "@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
"postcss": "^8",
"tailwindcss": "^3.3.0",
"typescript": "^5" "typescript": "^5"
} }
} }

View File

@@ -8,6 +8,59 @@ Check out what's new in the latest version and read our thoughts on it. For more
--- ---
# Documenso v1.8.0: Team Preferences, Signature Rejection, and Document Distribution
We're excited to announce the release of Documenso v1.8.0! This update brings powerful new features to enhance your document signing process. Here's what's new:
## 🌟 Key New Features
### 1. Team Preferences
Introducing **Team Preferences**, allowing administrators to configure settings and preferences that apply to documents across the entire team. This feature ensures consistency and simplifies management by letting you set default options, permissions, and preferences that automatically apply to all team members.
![Team Preferences](/changelog/v1_8_0/team-global-settings.jpeg)
### 2. Signature Rejection
Recipients now have the option to **reject signatures**. This feature enhances communication by allowing recipients to decline signing, providing feedback or requesting changes before the document is finalized.
<video
src="/changelog/v1_8_0/reject-document.mp4"
className="aspect-video w-full"
autoPlay
loop
controls
/>
### 3. Document Distribution Settings
With the new **Document Distribution Settings**, you have greater control over how your documents are shared. Distribute communications via our automated emails and templates or take full control using our API and your own notifications infrastructure.
## 🔧 Other Improvements
- **Support for Gmail SMTP Service**: Adds support for using Gmail as your SMTP service provider.
- **Certificate and Email Translations**: Added support for multiple languages in document certificates and emails, enhancing the experience for international users.
- **Field Movement Fixes**: Resolved issues related to moving fields within documents, improving the document preparation experience.
- **Docker Environment Update**: Improved Docker setup for smoother deployments and better environment consistency.
- **Billing Access Improvements**: Users now have uninterrupted access to billing information, simplifying account management.
- **Support Time Windows for 2FA Tokens**: Enhanced two-factor authentication by supporting time windows in 2FA tokens, improving flexibility.
## 💡 Recent Features
Don't forget to take advantage of these powerful features from our recent releases:
- **Signing Order**: Define the sequence in which recipients sign your documents for a structured signing process.
- **Document Visibility Controls**: Manage who can view your documents and at what stages, offering greater privacy and control.
- **Embedded Signing Experience**: Integrate the signing process directly into your own applications for a seamless user experience.
**👏 Thank You**
As always, we're grateful for the community's contributions and feedback. Your support helps us improve Documenso and deliver a top-notch open-source document signing solution.
We hope you enjoy the new features in Documenso v1.8.0. Happy signing!
---
# Documenso v1.7.1: Signing order and document visibility # Documenso v1.7.1: Signing order and document visibility
We're excited to introduce Documenso v1.7.1, bringing you improved control over your document signing process. Here are the key updates: We're excited to introduce Documenso v1.7.1, bringing you improved control over your document signing process. Here are the key updates:

View File

@@ -1,6 +1,6 @@
{ {
"name": "@documenso/marketing", "name": "@documenso/marketing",
"version": "1.8.0-rc.2", "version": "1.8.1-rc.1",
"private": true, "private": true,
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {

View File

@@ -2,8 +2,8 @@ declare namespace NodeJS {
export interface ProcessEnv { export interface ProcessEnv {
NEXT_PUBLIC_WEBAPP_URL?: string; NEXT_PUBLIC_WEBAPP_URL?: string;
NEXT_PUBLIC_MARKETING_URL?: string; NEXT_PUBLIC_MARKETING_URL?: string;
NEXT_PRIVATE_INTERNAL_WEBAPP_URL?:string; NEXT_PRIVATE_INTERNAL_WEBAPP_URL?: string;
NEXT_PRIVATE_DATABASE_URL: string; NEXT_PRIVATE_DATABASE_URL: string;
NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID: string; NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID: string;

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

View File

@@ -1,6 +1,6 @@
{ {
"name": "@documenso/web", "name": "@documenso/web",
"version": "1.8.0-rc.2", "version": "1.8.1-rc.1",
"private": true, "private": true,
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {
@@ -28,6 +28,7 @@
"@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/browser": "^9.0.1",
"@simplewebauthn/server": "^9.0.3", "@simplewebauthn/server": "^9.0.3",
"@tanstack/react-query": "^4.29.5", "@tanstack/react-query": "^4.29.5",
"colord": "^2.9.3",
"cookie-es": "^1.0.0", "cookie-es": "^1.0.0",
"formidable": "^2.1.1", "formidable": "^2.1.1",
"framer-motion": "^10.12.8", "framer-motion": "^10.12.8",
@@ -53,7 +54,7 @@
"react-icons": "^4.11.0", "react-icons": "^4.11.0",
"react-rnd": "^10.4.1", "react-rnd": "^10.4.1",
"recharts": "^2.7.2", "recharts": "^2.7.2",
"remeda": "^2.12.1", "remeda": "^2.17.3",
"sharp": "0.32.6", "sharp": "0.32.6",
"ts-pattern": "^5.0.5", "ts-pattern": "^5.0.5",
"ua-parser-js": "^1.0.37", "ua-parser-js": "^1.0.37",

View File

@@ -2,7 +2,7 @@ declare namespace NodeJS {
export interface ProcessEnv { export interface ProcessEnv {
NEXT_PUBLIC_WEBAPP_URL?: string; NEXT_PUBLIC_WEBAPP_URL?: string;
NEXT_PUBLIC_MARKETING_URL?: string; NEXT_PUBLIC_MARKETING_URL?: string;
NEXT_PRIVATE_INTERNAL_WEBAPP_URL?:string; NEXT_PRIVATE_INTERNAL_WEBAPP_URL?: string;
NEXT_PRIVATE_DATABASE_URL: string; NEXT_PRIVATE_DATABASE_URL: string;

View File

@@ -26,7 +26,7 @@ export const DocumentPageViewInformation = ({
const { _, i18n } = useLingui(); const { _, i18n } = useLingui();
const documentInformation = useMemo(() => { const documentInformation = useMemo(() => {
return [ const info = [
{ {
description: msg`Uploaded by`, description: msg`Uploaded by`,
value: userId === document.userId ? _(msg`You`) : document.User.name ?? document.User.email, value: userId === document.userId ? _(msg`You`) : document.User.name ?? document.User.email,
@@ -44,8 +44,20 @@ export const DocumentPageViewInformation = ({
.toRelative(), .toRelative(),
}, },
]; ];
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isMounted, document, userId]); if (document.deletedAt) {
info.push({
description: msg`Deleted`,
value:
document.deletedAt &&
DateTime.fromJSDate(document.deletedAt)
.setLocale(i18n.locales?.[0] || i18n.locale)
.toFormat('MMMM d, yyyy'),
});
}
return info;
}, [isMounted, document, i18n.locales?.[0] || i18n.locale, userId]);
return ( return (
<section className="dark:bg-background text-foreground border-border bg-widget flex flex-col rounded-xl border"> <section className="dark:bg-background text-foreground border-border bg-widget flex flex-col rounded-xl border">

View File

@@ -146,7 +146,10 @@ export const DocumentPageView = async ({ params, team }: DocumentPageViewProps)
<div className="flex flex-row justify-between truncate"> <div className="flex flex-row justify-between truncate">
<div> <div>
<h1 className="mt-4 truncate text-2xl font-semibold md:text-3xl" title={document.title}> <h1
className="mt-4 block max-w-[20rem] truncate text-2xl font-semibold md:max-w-[30rem] md:text-3xl"
title={document.title}
>
{document.title} {document.title}
</h1> </h1>
@@ -218,7 +221,7 @@ export const DocumentPageView = async ({ params, team }: DocumentPageViewProps)
<DocumentPageViewDropdown document={documentWithRecipients} team={team} /> <DocumentPageViewDropdown document={documentWithRecipients} team={team} />
</div> </div>
<p className="text-muted-foreground mt-2 px-4 text-sm "> <p className="text-muted-foreground mt-2 px-4 text-sm">
{match(document.status) {match(document.status)
.with(DocumentStatus.COMPLETED, () => ( .with(DocumentStatus.COMPLETED, () => (
<Trans>This document has been signed by all recipients</Trans> <Trans>This document has been signed by all recipients</Trans>

View File

@@ -109,7 +109,10 @@ export const DocumentEditPageView = async ({ params, team }: DocumentEditPageVie
<Trans>Documents</Trans> <Trans>Documents</Trans>
</Link> </Link>
<h1 className="mt-4 truncate text-2xl font-semibold md:text-3xl" title={document.title}> <h1
className="mt-4 block max-w-[20rem] truncate text-2xl font-semibold md:max-w-[30rem] md:text-3xl"
title={document.title}
>
{document.title} {document.title}
</h1> </h1>

View File

@@ -121,7 +121,10 @@ export const DocumentLogsPageView = async ({ params, team }: DocumentLogsPageVie
<div className="flex flex-col justify-between truncate sm:flex-row"> <div className="flex flex-col justify-between truncate sm:flex-row">
<div> <div>
<h1 className="mt-4 truncate text-2xl font-semibold md:text-3xl" title={document.title}> <h1
className="mt-4 block max-w-[20rem] truncate text-2xl font-semibold md:max-w-[30rem] md:text-3xl"
title={document.title}
>
{document.title} {document.title}
</h1> </h1>

View File

@@ -7,6 +7,7 @@ import Link from 'next/link';
import { Trans, msg } from '@lingui/macro'; import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react'; import { useLingui } from '@lingui/react';
import { import {
ArchiveRestore,
CheckCircle, CheckCircle,
Copy, Copy,
Download, Download,
@@ -23,8 +24,8 @@ import { useSession } from 'next-auth/react';
import { downloadPDF } from '@documenso/lib/client-only/download-pdf'; import { downloadPDF } from '@documenso/lib/client-only/download-pdf';
import { formatDocumentsPath } from '@documenso/lib/utils/teams'; import { formatDocumentsPath } from '@documenso/lib/utils/teams';
import { DocumentStatus, RecipientRole } from '@documenso/prisma/client';
import type { Document, Recipient, Team, User } from '@documenso/prisma/client'; import type { Document, Recipient, Team, User } from '@documenso/prisma/client';
import { DocumentStatus, RecipientRole } from '@documenso/prisma/client';
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data'; import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
import { trpc as trpcClient } from '@documenso/trpc/client'; import { trpc as trpcClient } from '@documenso/trpc/client';
import { DocumentShareButton } from '@documenso/ui/components/document/document-share-button'; import { DocumentShareButton } from '@documenso/ui/components/document/document-share-button';
@@ -43,6 +44,7 @@ import { ResendDocumentActionItem } from './_action-items/resend-document';
import { DeleteDocumentDialog } from './delete-document-dialog'; import { DeleteDocumentDialog } from './delete-document-dialog';
import { DuplicateDocumentDialog } from './duplicate-document-dialog'; import { DuplicateDocumentDialog } from './duplicate-document-dialog';
import { MoveDocumentDialog } from './move-document-dialog'; import { MoveDocumentDialog } from './move-document-dialog';
import { RestoreDocumentDialog } from './restore-document-dialog';
export type DataTableActionDropdownProps = { export type DataTableActionDropdownProps = {
row: Document & { row: Document & {
@@ -61,6 +63,7 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
const [isDeleteDialogOpen, setDeleteDialogOpen] = useState(false); const [isDeleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [isDuplicateDialogOpen, setDuplicateDialogOpen] = useState(false); const [isDuplicateDialogOpen, setDuplicateDialogOpen] = useState(false);
const [isMoveDialogOpen, setMoveDialogOpen] = useState(false); const [isMoveDialogOpen, setMoveDialogOpen] = useState(false);
const [isRestoreDialogOpen, setRestoreDialogOpen] = useState(false);
if (!session) { if (!session) {
return null; return null;
@@ -76,6 +79,7 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
// const isSigned = recipient?.signingStatus === SigningStatus.SIGNED; // const isSigned = recipient?.signingStatus === SigningStatus.SIGNED;
const isCurrentTeamDocument = team && row.team?.url === team.url; const isCurrentTeamDocument = team && row.team?.url === team.url;
const canManageDocument = Boolean(isOwner || isCurrentTeamDocument); const canManageDocument = Boolean(isOwner || isCurrentTeamDocument);
const isDeletedDocument = row.deletedAt !== null;
const documentsPath = formatDocumentsPath(team?.url); const documentsPath = formatDocumentsPath(team?.url);
@@ -181,13 +185,23 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
Void Void
</DropdownMenuItem> */} </DropdownMenuItem> */}
<DropdownMenuItem {isDeletedDocument ? (
onClick={() => setDeleteDialogOpen(true)} <DropdownMenuItem
disabled={Boolean(!canManageDocument && team?.teamEmail)} onClick={() => setRestoreDialogOpen(true)}
> disabled={Boolean(!canManageDocument)}
<Trash2 className="mr-2 h-4 w-4" /> >
{canManageDocument ? _(msg`Delete`) : _(msg`Hide`)} <ArchiveRestore className="mr-2 h-4 w-4" />
</DropdownMenuItem> Restore
</DropdownMenuItem>
) : (
<DropdownMenuItem
onClick={() => setDeleteDialogOpen(true)}
disabled={Boolean(!canManageDocument && team?.teamEmail)}
>
<Trash2 className="mr-2 h-4 w-4" />
{canManageDocument ? 'Delete' : 'Hide'}
</DropdownMenuItem>
)}
<DropdownMenuLabel> <DropdownMenuLabel>
<Trans>Share</Trans> <Trans>Share</Trans>
@@ -239,6 +253,16 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
onOpenChange={setMoveDialogOpen} onOpenChange={setMoveDialogOpen}
/> />
<RestoreDocumentDialog
id={row.id}
status={row.status}
documentTitle={row.title}
open={isRestoreDialogOpen}
onOpenChange={setRestoreDialogOpen}
teamId={team?.id}
canManageDocument={canManageDocument}
/>
{isDuplicateDialogOpen && ( {isDuplicateDialogOpen && (
<DuplicateDocumentDialog <DuplicateDocumentDialog
id={row.id} id={row.id}

View File

@@ -47,6 +47,8 @@ export const DeleteDocumentDialog = ({
const { refreshLimits } = useLimits(); const { refreshLimits } = useLimits();
const { _ } = useLingui(); const { _ } = useLingui();
const deleteMessage = msg`delete`;
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const [isDeleteEnabled, setIsDeleteEnabled] = useState(status === DocumentStatus.DRAFT); const [isDeleteEnabled, setIsDeleteEnabled] = useState(status === DocumentStatus.DRAFT);
@@ -87,7 +89,7 @@ export const DeleteDocumentDialog = ({
const onInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { const onInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(event.target.value); setInputValue(event.target.value);
setIsDeleteEnabled(event.target.value === _(msg`delete`)); setIsDeleteEnabled(event.target.value === _(deleteMessage));
}; };
return ( return (
@@ -181,7 +183,7 @@ export const DeleteDocumentDialog = ({
type="text" type="text"
value={inputValue} value={inputValue}
onChange={onInputChange} onChange={onInputChange}
placeholder={_(msg`Type 'delete' to confirm`)} placeholder={_(msg`Please type ${`'${_(deleteMessage)}'`} to confirm`)}
/> />
)} )}

View File

@@ -4,10 +4,10 @@ import { Trans } from '@lingui/macro';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session'; import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
import type { PeriodSelectorValue } from '@documenso/lib/server-only/document/find-documents'; import type { PeriodSelectorValue } from '@documenso/lib/server-only/document/find-documents';
import type { GetStatsInput } from '@documenso/lib/server-only/document/get-stats'; import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
import { getStats } from '@documenso/lib/server-only/document/get-stats'; import type { GetStatsInput } from '@documenso/lib/server-only/document/get-stats-new';
import { getStats } from '@documenso/lib/server-only/document/get-stats-new';
import { parseToIntegerArray } from '@documenso/lib/utils/params'; import { parseToIntegerArray } from '@documenso/lib/utils/params';
import { formatDocumentsPath } from '@documenso/lib/utils/teams'; import { formatDocumentsPath } from '@documenso/lib/utils/teams';
import type { Team, TeamEmail, TeamMemberRole } from '@documenso/prisma/client'; import type { Team, TeamEmail, TeamMemberRole } from '@documenso/prisma/client';
@@ -35,7 +35,7 @@ export interface DocumentsPageViewProps {
senderIds?: string; senderIds?: string;
search?: string; search?: string;
}; };
team?: Team & { teamEmail?: TeamEmail | null } & { currentTeamMember?: { role: TeamMemberRole } }; team?: Team & { teamEmail: TeamEmail | null } & { currentTeamMember?: { role: TeamMemberRole } };
} }
export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPageViewProps) => { export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPageViewProps) => {
@@ -50,25 +50,14 @@ export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPa
const currentTeam = team const currentTeam = team
? { id: team.id, url: team.url, teamEmail: team.teamEmail?.email } ? { id: team.id, url: team.url, teamEmail: team.teamEmail?.email }
: undefined; : undefined;
const currentTeamMemberRole = team?.currentTeamMember?.role;
const getStatOptions: GetStatsInput = { const getStatOptions: GetStatsInput = {
user, user,
period, period,
team,
search, search,
}; };
if (team) {
getStatOptions.team = {
teamId: team.id,
teamEmail: team.teamEmail?.email,
senderIds,
currentTeamMemberRole,
currentUserEmail: user.email,
userId: user.id,
};
}
const stats = await getStats(getStatOptions); const stats = await getStats(getStatOptions);
const results = await findDocuments({ const results = await findDocuments({
@@ -128,6 +117,7 @@ export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPa
ExtendedDocumentStatus.PENDING, ExtendedDocumentStatus.PENDING,
ExtendedDocumentStatus.COMPLETED, ExtendedDocumentStatus.COMPLETED,
ExtendedDocumentStatus.DRAFT, ExtendedDocumentStatus.DRAFT,
ExtendedDocumentStatus.BIN,
ExtendedDocumentStatus.ALL, ExtendedDocumentStatus.ALL,
].map((value) => ( ].map((value) => (
<TabsTrigger <TabsTrigger

View File

@@ -1,6 +1,6 @@
import { msg } from '@lingui/macro'; import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react'; import { useLingui } from '@lingui/react';
import { Bird, CheckCircle2 } from 'lucide-react'; import { Bird, CheckCircle2, Trash } from 'lucide-react';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status'; import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
@@ -30,6 +30,11 @@ export const EmptyDocumentState = ({ status }: EmptyDocumentProps) => {
message: msg`You have not yet created or received any documents. To create a document please upload one.`, message: msg`You have not yet created or received any documents. To create a document please upload one.`,
icon: Bird, icon: Bird,
})) }))
.with(ExtendedDocumentStatus.BIN, () => ({
title: msg`No documents in the bin`,
message: msg`There are no documents in the bin.`,
icon: Trash,
}))
.otherwise(() => ({ .otherwise(() => ({
title: msg`Nothing to do`, title: msg`Nothing to do`,
message: msg`All documents have been processed. Any new documents that are sent or received will show here.`, message: msg`All documents have been processed. Any new documents that are sent or received will show here.`,
@@ -42,7 +47,6 @@ export const EmptyDocumentState = ({ status }: EmptyDocumentProps) => {
data-testid="empty-document-state" data-testid="empty-document-state"
> >
<Icon className="h-12 w-12" strokeWidth={1.5} /> <Icon className="h-12 w-12" strokeWidth={1.5} />
<div className="text-center"> <div className="text-center">
<h3 className="text-lg font-semibold">{_(title)}</h3> <h3 className="text-lg font-semibold">{_(title)}</h3>

View File

@@ -0,0 +1,90 @@
import { useRouter } from 'next/navigation';
import type { DocumentStatus } from '@documenso/prisma/client';
import { trpc as trpcReact } from '@documenso/trpc/react';
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@documenso/ui/primitives/alert-dialog';
import { Button } from '@documenso/ui/primitives/button';
import { useToast } from '@documenso/ui/primitives/use-toast';
type RestoreDocumentDialogProps = {
id: number;
open: boolean;
onOpenChange: (_open: boolean) => void;
status: DocumentStatus;
documentTitle: string;
teamId?: number;
canManageDocument: boolean;
};
export function RestoreDocumentDialog({
id,
teamId,
open,
onOpenChange,
documentTitle,
canManageDocument,
}: RestoreDocumentDialogProps) {
const router = useRouter();
const { toast } = useToast();
const { mutateAsync: restoreDocument, isLoading } =
trpcReact.document.restoreDocument.useMutation({
onSuccess: () => {
router.refresh();
toast({
title: 'Document restored',
description: `"${documentTitle}" has been successfully restored`,
duration: 5000,
});
onOpenChange(false);
},
});
const onRestore = async () => {
try {
await restoreDocument({ id, teamId });
} catch {
toast({
title: 'Something went wrong',
description: 'This document could not be restored at this time. Please try again.',
variant: 'destructive',
duration: 7500,
});
}
};
return (
<AlertDialog open={open} onOpenChange={(value) => !isLoading && onOpenChange(value)}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
You are about to restore the document <strong>"{documentTitle}"</strong>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<Button
type="button"
loading={isLoading}
onClick={onRestore}
disabled={!canManageDocument}
>
Restore
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}

View File

@@ -12,9 +12,10 @@ import { createBillingPortal } from './create-billing-portal.action';
export type BillingPortalButtonProps = { export type BillingPortalButtonProps = {
buttonProps?: React.ComponentProps<typeof Button>; buttonProps?: React.ComponentProps<typeof Button>;
children?: React.ReactNode;
}; };
export const BillingPortalButton = ({ buttonProps }: BillingPortalButtonProps) => { export const BillingPortalButton = ({ buttonProps, children }: BillingPortalButtonProps) => {
const { _ } = useLingui(); const { _ } = useLingui();
const { toast } = useToast(); const { toast } = useToast();
@@ -63,7 +64,7 @@ export const BillingPortalButton = ({ buttonProps }: BillingPortalButtonProps) =
onClick={async () => handleFetchPortalUrl()} onClick={async () => handleFetchPortalUrl()}
loading={isFetchingPortalUrl} loading={isFetchingPortalUrl}
> >
<Trans>Manage Subscription</Trans> {children || <Trans>Manage Subscription</Trans>}
</Button> </Button>
); );
}; };

View File

@@ -68,60 +68,74 @@ export default async function BillingSettingsPage() {
return ( return (
<div> <div>
<h3 className="text-2xl font-semibold"> <div className="flex flex-row items-end justify-between">
<Trans>Billing</Trans> <div>
</h3> <h3 className="text-2xl font-semibold">
<Trans>Billing</Trans>
</h3>
<div className="text-muted-foreground mt-2 text-sm"> <div className="text-muted-foreground mt-2 text-sm">
{isMissingOrInactiveOrFreePlan && ( {isMissingOrInactiveOrFreePlan && (
<p>
<Trans>
You are currently on the <span className="font-semibold">Free Plan</span>.
</Trans>
</p>
)}
{/* Todo: Translation */}
{!isMissingOrInactiveOrFreePlan &&
match(subscription.status)
.with('ACTIVE', () => (
<p>
{subscriptionProduct ? (
<span>
You are currently subscribed to{' '}
<span className="font-semibold">{subscriptionProduct.name}</span>
</span>
) : (
<span>You currently have an active plan</span>
)}
{subscription.periodEnd && (
<span>
{' '}
which is set to{' '}
{subscription.cancelAtPeriodEnd ? (
<span>
end on{' '}
<span className="font-semibold">{i18n.date(subscription.periodEnd)}.</span>
</span>
) : (
<span>
automatically renew on{' '}
<span className="font-semibold">{i18n.date(subscription.periodEnd)}.</span>
</span>
)}
</span>
)}
</p>
))
.with('PAST_DUE', () => (
<p> <p>
<Trans> <Trans>
Your current plan is past due. Please update your payment information. You are currently on the <span className="font-semibold">Free Plan</span>.
</Trans> </Trans>
</p> </p>
)) )}
.otherwise(() => null)}
{/* Todo: Translation */}
{!isMissingOrInactiveOrFreePlan &&
match(subscription.status)
.with('ACTIVE', () => (
<p>
{subscriptionProduct ? (
<span>
You are currently subscribed to{' '}
<span className="font-semibold">{subscriptionProduct.name}</span>
</span>
) : (
<span>You currently have an active plan</span>
)}
{subscription.periodEnd && (
<span>
{' '}
which is set to{' '}
{subscription.cancelAtPeriodEnd ? (
<span>
end on{' '}
<span className="font-semibold">
{i18n.date(subscription.periodEnd)}.
</span>
</span>
) : (
<span>
automatically renew on{' '}
<span className="font-semibold">
{i18n.date(subscription.periodEnd)}.
</span>
</span>
)}
</span>
)}
</p>
))
.with('PAST_DUE', () => (
<p>
<Trans>
Your current plan is past due. Please update your payment information.
</Trans>
</p>
))
.otherwise(() => null)}
</div>
</div>
{isMissingOrInactiveOrFreePlan && (
<BillingPortalButton>
<Trans>Manage billing</Trans>
</BillingPortalButton>
)}
</div> </div>
<hr className="my-4" /> <hr className="my-4" />

View File

@@ -141,6 +141,23 @@ export const EditTemplateForm = ({
}, },
}); });
const { mutateAsync: updateTypedSignature } =
trpc.template.updateTemplateTypedSignatureSettings.useMutation({
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
onSuccess: (newData) => {
utils.template.getTemplateWithDetailsById.setData(
{
id: initialTemplate.id,
},
(oldData) => ({
...(oldData || initialTemplate),
...newData,
id: Number(newData.id),
}),
);
},
});
const onAddSettingsFormSubmit = async (data: TAddTemplateSettingsFormSchema) => { const onAddSettingsFormSubmit = async (data: TAddTemplateSettingsFormSchema) => {
try { try {
await updateTemplateSettings({ await updateTemplateSettings({
@@ -211,6 +228,12 @@ export const EditTemplateForm = ({
fields: data.fields, fields: data.fields,
}); });
await updateTypedSignature({
templateId: template.id,
teamId: team?.id,
typedSignatureEnabled: data.typedSignatureEnabled,
});
// Clear all field data from localStorage // Clear all field data from localStorage
for (let i = 0; i < localStorage.length; i++) { for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i); const key = localStorage.key(i);
@@ -225,14 +248,13 @@ export const EditTemplateForm = ({
duration: 5000, duration: 5000,
}); });
// Router refresh is here to clear the router cache for when navigating to /documents.
router.refresh();
router.push(templateRootPath); router.push(templateRootPath);
} catch (err) { } catch (err) {
console.error(err);
toast({ toast({
title: _(msg`Error`), title: _(msg`Error`),
description: _(msg`An error occurred while adding signers.`), description: _(msg`An error occurred while adding fields.`),
variant: 'destructive', variant: 'destructive',
}); });
} }
@@ -301,6 +323,7 @@ export const EditTemplateForm = ({
fields={fields} fields={fields}
onSubmit={onAddFieldsFormSubmit} onSubmit={onAddFieldsFormSubmit}
teamId={team?.id} teamId={team?.id}
typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled}
/> />
</Stepper> </Stepper>
</DocumentFlowFormContainer> </DocumentFlowFormContainer>

View File

@@ -63,7 +63,10 @@ export const TemplateEditPageView = async ({ params, team }: TemplateEditPageVie
<Trans>Template</Trans> <Trans>Template</Trans>
</Link> </Link>
<h1 className="mt-4 truncate text-2xl font-semibold md:text-3xl" title={template.title}> <h1
className="mt-4 block max-w-[20rem] truncate text-2xl font-semibold md:max-w-[30rem] md:text-3xl"
title={template.title}
>
{template.title} {template.title}
</h1> </h1>

View File

@@ -73,7 +73,6 @@ export const TemplatePageView = async ({ params, team }: TemplatePageViewProps)
const mockedDocumentMeta = templateMeta const mockedDocumentMeta = templateMeta
? { ? {
typedSignatureEnabled: false,
...templateMeta, ...templateMeta,
signingOrder: templateMeta.signingOrder || DocumentSigningOrder.SEQUENTIAL, signingOrder: templateMeta.signingOrder || DocumentSigningOrder.SEQUENTIAL,
documentId: 0, documentId: 0,
@@ -89,7 +88,10 @@ export const TemplatePageView = async ({ params, team }: TemplatePageViewProps)
<div className="flex flex-row justify-between truncate"> <div className="flex flex-row justify-between truncate">
<div> <div>
<h1 className="mt-4 truncate text-2xl font-semibold md:text-3xl" title={template.title}> <h1
className="mt-4 block max-w-[20rem] truncate text-2xl font-semibold md:max-w-[30rem] md:text-3xl"
title={template.title}
>
{template.title} {template.title}
</h1> </h1>
@@ -155,7 +157,7 @@ export const TemplatePageView = async ({ params, team }: TemplatePageViewProps)
</div> </div>
</div> </div>
<p className="text-muted-foreground mt-2 px-4 text-sm "> <p className="text-muted-foreground mt-2 px-4 text-sm">
<Trans>Manage and view template</Trans> <Trans>Manage and view template</Trans>
</p> </p>

View File

@@ -209,11 +209,19 @@ export default async function SigningCertificate({ searchParams }: SigningCertif
boxShadow: `0px 0px 0px 4.88px rgba(122, 196, 85, 0.1), 0px 0px 0px 1.22px rgba(122, 196, 85, 0.6), 0px 0px 0px 0.61px rgba(122, 196, 85, 1)`, boxShadow: `0px 0px 0px 4.88px rgba(122, 196, 85, 0.1), 0px 0px 0px 1.22px rgba(122, 196, 85, 0.6), 0px 0px 0px 0.61px rgba(122, 196, 85, 1)`,
}} }}
> >
<img {signature.Signature?.signatureImageAsBase64 && (
src={`${signature.Signature?.signatureImageAsBase64}`} <img
alt="Signature" src={`${signature.Signature?.signatureImageAsBase64}`}
className="max-h-12 max-w-full" alt="Signature"
/> className="max-h-12 max-w-full"
/>
)}
{signature.Signature?.typedSignature && (
<p className="font-signature text-center text-sm">
{signature.Signature?.typedSignature}
</p>
)}
</div> </div>
<p className="text-muted-foreground mt-2 text-sm print:text-xs"> <p className="text-muted-foreground mt-2 text-sm print:text-xs">

View File

@@ -12,7 +12,6 @@ import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
import { DocumentAuthProvider } from '~/app/(signing)/sign/[token]/document-auth-provider'; import { DocumentAuthProvider } from '~/app/(signing)/sign/[token]/document-auth-provider';
import { SigningProvider } from '~/app/(signing)/sign/[token]/provider'; import { SigningProvider } from '~/app/(signing)/sign/[token]/provider';
import { truncateTitle } from '~/helpers/truncate-title';
import { DirectTemplatePageView } from './direct-template'; import { DirectTemplatePageView } from './direct-template';
import { DirectTemplateAuthPageView } from './signing-auth-page'; import { DirectTemplateAuthPageView } from './signing-auth-page';
@@ -72,8 +71,11 @@ export default async function TemplatesDirectPage({ params }: TemplatesDirectPag
user={user} user={user}
> >
<div className="mx-auto -mt-4 w-full max-w-screen-xl px-4 md:px-8"> <div className="mx-auto -mt-4 w-full max-w-screen-xl px-4 md:px-8">
<h1 className="mt-4 truncate text-2xl font-semibold md:text-3xl" title={template.title}> <h1
{truncateTitle(template.title)} className="mt-4 block max-w-[20rem] truncate text-2xl font-semibold md:max-w-[30rem] md:text-3xl"
title={template.title}
>
{template.title}
</h1> </h1>
<div className="text-muted-foreground mb-8 mt-2.5 flex items-center gap-x-2"> <div className="text-muted-foreground mb-8 mt-2.5 flex items-center gap-x-2">

View File

@@ -102,9 +102,9 @@ export const SignDirectTemplateForm = ({
created: new Date(), created: new Date(),
recipientId: 1, recipientId: 1,
fieldId: 1, fieldId: 1,
signatureImageAsBase64: value.value, signatureImageAsBase64: value.value.startsWith('data:') ? value.value : null,
typedSignature: null, typedSignature: value.value.startsWith('data:') ? null : value.value,
}; } satisfies Signature;
} }
if (field.type === FieldType.DATE) { if (field.type === FieldType.DATE) {

View File

@@ -0,0 +1,237 @@
'use client';
import { useState, useTransition } from 'react';
import { useRouter } from 'next/navigation';
import { Plural, Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { useForm } from 'react-hook-form';
import { P, match } from 'ts-pattern';
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
import { DocumentAuth } from '@documenso/lib/types/document-auth';
import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
import type { Field, Recipient } from '@documenso/prisma/client';
import { FieldType } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import { FRIENDLY_FIELD_TYPE } from '@documenso/ui/primitives/document-flow/types';
import { Form } from '@documenso/ui/primitives/form/form';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { SigningDisclosure } from '~/components/general/signing-disclosure';
import { useRequiredDocumentAuthContext } from './document-auth-provider';
import { useRequiredSigningContext } from './provider';
const AUTO_SIGNABLE_FIELD_TYPES: string[] = [
FieldType.NAME,
FieldType.INITIALS,
FieldType.EMAIL,
FieldType.DATE,
];
// The action auth types that are not allowed to be auto signed
//
// Reasoning: If the action auth is a passkey or 2FA, it's likely that the owner of the document
// intends on having the user manually sign due to the additional security measures employed for
// other field types.
const NON_AUTO_SIGNABLE_ACTION_AUTH_TYPES: string[] = [
DocumentAuth.PASSKEY,
DocumentAuth.TWO_FACTOR_AUTH,
];
// The threshold for the number of fields that could be autosigned before displaying the dialog
//
// Reasoning: If there aren't that many fields, it's likely going to be easier to manually sign each one
// while for larger documents with many fields it will be beneficial to sign away the boilerplate fields.
const AUTO_SIGN_THRESHOLD = 5;
export type AutoSignProps = {
recipient: Pick<Recipient, 'id' | 'token'>;
fields: Field[];
};
export const AutoSign = ({ recipient, fields }: AutoSignProps) => {
const { _ } = useLingui();
const { toast } = useToast();
const router = useRouter();
const { email, fullName } = useRequiredSigningContext();
const { derivedRecipientActionAuth } = useRequiredDocumentAuthContext();
const [open, setOpen] = useState(false);
const [isPending, startTransition] = useTransition();
const form = useForm();
const { mutateAsync: signFieldWithToken } = trpc.field.signFieldWithToken.useMutation();
const autoSignableFields = fields.filter((field) => {
if (field.inserted) {
return false;
}
if (!AUTO_SIGNABLE_FIELD_TYPES.includes(field.type)) {
return false;
}
if (field.type === FieldType.NAME && !fullName) {
return false;
}
if (field.type === FieldType.INITIALS && !fullName) {
return false;
}
if (field.type === FieldType.EMAIL && !email) {
return false;
}
return true;
});
const actionAuthAllowsAutoSign = !NON_AUTO_SIGNABLE_ACTION_AUTH_TYPES.includes(
derivedRecipientActionAuth ?? '',
);
const onSubmit = async () => {
const results = await Promise.allSettled(
autoSignableFields.map(async (field) => {
const value = match(field.type)
.with(FieldType.NAME, () => fullName)
.with(FieldType.INITIALS, () => extractInitials(fullName))
.with(FieldType.EMAIL, () => email)
.with(FieldType.DATE, () => new Date().toISOString())
.otherwise(() => '');
const authOptions = match(derivedRecipientActionAuth)
.with(DocumentAuth.ACCOUNT, () => ({
type: DocumentAuth.ACCOUNT,
}))
.with(DocumentAuth.EXPLICIT_NONE, () => ({
type: DocumentAuth.EXPLICIT_NONE,
}))
.with(null, () => undefined)
.with(
P.union(DocumentAuth.PASSKEY, DocumentAuth.TWO_FACTOR_AUTH),
// This is a bit dirty, but the sentinel value used here is incredibly short-lived.
() => 'NOT_SUPPORTED' as const,
)
.exhaustive();
if (authOptions === 'NOT_SUPPORTED') {
throw new Error('Action auth is not supported for auto signing');
}
if (!value) {
throw new Error('No value to sign');
}
return await signFieldWithToken({
token: recipient.token,
fieldId: field.id,
value,
isBase64: false,
authOptions,
});
}),
);
if (results.some((result) => result.status === 'rejected')) {
toast({
title: _(msg`Error`),
description: _(
msg`An error occurred while auto-signing the document, some fields may not be signed. Please review and manually sign any remaining fields.`,
),
duration: 5000,
variant: 'destructive',
});
}
startTransition(() => {
router.refresh();
setOpen(false);
});
};
unsafe_useEffectOnce(() => {
if (actionAuthAllowsAutoSign && autoSignableFields.length > AUTO_SIGN_THRESHOLD) {
setOpen(true);
}
});
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Automatically sign fields</DialogTitle>
</DialogHeader>
<div className="text-muted-foreground max-w-[50ch]">
<p>
<Trans>
When you sign a document, we can automatically fill in and sign the following fields
using information that has already been provided. You can also manually sign or remove
any automatically signed fields afterwards if you desire.
</Trans>
</p>
<ul className="mt-4 flex list-inside list-disc flex-col gap-y-0.5">
{AUTO_SIGNABLE_FIELD_TYPES.map((fieldType) => (
<li key={fieldType}>
<Trans>{_(FRIENDLY_FIELD_TYPE[fieldType as FieldType])}</Trans>
<span className="pl-2 text-sm">
(
<Plural
value={autoSignableFields.filter((f) => f.type === fieldType).length}
one="1 matching field"
other="# matching fields"
/>
)
</span>
</li>
))}
</ul>
</div>
<SigningDisclosure className="mt-4" />
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<DialogFooter className="flex w-full flex-1 flex-nowrap gap-2">
<Button
type="button"
variant="secondary"
onClick={() => {
setOpen(false);
}}
>
<Trans>Cancel</Trans>
</Button>
<Button
type="submit"
className="min-w-[6rem]"
loading={form.formState.isSubmitting || isPending}
disabled={!autoSignableFields.length}
>
<Trans>Sign</Trans>
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
};

View File

@@ -24,8 +24,6 @@ import { SigningCard3D } from '@documenso/ui/components/signing-card';
import { cn } from '@documenso/ui/lib/utils'; import { cn } from '@documenso/ui/lib/utils';
import { Badge } from '@documenso/ui/primitives/badge'; import { Badge } from '@documenso/ui/primitives/badge';
import { truncateTitle } from '~/helpers/truncate-title';
import { SigningAuthPageView } from '../signing-auth-page'; import { SigningAuthPageView } from '../signing-auth-page';
import { ClaimAccount } from './claim-account'; import { ClaimAccount } from './claim-account';
import { DocumentPreviewButton } from './document-preview-button'; import { DocumentPreviewButton } from './document-preview-button';
@@ -61,8 +59,6 @@ export default async function CompletedSigningPage({
return notFound(); return notFound();
} }
const truncatedTitle = truncateTitle(document.title);
const { documentData } = document; const { documentData } = document;
const [fields, recipient] = await Promise.all([ const [fields, recipient] = await Promise.all([
@@ -118,7 +114,9 @@ export default async function CompletedSigningPage({
})} })}
> >
<Badge variant="neutral" size="default" className="mb-6 rounded-xl border bg-transparent"> <Badge variant="neutral" size="default" className="mb-6 rounded-xl border bg-transparent">
{truncatedTitle} <span className="block max-w-[10rem] truncate font-medium hover:underline md:max-w-[20rem]">
{document.title}
</span>
</Badge> </Badge>
{/* Card with recipient */} {/* Card with recipient */}

View File

@@ -144,13 +144,13 @@ export const DateField = ({
)} )}
{!field.inserted && ( {!field.inserted && (
<p className="group-hover:text-primary text-muted-foreground duration-200 group-hover:text-yellow-300"> <p className="group-hover:text-primary text-muted-foreground text-[clamp(0.425rem,25cqw,0.825rem)] duration-200 group-hover:text-yellow-300">
<Trans>Date</Trans> <Trans>Date</Trans>
</p> </p>
)} )}
{field.inserted && ( {field.inserted && (
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.625rem,1cqw,0.825rem)] duration-200"> <p className="text-muted-foreground dark:text-background/80 text-[clamp(0.425rem,25cqw,0.825rem)] duration-200">
{localDateString} {localDateString}
</p> </p>
)} )}

View File

@@ -178,7 +178,7 @@ export const DropdownField = ({
)} )}
{!field.inserted && ( {!field.inserted && (
<p className="group-hover:text-primary text-muted-foreground flex flex-col items-center justify-center duration-200"> <p className="group-hover:text-primary text-muted-foreground flex flex-col items-center justify-center duration-200 ">
<Select value={localChoice} onValueChange={handleSelectItem}> <Select value={localChoice} onValueChange={handleSelectItem}>
<SelectTrigger <SelectTrigger
className={cn( className={cn(
@@ -190,7 +190,7 @@ export const DropdownField = ({
)} )}
> >
<SelectValue <SelectValue
className="text-[clamp(0.625rem,1cqw,0.825rem)]" className="text-[clamp(0.425rem,25cqw,0.825rem)]"
placeholder={`${_(msg`Select`)}`} placeholder={`${_(msg`Select`)}`}
/> />
</SelectTrigger> </SelectTrigger>
@@ -206,7 +206,7 @@ export const DropdownField = ({
)} )}
{field.inserted && ( {field.inserted && (
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.625rem,1cqw,0.825rem)] duration-200"> <p className="text-muted-foreground dark:text-background/80 text-[clamp(0.425rem,25cqw,0.825rem)] duration-200">
{field.customText} {field.customText}
</p> </p>
)} )}

View File

@@ -122,13 +122,13 @@ export const EmailField = ({ field, recipient, onSignField, onUnsignField }: Ema
)} )}
{!field.inserted && ( {!field.inserted && (
<p className="group-hover:text-primary text-muted-foreground duration-200 group-hover:text-yellow-300"> <p className="group-hover:text-primary text-muted-foreground text-[clamp(0.425rem,25cqw,0.825rem)] duration-200 group-hover:text-yellow-300">
<Trans>Email</Trans> <Trans>Email</Trans>
</p> </p>
)} )}
{field.inserted && ( {field.inserted && (
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.625rem,1cqw,0.825rem)] duration-200"> <p className="text-muted-foreground dark:text-background/80 text-[clamp(0.425rem,25cqw,0.825rem)] duration-200">
{field.customText} {field.customText}
</p> </p>
)} )}

View File

@@ -128,13 +128,13 @@ export const InitialsField = ({
)} )}
{!field.inserted && ( {!field.inserted && (
<p className="group-hover:text-primary text-muted-foreground duration-200 group-hover:text-yellow-300"> <p className="group-hover:text-primary text-muted-foreground text-[clamp(0.425rem,25cqw,0.825rem)] duration-200 group-hover:text-yellow-300">
<Trans>Initials</Trans> <Trans>Initials</Trans>
</p> </p>
)} )}
{field.inserted && ( {field.inserted && (
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.625rem,1cqw,0.825rem)] duration-200"> <p className="text-muted-foreground dark:text-background/80 text-[clamp(0.425rem,25cqw,0.825rem)] duration-200">
{field.customText} {field.customText}
</p> </p>
)} )}

View File

@@ -172,7 +172,7 @@ export const NameField = ({ field, recipient, onSignField, onUnsignField }: Name
)} )}
{field.inserted && ( {field.inserted && (
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.625rem,1cqw,0.825rem)] duration-200"> <p className="text-muted-foreground dark:text-background/80 text-[clamp(0.425rem,25cqw,0.825rem)] duration-200">
{field.customText} {field.customText}
</p> </p>
)} )}

View File

@@ -252,14 +252,15 @@ export const NumberField = ({ field, recipient, onSignField, onUnsignField }: Nu
}, },
)} )}
> >
<span className="flex items-center justify-center gap-x-1 text-sm"> <span className="flex items-center justify-center gap-x-1">
<Hash className="h-4 w-4" /> {fieldDisplayName} <Hash className="h-[clamp(0.625rem,20cqw,0.925rem)] w-[clamp(0.625rem,20cqw,0.925rem)]" />{' '}
<span className="text-[clamp(0.425rem,25cqw,0.825rem)]">{fieldDisplayName}</span>
</span> </span>
</p> </p>
)} )}
{field.inserted && ( {field.inserted && (
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.625rem,1cqw,0.825rem)] duration-200"> <p className="text-muted-foreground dark:text-background/80 text-[clamp(0.425rem,25cqw,0.825rem)] duration-200">
{field.customText} {field.customText}
</p> </p>
)} )}

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { createContext, useContext, useState } from 'react'; import { createContext, useContext, useEffect, useState } from 'react';
export type SigningContextValue = { export type SigningContextValue = {
fullName: string; fullName: string;
@@ -44,6 +44,12 @@ export const SigningProvider = ({
const [email, setEmail] = useState(initialEmail || ''); const [email, setEmail] = useState(initialEmail || '');
const [signature, setSignature] = useState(initialSignature || null); const [signature, setSignature] = useState(initialSignature || null);
useEffect(() => {
if (initialSignature) {
setSignature(initialSignature);
}
}, [initialSignature]);
return ( return (
<SigningContext.Provider <SigningContext.Provider
value={{ value={{

View File

@@ -14,7 +14,6 @@ import {
} from '@documenso/ui/primitives/dialog'; } from '@documenso/ui/primitives/dialog';
import { SigningDisclosure } from '~/components/general/signing-disclosure'; import { SigningDisclosure } from '~/components/general/signing-disclosure';
import { truncateTitle } from '~/helpers/truncate-title';
export type SignDialogProps = { export type SignDialogProps = {
isSubmitting: boolean; isSubmitting: boolean;
@@ -36,7 +35,7 @@ export const SignDialog = ({
disabled = false, disabled = false,
}: SignDialogProps) => { }: SignDialogProps) => {
const [showDialog, setShowDialog] = useState(false); const [showDialog, setShowDialog] = useState(false);
const truncatedTitle = truncateTitle(documentTitle);
const isComplete = fields.every((field) => field.inserted); const isComplete = fields.every((field) => field.inserted);
const handleOpenChange = (open: boolean) => { const handleOpenChange = (open: boolean) => {
@@ -75,7 +74,13 @@ export const SignDialog = ({
{role === RecipientRole.VIEWER && ( {role === RecipientRole.VIEWER && (
<span> <span>
<Trans> <Trans>
You are about to complete viewing "{truncatedTitle}". <span className="inline-flex flex-wrap">
You are about to complete viewing "
<span className="inline-block max-w-[11rem] truncate align-baseline">
{documentTitle}
</span>
".
</span>
<br /> Are you sure? <br /> Are you sure?
</Trans> </Trans>
</span> </span>
@@ -83,7 +88,13 @@ export const SignDialog = ({
{role === RecipientRole.SIGNER && ( {role === RecipientRole.SIGNER && (
<span> <span>
<Trans> <Trans>
You are about to complete signing "{truncatedTitle}". <span className="inline-flex flex-wrap">
You are about to complete signing "
<span className="inline-block max-w-[11rem] truncate align-baseline">
{documentTitle}
</span>
".
</span>
<br /> Are you sure? <br /> Are you sure?
</Trans> </Trans>
</span> </span>
@@ -91,7 +102,13 @@ export const SignDialog = ({
{role === RecipientRole.APPROVER && ( {role === RecipientRole.APPROVER && (
<span> <span>
<Trans> <Trans>
You are about to complete approving "{truncatedTitle}". <span className="inline-flex flex-wrap">
You are about to complete approving{' '}
<span className="inline-block max-w-[11rem] truncate align-baseline">
"{documentTitle}"
</span>
.
</span>
<br /> Are you sure? <br /> Are you sure?
</Trans> </Trans>
</span> </span>

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useMemo, useState, useTransition } from 'react'; import { useLayoutEffect, useMemo, useRef, useState, useTransition } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -51,6 +51,10 @@ export const SignatureField = ({
const { _ } = useLingui(); const { _ } = useLingui();
const { toast } = useToast(); const { toast } = useToast();
const signatureRef = useRef<HTMLParagraphElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const [fontSize, setFontSize] = useState(2);
const { signature: providedSignature, setSignature: setProvidedSignature } = const { signature: providedSignature, setSignature: setProvidedSignature } =
useRequiredSigningContext(); useRequiredSigningContext();
@@ -108,6 +112,7 @@ export const SignatureField = ({
actionTarget: field.type, actionTarget: field.type,
}); });
}; };
const onSign = async (authOptions?: TRecipientActionAuth, signature?: string) => { const onSign = async (authOptions?: TRecipientActionAuth, signature?: string) => {
try { try {
const value = signature || providedSignature; const value = signature || providedSignature;
@@ -117,11 +122,23 @@ export const SignatureField = ({
return; return;
} }
const isTypedSignature = !value.startsWith('data:image');
if (isTypedSignature && !typedSignatureEnabled) {
toast({
title: _(msg`Error`),
description: _(msg`Typed signatures are not allowed. Please draw your signature.`),
variant: 'destructive',
});
return;
}
const payload: TSignFieldWithTokenMutationSchema = { const payload: TSignFieldWithTokenMutationSchema = {
token: recipient.token, token: recipient.token,
fieldId: field.id, fieldId: field.id,
value, value,
isBase64: true, isBase64: !isTypedSignature,
authOptions, authOptions,
}; };
@@ -176,6 +193,41 @@ export const SignatureField = ({
} }
}; };
useLayoutEffect(() => {
if (!signatureRef.current || !containerRef.current || !signature?.typedSignature) {
return;
}
const adjustTextSize = () => {
const container = containerRef.current;
const text = signatureRef.current;
if (!container || !text) {
return;
}
let size = 2;
text.style.fontSize = `${size}rem`;
while (
(text.scrollWidth > container.clientWidth || text.scrollHeight > container.clientHeight) &&
size > 0.8
) {
size -= 0.1;
text.style.fontSize = `${size}rem`;
}
setFontSize(size);
};
const resizeObserver = new ResizeObserver(adjustTextSize);
resizeObserver.observe(containerRef.current);
adjustTextSize();
return () => resizeObserver.disconnect();
}, [signature?.typedSignature]);
return ( return (
<SigningFieldContainer <SigningFieldContainer
field={field} field={field}
@@ -191,7 +243,7 @@ export const SignatureField = ({
)} )}
{state === 'empty' && ( {state === 'empty' && (
<p className="group-hover:text-primary font-signature text-muted-foreground text-xl duration-200 group-hover:text-yellow-300"> <p className="group-hover:text-primary font-signature text-muted-foreground text-[clamp(0.575rem,25cqw,1.2rem)] text-xl duration-200 group-hover:text-yellow-300">
<Trans>Signature</Trans> <Trans>Signature</Trans>
</p> </p>
)} )}
@@ -205,10 +257,15 @@ export const SignatureField = ({
)} )}
{state === 'signed-text' && ( {state === 'signed-text' && (
<p className="font-signature text-muted-foreground dark:text-background text-lg duration-200 sm:text-xl md:text-2xl lg:text-3xl"> <div ref={containerRef} className="flex h-full w-full items-center justify-center p-2">
{/* This optional chaining is intentional, we don't want to move the check into the condition above */} <p
{signature?.typedSignature} ref={signatureRef}
</p> className="font-signature text-muted-foreground dark:text-background w-full overflow-hidden break-all text-center leading-tight duration-200"
style={{ fontSize: `${fontSize}rem` }}
>
{signature?.typedSignature}
</p>
</div>
)} )}
<Dialog open={showSignatureModal} onOpenChange={setShowSignatureModal}> <Dialog open={showSignatureModal} onOpenChange={setShowSignatureModal}>

View File

@@ -22,6 +22,7 @@ import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
import { DocumentReadOnlyFields } from '~/components/document/document-read-only-fields'; import { DocumentReadOnlyFields } from '~/components/document/document-read-only-fields';
import { AutoSign } from './auto-sign';
import { CheckboxField } from './checkbox-field'; import { CheckboxField } from './checkbox-field';
import { DateField } from './date-field'; import { DateField } from './date-field';
import { DropdownField } from './dropdown-field'; import { DropdownField } from './dropdown-field';
@@ -54,7 +55,10 @@ export const SigningPageView = ({
return ( return (
<div className="mx-auto w-full max-w-screen-xl"> <div className="mx-auto w-full max-w-screen-xl">
<h1 className="mt-4 truncate text-2xl font-semibold md:text-3xl" title={document.title}> <h1
className="mt-4 block max-w-[20rem] truncate text-2xl font-semibold md:max-w-[30rem] md:text-3xl"
title={document.title}
>
{document.title} {document.title}
</h1> </h1>
@@ -113,6 +117,8 @@ export const SigningPageView = ({
<DocumentReadOnlyFields fields={completedFields} /> <DocumentReadOnlyFields fields={completedFields} />
<AutoSign recipient={recipient} fields={fields} />
<ElementVisible target={PDF_VIEWER_PAGE_SELECTOR}> <ElementVisible target={PDF_VIEWER_PAGE_SELECTOR}>
{fields.map((field) => {fields.map((field) =>
match(field.type) match(field.type)

View File

@@ -252,14 +252,16 @@ export const TextField = ({ field, recipient, onSignField, onUnsignField }: Text
)} )}
> >
<span className="flex items-center justify-center gap-x-1"> <span className="flex items-center justify-center gap-x-1">
<Type /> <Type className="h-[clamp(0.625rem,20cqw,0.925rem)] w-[clamp(0.625rem,20cqw,0.925rem)]" />
{fieldDisplayName || <Trans>Text</Trans>} <span className="text-[clamp(0.425rem,25cqw,0.825rem)]">
{fieldDisplayName || <Trans>Text</Trans>}
</span>
</span> </span>
</p> </p>
)} )}
{field.inserted && ( {field.inserted && (
<p className="text-muted-foreground dark:text-background/80 flex items-center justify-center gap-x-1 duration-200"> <p className="text-muted-foreground dark:text-background/80 flex items-center justify-center gap-x-1 text-[clamp(0.425rem,25cqw,0.825rem)] duration-200">
{field.customText.length < 20 {field.customText.length < 20
? field.customText ? field.customText
: field.customText.substring(0, 15) + '...'} : field.customText.substring(0, 15) + '...'}

View File

@@ -52,13 +52,7 @@ export default async function TeamsSettingsPage({ params }: TeamsSettingsPagePro
<AvatarImageForm className="mb-8" team={team} user={session.user} /> <AvatarImageForm className="mb-8" team={team} user={session.user} />
<UpdateTeamForm <UpdateTeamForm teamId={team.id} teamName={team.name} teamUrl={team.url} />
teamId={team.id}
teamName={team.name}
teamUrl={team.url}
documentVisibility={team.teamGlobalSettings?.documentVisibility}
includeSenderDetails={team.teamGlobalSettings?.includeSenderDetails}
/>
<section className="mt-6 space-y-6"> <section className="mt-6 space-y-6">
{(team.teamEmail || team.emailVerification) && ( {(team.teamEmail || team.emailVerification) && (

View File

@@ -39,6 +39,8 @@ const ZTeamDocumentPreferencesFormSchema = z.object({
documentVisibility: z.nativeEnum(DocumentVisibility), documentVisibility: z.nativeEnum(DocumentVisibility),
documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES), documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES),
includeSenderDetails: z.boolean(), includeSenderDetails: z.boolean(),
typedSignatureEnabled: z.boolean(),
includeSigningCertificate: z.boolean(),
}); });
type TTeamDocumentPreferencesFormSchema = z.infer<typeof ZTeamDocumentPreferencesFormSchema>; type TTeamDocumentPreferencesFormSchema = z.infer<typeof ZTeamDocumentPreferencesFormSchema>;
@@ -68,6 +70,8 @@ export const TeamDocumentPreferencesForm = ({
? settings?.documentLanguage ? settings?.documentLanguage
: 'en', : 'en',
includeSenderDetails: settings?.includeSenderDetails ?? false, includeSenderDetails: settings?.includeSenderDetails ?? false,
typedSignatureEnabled: settings?.typedSignatureEnabled ?? true,
includeSigningCertificate: settings?.includeSigningCertificate ?? true,
}, },
resolver: zodResolver(ZTeamDocumentPreferencesFormSchema), resolver: zodResolver(ZTeamDocumentPreferencesFormSchema),
}); });
@@ -76,7 +80,13 @@ export const TeamDocumentPreferencesForm = ({
const onSubmit = async (data: TTeamDocumentPreferencesFormSchema) => { const onSubmit = async (data: TTeamDocumentPreferencesFormSchema) => {
try { try {
const { documentVisibility, documentLanguage, includeSenderDetails } = data; const {
documentVisibility,
documentLanguage,
includeSenderDetails,
includeSigningCertificate,
typedSignatureEnabled,
} = data;
await updateTeamDocumentPreferences({ await updateTeamDocumentPreferences({
teamId: team.id, teamId: team.id,
@@ -84,6 +94,8 @@ export const TeamDocumentPreferencesForm = ({
documentVisibility, documentVisibility,
documentLanguage, documentLanguage,
includeSenderDetails, includeSenderDetails,
typedSignatureEnabled,
includeSigningCertificate,
}, },
}); });
@@ -105,7 +117,7 @@ export const TeamDocumentPreferencesForm = ({
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}> <form onSubmit={form.handleSubmit(onSubmit)}>
<fieldset <fieldset
className="flex h-full max-w-xl flex-col gap-y-4" className="flex h-full max-w-xl flex-col gap-y-6"
disabled={form.formState.isSubmitting} disabled={form.formState.isSubmitting}
> >
<FormField <FormField
@@ -227,6 +239,67 @@ export const TeamDocumentPreferencesForm = ({
)} )}
/> />
<FormField
control={form.control}
name="typedSignatureEnabled"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>
<Trans>Enable Typed Signature</Trans>
</FormLabel>
<div>
<FormControl className="block">
<Switch
ref={field.ref}
name={field.name}
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</div>
<FormDescription>
<Trans>
Controls whether the recipients can sign the documents using a typed signature.
Enable or disable the typed signature globally.
</Trans>
</FormDescription>
</FormItem>
)}
/>
<FormField
control={form.control}
name="includeSigningCertificate"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>
<Trans>Include the Signing Certificate in the Document</Trans>
</FormLabel>
<div>
<FormControl className="block">
<Switch
ref={field.ref}
name={field.name}
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</div>
<FormDescription>
<Trans>
Controls whether the signing certificate will be included in the document when
it is downloaded. The signing certificate can still be downloaded from the logs
page separately.
</Trans>
</FormDescription>
</FormItem>
)}
/>
<div className="flex flex-row justify-end space-x-4"> <div className="flex flex-row justify-end space-x-4">
<Button type="submit" loading={form.formState.isSubmitting}> <Button type="submit" loading={form.formState.isSubmitting}>
<Trans>Save</Trans> <Trans>Save</Trans>

View File

@@ -1,8 +1,12 @@
import { z } from 'zod'; import { z } from 'zod';
import { ZCssVarsSchema } from './css-vars';
export const ZBaseEmbedDataSchema = z.object({ export const ZBaseEmbedDataSchema = z.object({
darkModeDisabled: z.boolean().optional().default(false),
css: z css: z
.string() .string()
.optional() .optional()
.transform((value) => value || undefined), .transform((value) => value || undefined),
cssVars: ZCssVarsSchema.optional().default({}),
}); });

View File

@@ -10,6 +10,7 @@ export type EmbedDocumentCompletedPageProps = {
}; };
export const EmbedDocumentCompleted = ({ name, signature }: EmbedDocumentCompletedPageProps) => { export const EmbedDocumentCompleted = ({ name, signature }: EmbedDocumentCompletedPageProps) => {
console.log({ signature });
return ( return (
<div className="relative mx-auto flex min-h-[100dvh] max-w-screen-lg flex-col items-center justify-center p-6"> <div className="relative mx-auto flex min-h-[100dvh] max-w-screen-lg flex-col items-center justify-center p-6">
<h3 className="text-foreground text-2xl font-semibold"> <h3 className="text-foreground text-2xl font-semibold">

View File

@@ -0,0 +1,59 @@
import { colord } from 'colord';
import { toSnakeCase } from 'remeda';
import { z } from 'zod';
export const ZCssVarsSchema = z
.object({
background: z.string().optional().describe('Base background color'),
foreground: z.string().optional().describe('Base text color'),
muted: z.string().optional().describe('Muted/subtle background color'),
mutedForeground: z.string().optional().describe('Muted/subtle text color'),
popover: z.string().optional().describe('Popover/dropdown background color'),
popoverForeground: z.string().optional().describe('Popover/dropdown text color'),
card: z.string().optional().describe('Card background color'),
cardBorder: z.string().optional().describe('Card border color'),
cardBorderTint: z.string().optional().describe('Card border tint/highlight color'),
cardForeground: z.string().optional().describe('Card text color'),
fieldCard: z.string().optional().describe('Field card background color'),
fieldCardBorder: z.string().optional().describe('Field card border color'),
fieldCardForeground: z.string().optional().describe('Field card text color'),
widget: z.string().optional().describe('Widget background color'),
widgetForeground: z.string().optional().describe('Widget text color'),
border: z.string().optional().describe('Default border color'),
input: z.string().optional().describe('Input field border color'),
primary: z.string().optional().describe('Primary action/button color'),
primaryForeground: z.string().optional().describe('Primary action/button text color'),
secondary: z.string().optional().describe('Secondary action/button color'),
secondaryForeground: z.string().optional().describe('Secondary action/button text color'),
accent: z.string().optional().describe('Accent/highlight color'),
accentForeground: z.string().optional().describe('Accent/highlight text color'),
destructive: z.string().optional().describe('Destructive/danger action color'),
destructiveForeground: z.string().optional().describe('Destructive/danger text color'),
ring: z.string().optional().describe('Focus ring color'),
radius: z.string().optional().describe('Border radius size in REM units'),
warning: z.string().optional().describe('Warning/alert color'),
})
.describe('Custom CSS variables for theming');
export type TCssVarsSchema = z.infer<typeof ZCssVarsSchema>;
export const toNativeCssVars = (vars: TCssVarsSchema) => {
const cssVars: Record<string, string> = {};
const { radius, ...colorVars } = vars;
for (const [key, value] of Object.entries(colorVars)) {
if (value) {
const color = colord(value);
const { h, s, l } = color.toHsl();
cssVars[`--${toSnakeCase(key)}`] = `${h} ${s} ${l}`;
}
}
if (radius) {
cssVars[`--radius`] = `${radius}`;
}
return cssVars;
};

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useEffect, useState } from 'react'; import { useEffect, useLayoutEffect, useState } from 'react';
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
@@ -14,7 +14,7 @@ import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-form
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer'; import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones'; import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
import { validateFieldsInserted } from '@documenso/lib/utils/fields'; import { validateFieldsInserted } from '@documenso/lib/utils/fields';
import type { DocumentMeta, Recipient, TemplateMeta } from '@documenso/prisma/client'; import type { DocumentMeta, Recipient, Signature, TemplateMeta } from '@documenso/prisma/client';
import { type DocumentData, type Field, FieldType } from '@documenso/prisma/client'; import { type DocumentData, type Field, FieldType } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react'; import { trpc } from '@documenso/trpc/react';
import type { import type {
@@ -38,6 +38,7 @@ import { Logo } from '~/components/branding/logo';
import { EmbedClientLoading } from '../../client-loading'; import { EmbedClientLoading } from '../../client-loading';
import { EmbedDocumentCompleted } from '../../completed'; import { EmbedDocumentCompleted } from '../../completed';
import { EmbedDocumentFields } from '../../document-fields'; import { EmbedDocumentFields } from '../../document-fields';
import { injectCss } from '../../util';
import { ZDirectTemplateEmbedDataSchema } from './schema'; import { ZDirectTemplateEmbedDataSchema } from './schema';
export type EmbedDirectTemplateClientPageProps = { export type EmbedDirectTemplateClientPageProps = {
@@ -47,6 +48,8 @@ export type EmbedDirectTemplateClientPageProps = {
recipient: Recipient; recipient: Recipient;
fields: Field[]; fields: Field[];
metadata?: DocumentMeta | TemplateMeta | null; metadata?: DocumentMeta | TemplateMeta | null;
hidePoweredBy?: boolean;
isPlatformOrEnterprise?: boolean;
}; };
export const EmbedDirectTemplateClientPage = ({ export const EmbedDirectTemplateClientPage = ({
@@ -56,6 +59,8 @@ export const EmbedDirectTemplateClientPage = ({
recipient, recipient,
fields, fields,
metadata, metadata,
hidePoweredBy = false,
isPlatformOrEnterprise = false,
}: EmbedDirectTemplateClientPageProps) => { }: EmbedDirectTemplateClientPageProps) => {
const { _ } = useLingui(); const { _ } = useLingui();
const { toast } = useToast(); const { toast } = useToast();
@@ -108,9 +113,9 @@ export const EmbedDirectTemplateClientPage = ({
created: new Date(), created: new Date(),
recipientId: 1, recipientId: 1,
fieldId: 1, fieldId: 1,
signatureImageAsBase64: payload.value, signatureImageAsBase64: payload.value.startsWith('data:') ? payload.value : null,
typedSignature: null, typedSignature: payload.value.startsWith('data:') ? null : payload.value,
}; } satisfies Signature;
} }
if (field.type === FieldType.DATE) { if (field.type === FieldType.DATE) {
@@ -249,7 +254,7 @@ export const EmbedDirectTemplateClientPage = ({
} }
}; };
useEffect(() => { useLayoutEffect(() => {
const hash = window.location.hash.slice(1); const hash = window.location.hash.slice(1);
try { try {
@@ -264,6 +269,17 @@ export const EmbedDirectTemplateClientPage = ({
setFullName(data.name); setFullName(data.name);
setIsNameLocked(!!data.lockName); setIsNameLocked(!!data.lockName);
} }
if (data.darkModeDisabled) {
document.documentElement.classList.add('dark-mode-disabled');
}
if (isPlatformOrEnterprise) {
injectCss({
css: data.css,
cssVars: data.cssVars,
});
}
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }
@@ -296,8 +312,8 @@ export const EmbedDirectTemplateClientPage = ({
fieldId: 1, fieldId: 1,
recipientId: 1, recipientId: 1,
created: new Date(), created: new Date(),
typedSignature: null, signatureImageAsBase64: signature?.startsWith('data:') ? signature : null,
signatureImageAsBase64: signature, typedSignature: signature?.startsWith('data:') ? null : signature,
}} }}
/> />
); );
@@ -452,10 +468,12 @@ export const EmbedDirectTemplateClientPage = ({
/> />
</div> </div>
<div className="bg-primary text-primary-foreground fixed bottom-0 left-0 z-40 rounded-tr px-2 py-1 text-xs font-medium opacity-60 hover:opacity-100"> {!hidePoweredBy && (
<span>Powered by</span> <div className="bg-primary text-primary-foreground fixed bottom-0 left-0 z-40 rounded-tr px-2 py-1 text-xs font-medium opacity-60 hover:opacity-100">
<Logo className="ml-2 inline-block h-[14px]" /> <span>Powered by</span>
</div> <Logo className="ml-2 inline-block h-[14px]" />
</div>
)}
</div> </div>
); );
}; };

View File

@@ -2,8 +2,11 @@ import { notFound } from 'next/navigation';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
import { isDocumentPlatform } from '@documenso/ee/server-only/util/is-document-platform';
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session'; import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
import { getTemplateByDirectLinkToken } from '@documenso/lib/server-only/template/get-template-by-direct-link-token'; import { getTemplateByDirectLinkToken } from '@documenso/lib/server-only/template/get-template-by-direct-link-token';
import { DocumentAccessAuth } from '@documenso/lib/types/document-auth'; import { DocumentAccessAuth } from '@documenso/lib/types/document-auth';
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth'; import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
@@ -51,6 +54,14 @@ export default async function EmbedDirectTemplatePage({ params }: EmbedDirectTem
documentAuth: template.authOptions, documentAuth: template.authOptions,
}); });
const [isPlatformDocument, isEnterpriseDocument] = await Promise.all([
isDocumentPlatform(template),
isUserEnterprise({
userId: template.userId,
teamId: template.teamId ?? undefined,
}),
]);
const isAccessAuthValid = match(derivedRecipientAccessAuth) const isAccessAuthValid = match(derivedRecipientAccessAuth)
.with(DocumentAccessAuth.ACCOUNT, () => user !== null) .with(DocumentAccessAuth.ACCOUNT, () => user !== null)
.with(null, () => true) .with(null, () => true)
@@ -72,6 +83,12 @@ export default async function EmbedDirectTemplatePage({ params }: EmbedDirectTem
const fields = template.Field.filter((field) => field.recipientId === directTemplateRecipientId); const fields = template.Field.filter((field) => field.recipientId === directTemplateRecipientId);
const team = template.teamId
? await getTeamById({ teamId: template.teamId, userId: template.userId }).catch(() => null)
: null;
const hidePoweredBy = team?.teamGlobalSettings?.brandingHidePoweredBy ?? false;
return ( return (
<SigningProvider email={user?.email} fullName={user?.name} signature={user?.signature}> <SigningProvider email={user?.email} fullName={user?.name} signature={user?.signature}>
<DocumentAuthProvider <DocumentAuthProvider
@@ -86,6 +103,8 @@ export default async function EmbedDirectTemplatePage({ params }: EmbedDirectTem
recipient={recipient} recipient={recipient}
fields={fields} fields={fields}
metadata={template.templateMeta} metadata={template.templateMeta}
hidePoweredBy={isPlatformDocument || isEnterpriseDocument || hidePoweredBy}
isPlatformOrEnterprise={isPlatformDocument || isEnterpriseDocument}
/> />
</DocumentAuthProvider> </DocumentAuthProvider>
</SigningProvider> </SigningProvider>

View File

@@ -58,6 +58,7 @@ export const EmbedDocumentFields = ({
recipient={recipient} recipient={recipient}
onSignField={onSignField} onSignField={onSignField}
onUnsignField={onUnsignField} onUnsignField={onUnsignField}
typedSignatureEnabled={metadata?.typedSignatureEnabled}
/> />
)) ))
.with(FieldType.INITIALS, () => ( .with(FieldType.INITIALS, () => (

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useEffect, useState } from 'react'; import { useEffect, useLayoutEffect, useState } from 'react';
import { Trans, msg } from '@lingui/macro'; import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react'; import { useLingui } from '@lingui/react';
@@ -28,6 +28,7 @@ import { Logo } from '~/components/branding/logo';
import { EmbedClientLoading } from '../../client-loading'; import { EmbedClientLoading } from '../../client-loading';
import { EmbedDocumentCompleted } from '../../completed'; import { EmbedDocumentCompleted } from '../../completed';
import { EmbedDocumentFields } from '../../document-fields'; import { EmbedDocumentFields } from '../../document-fields';
import { injectCss } from '../../util';
import { ZSignDocumentEmbedDataSchema } from './schema'; import { ZSignDocumentEmbedDataSchema } from './schema';
export type EmbedSignDocumentClientPageProps = { export type EmbedSignDocumentClientPageProps = {
@@ -38,6 +39,8 @@ export type EmbedSignDocumentClientPageProps = {
fields: Field[]; fields: Field[];
metadata?: DocumentMeta | TemplateMeta | null; metadata?: DocumentMeta | TemplateMeta | null;
isCompleted?: boolean; isCompleted?: boolean;
hidePoweredBy?: boolean;
isPlatformOrEnterprise?: boolean;
}; };
export const EmbedSignDocumentClientPage = ({ export const EmbedSignDocumentClientPage = ({
@@ -48,6 +51,8 @@ export const EmbedSignDocumentClientPage = ({
fields, fields,
metadata, metadata,
isCompleted, isCompleted,
hidePoweredBy = false,
isPlatformOrEnterprise = false,
}: EmbedSignDocumentClientPageProps) => { }: EmbedSignDocumentClientPageProps) => {
const { _ } = useLingui(); const { _ } = useLingui();
const { toast } = useToast(); const { toast } = useToast();
@@ -131,7 +136,7 @@ export const EmbedSignDocumentClientPage = ({
} }
}; };
useEffect(() => { useLayoutEffect(() => {
const hash = window.location.hash.slice(1); const hash = window.location.hash.slice(1);
try { try {
@@ -144,6 +149,17 @@ export const EmbedSignDocumentClientPage = ({
// Since a recipient can be provided a name we can lock it without requiring // Since a recipient can be provided a name we can lock it without requiring
// a to be provided by the parent application, unlike direct templates. // a to be provided by the parent application, unlike direct templates.
setIsNameLocked(!!data.lockName); setIsNameLocked(!!data.lockName);
if (data.darkModeDisabled) {
document.documentElement.classList.add('dark-mode-disabled');
}
if (isPlatformOrEnterprise) {
injectCss({
css: data.css,
cssVars: data.cssVars,
});
}
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }
@@ -176,8 +192,8 @@ export const EmbedSignDocumentClientPage = ({
fieldId: 1, fieldId: 1,
recipientId: 1, recipientId: 1,
created: new Date(), created: new Date(),
typedSignature: null, signatureImageAsBase64: signature?.startsWith('data:') ? signature : null,
signatureImageAsBase64: signature, typedSignature: signature?.startsWith('data:') ? null : signature,
}} }}
/> />
); );
@@ -202,7 +218,7 @@ export const EmbedSignDocumentClientPage = ({
className="group/document-widget fixed bottom-8 left-0 z-50 h-fit w-full flex-shrink-0 px-6 md:sticky md:top-4 md:z-auto md:w-[350px] md:px-0" className="group/document-widget fixed bottom-8 left-0 z-50 h-fit w-full flex-shrink-0 px-6 md:sticky md:top-4 md:z-auto md:w-[350px] md:px-0"
data-expanded={isExpanded || undefined} data-expanded={isExpanded || undefined}
> >
<div className="border-border bg-widget flex w-full flex-col rounded-xl border px-4 py-4 md:py-6"> <div className="border-border bg-widget flex w-full flex-col rounded-xl border px-4 py-4 md:py-6">
{/* Header */} {/* Header */}
<div> <div>
<div className="flex items-center justify-between gap-x-2"> <div className="flex items-center justify-between gap-x-2">
@@ -325,10 +341,12 @@ export const EmbedSignDocumentClientPage = ({
<EmbedDocumentFields recipient={recipient} fields={fields} metadata={metadata} /> <EmbedDocumentFields recipient={recipient} fields={fields} metadata={metadata} />
</div> </div>
<div className="bg-primary text-primary-foreground fixed bottom-0 left-0 z-40 rounded-tr px-2 py-1 text-xs font-medium opacity-60 hover:opacity-100"> {!hidePoweredBy && (
<span>Powered by</span> <div className="bg-primary text-primary-foreground fixed bottom-0 left-0 z-40 rounded-tr px-2 py-1 text-xs font-medium opacity-60 hover:opacity-100">
<Logo className="ml-2 inline-block h-[14px]" /> <span>Powered by</span>
</div> <Logo className="ml-2 inline-block h-[14px]" />
</div>
)}
</div> </div>
); );
}; };

View File

@@ -2,11 +2,14 @@ import { notFound } from 'next/navigation';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
import { isDocumentPlatform } from '@documenso/ee/server-only/util/is-document-platform';
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session'; import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token'; import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token'; import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token'; import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
import { DocumentAccessAuth } from '@documenso/lib/types/document-auth'; import { DocumentAccessAuth } from '@documenso/lib/types/document-auth';
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth'; import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
import { DocumentStatus } from '@documenso/prisma/client'; import { DocumentStatus } from '@documenso/prisma/client';
@@ -56,6 +59,14 @@ export default async function EmbedSignDocumentPage({ params }: EmbedSignDocumen
return <EmbedPaywall />; return <EmbedPaywall />;
} }
const [isPlatformDocument, isEnterpriseDocument] = await Promise.all([
isDocumentPlatform(document),
isUserEnterprise({
userId: document.userId,
teamId: document.teamId ?? undefined,
}),
]);
const { derivedRecipientAccessAuth } = extractDocumentAuthMethods({ const { derivedRecipientAccessAuth } = extractDocumentAuthMethods({
documentAuth: document.authOptions, documentAuth: document.authOptions,
}); });
@@ -74,6 +85,12 @@ export default async function EmbedSignDocumentPage({ params }: EmbedSignDocumen
); );
} }
const team = document.teamId
? await getTeamById({ teamId: document.teamId, userId: document.userId }).catch(() => null)
: null;
const hidePoweredBy = team?.teamGlobalSettings?.brandingHidePoweredBy ?? false;
return ( return (
<SigningProvider <SigningProvider
email={recipient.email} email={recipient.email}
@@ -93,6 +110,8 @@ export default async function EmbedSignDocumentPage({ params }: EmbedSignDocumen
fields={fields} fields={fields}
metadata={document.documentMeta} metadata={document.documentMeta}
isCompleted={document.status === DocumentStatus.COMPLETED} isCompleted={document.status === DocumentStatus.COMPLETED}
hidePoweredBy={isPlatformDocument || isEnterpriseDocument || hidePoweredBy}
isPlatformOrEnterprise={isPlatformDocument || isEnterpriseDocument}
/> />
</DocumentAuthProvider> </DocumentAuthProvider>
</SigningProvider> </SigningProvider>

View File

@@ -0,0 +1,20 @@
import { type TCssVarsSchema, toNativeCssVars } from './css-vars';
export const injectCss = (options: { css?: string; cssVars?: TCssVarsSchema }) => {
const { css, cssVars } = options;
if (css) {
const style = document.createElement('style');
style.innerHTML = css;
document.head.appendChild(style);
}
if (cssVars) {
const nativeVars = toNativeCssVars(cssVars);
for (const [key, value] of Object.entries(nativeVars)) {
document.documentElement.style.setProperty(key, value);
}
}
};

View File

@@ -25,8 +25,6 @@ export const StackAvatar = ({ first, zIndex, fallbackText = '', type }: StackAva
zIndexClass = ZIndexes[zIndex] ?? ''; zIndexClass = ZIndexes[zIndex] ?? '';
} }
console.log({ type, fallbackText });
switch (type) { switch (type) {
case RecipientStatusType.UNSIGNED: case RecipientStatusType.UNSIGNED:
classes = 'bg-dawn-200 text-dawn-900'; classes = 'bg-dawn-200 text-dawn-900';

View File

@@ -6,22 +6,14 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, msg } from '@lingui/macro'; import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react'; import { useLingui } from '@lingui/react';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { useSession } from 'next-auth/react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import type { z } from 'zod'; import type { z } from 'zod';
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app'; import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { DocumentVisibility } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react'; import { trpc } from '@documenso/trpc/react';
import { ZUpdateTeamMutationSchema } from '@documenso/trpc/server/team-router/schema'; import { ZUpdateTeamMutationSchema } from '@documenso/trpc/server/team-router/schema';
import {
DocumentVisibilitySelect,
DocumentVisibilityTooltip,
} from '@documenso/ui/components/document/document-visibility-select';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { Checkbox } from '@documenso/ui/primitives/checkbox';
import { import {
Form, Form,
FormControl, FormControl,
@@ -37,29 +29,17 @@ export type UpdateTeamDialogProps = {
teamId: number; teamId: number;
teamName: string; teamName: string;
teamUrl: string; teamUrl: string;
documentVisibility?: DocumentVisibility;
includeSenderDetails?: boolean;
}; };
const ZUpdateTeamFormSchema = ZUpdateTeamMutationSchema.shape.data.pick({ const ZUpdateTeamFormSchema = ZUpdateTeamMutationSchema.shape.data.pick({
name: true, name: true,
url: true, url: true,
documentVisibility: true,
includeSenderDetails: true,
}); });
type TUpdateTeamFormSchema = z.infer<typeof ZUpdateTeamFormSchema>; type TUpdateTeamFormSchema = z.infer<typeof ZUpdateTeamFormSchema>;
export const UpdateTeamForm = ({ export const UpdateTeamForm = ({ teamId, teamName, teamUrl }: UpdateTeamDialogProps) => {
teamId,
teamName,
teamUrl,
documentVisibility,
includeSenderDetails,
}: UpdateTeamDialogProps) => {
const router = useRouter(); const router = useRouter();
const { data: session } = useSession();
const email = session?.user?.email;
const { _ } = useLingui(); const { _ } = useLingui();
const { toast } = useToast(); const { toast } = useToast();
@@ -68,36 +48,17 @@ export const UpdateTeamForm = ({
defaultValues: { defaultValues: {
name: teamName, name: teamName,
url: teamUrl, url: teamUrl,
documentVisibility,
includeSenderDetails,
}, },
}); });
const { mutateAsync: updateTeam } = trpc.team.updateTeam.useMutation(); const { mutateAsync: updateTeam } = trpc.team.updateTeam.useMutation();
const includeSenderDetailsCheck = form.watch('includeSenderDetails');
const mapVisibilityToRole = (visibility: DocumentVisibility): DocumentVisibility => const onFormSubmit = async ({ name, url }: TUpdateTeamFormSchema) => {
match(visibility)
.with(DocumentVisibility.ADMIN, () => DocumentVisibility.ADMIN)
.with(DocumentVisibility.MANAGER_AND_ABOVE, () => DocumentVisibility.MANAGER_AND_ABOVE)
.otherwise(() => DocumentVisibility.EVERYONE);
const currentVisibilityRole = mapVisibilityToRole(
documentVisibility ?? DocumentVisibility.EVERYONE,
);
const onFormSubmit = async ({
name,
url,
documentVisibility,
includeSenderDetails,
}: TUpdateTeamFormSchema) => {
try { try {
await updateTeam({ await updateTeam({
data: { data: {
name, name,
url, url,
documentVisibility,
includeSenderDetails,
}, },
teamId, teamId,
}); });
@@ -111,8 +72,6 @@ export const UpdateTeamForm = ({
form.reset({ form.reset({
name, name,
url, url,
documentVisibility,
includeSenderDetails,
}); });
if (url !== teamUrl) { if (url !== teamUrl) {
@@ -186,68 +145,6 @@ export const UpdateTeamForm = ({
)} )}
/> />
<FormField
control={form.control}
name="documentVisibility"
render={({ field }) => (
<FormItem>
<FormLabel className="mt-4 flex flex-row items-center">
<Trans>Default Document Visibility</Trans>
<DocumentVisibilityTooltip />
</FormLabel>
<FormControl>
<DocumentVisibilitySelect
currentMemberRole={currentVisibilityRole}
isTeamSettings={true}
{...field}
onValueChange={field.onChange}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="mb-4">
<FormField
control={form.control}
name="includeSenderDetails"
render={({ field }) => (
<FormItem>
<div className="mt-6 flex flex-row items-center gap-4">
<FormLabel>
<Trans>Send on Behalf of Team</Trans>
</FormLabel>
<FormControl>
<Checkbox
className="h-5 w-5"
checkClassName="text-white"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</div>
{includeSenderDetailsCheck ? (
<blockquote className="text-foreground/50 text-xs italic">
<Trans>
"{email}" on behalf of "{teamName}" has invited you to sign "example
document".
</Trans>
</blockquote>
) : (
<blockquote className="text-foreground/50 text-xs italic">
<Trans>"{teamUrl}" has invited you to sign "example document".</Trans>
</blockquote>
)}
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex flex-row justify-end space-x-4"> <div className="flex flex-row justify-end space-x-4">
<AnimatePresence> <AnimatePresence>
{form.formState.isDirty && ( {form.formState.isDirty && (

View File

@@ -167,6 +167,7 @@ export const DocumentHistorySheet = ({
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_CREATED }, { type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_CREATED },
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED }, { type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED },
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED }, { type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED },
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RESTORED },
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED }, { type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED },
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED }, { type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED },
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED }, { type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED },

View File

@@ -3,7 +3,7 @@ import type { HTMLAttributes } from 'react';
import type { MessageDescriptor } from '@lingui/core'; import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/macro'; import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react'; import { useLingui } from '@lingui/react';
import { CheckCircle2, Clock, File } from 'lucide-react'; import { CheckCircle2, Clock, File, TrashIcon } from 'lucide-react';
import type { LucideIcon } from 'lucide-react/dist/lucide-react'; import type { LucideIcon } from 'lucide-react/dist/lucide-react';
import type { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status'; import type { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
@@ -47,6 +47,12 @@ export const FRIENDLY_STATUS_MAP: Record<ExtendedDocumentStatus, FriendlyStatus>
labelExtended: msg`Document All`, labelExtended: msg`Document All`,
color: 'text-muted-foreground', color: 'text-muted-foreground',
}, },
BIN: {
label: msg`Bin`,
labelExtended: msg`Document Bin`,
icon: TrashIcon,
color: 'text-red-500 dark:text-red-200',
},
}; };
export type DocumentStatusProps = HTMLAttributes<HTMLSpanElement> & { export type DocumentStatusProps = HTMLAttributes<HTMLSpanElement> & {

View File

@@ -138,6 +138,7 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
containerClassName={cn('rounded-lg border bg-background')} containerClassName={cn('rounded-lg border bg-background')}
defaultValue={user.signature ?? undefined} defaultValue={user.signature ?? undefined}
onChange={(v) => onChange(v ?? '')} onChange={(v) => onChange(v ?? '')}
allowTypedSignature={true}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />

796
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"private": true, "private": true,
"version": "1.8.0-rc.2", "version": "1.8.1-rc.1",
"scripts": { "scripts": {
"build": "turbo run build", "build": "turbo run build",
"build:web": "turbo run build --filter=@documenso/web", "build:web": "turbo run build --filter=@documenso/web",
@@ -52,7 +52,7 @@
"husky": "^9.0.11", "husky": "^9.0.11",
"lint-staged": "^15.2.2", "lint-staged": "^15.2.2",
"playwright": "1.43.0", "playwright": "1.43.0",
"prettier": "^2.5.1", "prettier": "^3.3.3",
"rimraf": "^5.0.1", "rimraf": "^5.0.1",
"turbo": "^1.9.3" "turbo": "^1.9.3"
}, },

View File

@@ -168,6 +168,9 @@ export const ApiContractV1 = c.router(
500: ZUnsuccessfulResponseSchema, 500: ZUnsuccessfulResponseSchema,
}, },
summary: 'Send a document for signing', summary: 'Send a document for signing',
// I'm aware this should be in the variable itself, which it is, however it's difficult for users to find in our current UI.
description:
'Notes\n\n`sendEmail` - Whether to send an email to the recipients asking them to action the document. If you disable this, you will need to manually distribute the document to the recipients using the generated signing links. Defaults to true',
}, },
resendDocument: { resendDocument: {

View File

@@ -302,6 +302,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
redirectUrl: body.meta.redirectUrl, redirectUrl: body.meta.redirectUrl,
signingOrder: body.meta.signingOrder, signingOrder: body.meta.signingOrder,
language: body.meta.language, language: body.meta.language,
typedSignatureEnabled: body.meta.typedSignatureEnabled,
requestMetadata: extractNextApiRequestMetadata(args.req), requestMetadata: extractNextApiRequestMetadata(args.req),
}); });

View File

@@ -3,7 +3,6 @@ import { z } from 'zod';
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats'; import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n'; import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
import '@documenso/lib/constants/time-zones';
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@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 { ZUrlSchema } from '@documenso/lib/schemas/common';
import { import {
@@ -14,6 +13,7 @@ import {
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta'; import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
import { import {
DocumentDataType, DocumentDataType,
DocumentDistributionMethod,
DocumentSigningOrder, DocumentSigningOrder,
FieldType, FieldType,
ReadStatus, ReadStatus,
@@ -67,7 +67,10 @@ export type TSuccessfulDocumentResponseSchema = z.infer<typeof ZSuccessfulDocume
export const ZSendDocumentForSigningMutationSchema = z export const ZSendDocumentForSigningMutationSchema = z
.object({ .object({
sendEmail: z.boolean().optional().default(true), sendEmail: z.boolean().optional().default(true).openapi({
description:
'Whether to send an email to the recipients asking them to action the document. If you disable this, you will need to manually distribute the document to the recipients using the generated signing links.',
}),
}) })
.or(z.literal('').transform(() => ({ sendEmail: true }))); .or(z.literal('').transform(() => ({ sendEmail: true })));
@@ -129,6 +132,7 @@ export const ZCreateDocumentMutationSchema = z.object({
redirectUrl: z.string(), redirectUrl: z.string(),
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(), signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(), language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
typedSignatureEnabled: z.boolean().optional().default(true),
}) })
.partial(), .partial(),
authOptions: z authOptions: z
@@ -223,14 +227,14 @@ export type TCreateDocumentFromTemplateMutationResponseSchema = z.infer<
export const ZGenerateDocumentFromTemplateMutationSchema = z.object({ export const ZGenerateDocumentFromTemplateMutationSchema = z.object({
title: z.string().optional(), title: z.string().optional(),
externalId: z.string().nullish(), externalId: z.string().optional(),
recipients: z recipients: z
.array( .array(
z.object({ z.object({
id: z.number(), id: z.number(),
email: z.string().email(),
name: z.string().optional(), name: z.string().optional(),
email: z.string().email().min(1), signingOrder: z.number().optional(),
signingOrder: z.number().nullish(),
}), }),
) )
.refine( .refine(
@@ -249,8 +253,10 @@ export const ZGenerateDocumentFromTemplateMutationSchema = z.object({
timezone: z.string(), timezone: z.string(),
dateFormat: z.string(), dateFormat: z.string(),
redirectUrl: ZUrlSchema, redirectUrl: ZUrlSchema,
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(), signingOrder: z.nativeEnum(DocumentSigningOrder),
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(), language: z.enum(SUPPORTED_LANGUAGE_CODES),
distributionMethod: z.nativeEnum(DocumentDistributionMethod),
typedSignatureEnabled: z.boolean(),
}) })
.partial() .partial()
.optional(), .optional(),

View File

@@ -0,0 +1,271 @@
import { expect, test } from '@playwright/test';
import { PDFDocument } from 'pdf-lib';
import { getDocumentByToken } from '@documenso/lib/server-only/document/get-document-by-token';
import { getFile } from '@documenso/lib/universal/upload/get-file';
import { prisma } from '@documenso/prisma';
import { DocumentStatus, FieldType } from '@documenso/prisma/client';
import { seedPendingDocumentWithFullFields } from '@documenso/prisma/seed/documents';
import { seedTeam } from '@documenso/prisma/seed/teams';
import { seedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication';
test.describe('Signing Certificate Tests', () => {
test('individual document should always include signing certificate', async ({ page }) => {
const user = await seedUser();
const { document, recipients } = await seedPendingDocumentWithFullFields({
owner: user,
recipients: ['signer@example.com'],
fields: [FieldType.SIGNATURE],
});
const documentData = await prisma.documentData
.findFirstOrThrow({
where: {
id: document.documentDataId,
},
})
.then(async (data) => getFile(data));
const originalPdf = await PDFDocument.load(documentData);
const recipient = recipients[0];
// Sign the document
await page.goto(`/sign/${recipient.token}`);
const canvas = page.locator('canvas');
const box = await canvas.boundingBox();
if (box) {
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
await page.mouse.down();
await page.mouse.move(box.x + box.width / 4, box.y + box.height / 4);
await page.mouse.up();
}
for (const field of recipient.Field) {
await page.locator(`#field-${field.id}`).getByRole('button').click();
await expect(page.locator(`#field-${field.id}`)).toHaveAttribute('data-inserted', 'true');
}
await page.getByRole('button', { name: 'Complete' }).click();
await page.getByRole('button', { name: 'Sign' }).click();
await page.waitForURL(`/sign/${recipient.token}/complete`);
await expect(async () => {
const { status } = await getDocumentByToken({
token: recipient.token,
});
expect(status).toBe(DocumentStatus.COMPLETED);
}).toPass();
// Get the completed document
const completedDocument = await prisma.document.findFirstOrThrow({
where: { id: document.id },
include: { documentData: true },
});
const completedDocumentData = await getFile(completedDocument.documentData);
// Load the PDF and check number of pages
const pdfDoc = await PDFDocument.load(completedDocumentData);
expect(pdfDoc.getPageCount()).toBe(originalPdf.getPageCount() + 1); // Original + Certificate
});
test('team document with signing certificate enabled should include certificate', async ({
page,
}) => {
const team = await seedTeam();
const { document, recipients } = await seedPendingDocumentWithFullFields({
owner: team.owner,
recipients: ['signer@example.com'],
fields: [FieldType.SIGNATURE],
updateDocumentOptions: {
teamId: team.id,
},
});
await prisma.teamGlobalSettings.create({
data: {
teamId: team.id,
includeSigningCertificate: true,
},
});
const documentData = await prisma.documentData
.findFirstOrThrow({
where: {
id: document.documentDataId,
},
})
.then(async (data) => getFile(data));
const originalPdf = await PDFDocument.load(documentData);
const recipient = recipients[0];
// Sign the document
await page.goto(`/sign/${recipient.token}`);
const canvas = page.locator('canvas');
const box = await canvas.boundingBox();
if (box) {
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
await page.mouse.down();
await page.mouse.move(box.x + box.width / 4, box.y + box.height / 4);
await page.mouse.up();
}
for (const field of recipient.Field) {
await page.locator(`#field-${field.id}`).getByRole('button').click();
await expect(page.locator(`#field-${field.id}`)).toHaveAttribute('data-inserted', 'true');
}
await page.getByRole('button', { name: 'Complete' }).click();
await page.getByRole('button', { name: 'Sign' }).click();
await page.waitForURL(`/sign/${recipient.token}/complete`);
await expect(async () => {
const { status } = await getDocumentByToken({
token: recipient.token,
});
expect(status).toBe(DocumentStatus.COMPLETED);
}).toPass();
// Get the completed document
const completedDocument = await prisma.document.findFirstOrThrow({
where: { id: document.id },
include: { documentData: true },
});
const completedDocumentData = await getFile(completedDocument.documentData);
// Load the PDF and check number of pages
const completedPdf = await PDFDocument.load(completedDocumentData);
expect(completedPdf.getPageCount()).toBe(originalPdf.getPageCount() + 1); // Original + Certificate
});
test('team document with signing certificate disabled should not include certificate', async ({
page,
}) => {
const team = await seedTeam();
const { document, recipients } = await seedPendingDocumentWithFullFields({
owner: team.owner,
recipients: ['signer@example.com'],
fields: [FieldType.SIGNATURE],
updateDocumentOptions: {
teamId: team.id,
},
});
await prisma.teamGlobalSettings.create({
data: {
teamId: team.id,
includeSigningCertificate: false,
},
});
const documentData = await prisma.documentData
.findFirstOrThrow({
where: {
id: document.documentDataId,
},
})
.then(async (data) => getFile(data));
const originalPdf = await PDFDocument.load(documentData);
const recipient = recipients[0];
// Sign the document
await page.goto(`/sign/${recipient.token}`);
const canvas = page.locator('canvas');
const box = await canvas.boundingBox();
if (box) {
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
await page.mouse.down();
await page.mouse.move(box.x + box.width / 4, box.y + box.height / 4);
await page.mouse.up();
}
for (const field of recipient.Field) {
await page.locator(`#field-${field.id}`).getByRole('button').click();
await expect(page.locator(`#field-${field.id}`)).toHaveAttribute('data-inserted', 'true');
}
await page.getByRole('button', { name: 'Complete' }).click();
await page.getByRole('button', { name: 'Sign' }).click();
await page.waitForURL(`/sign/${recipient.token}/complete`);
await expect(async () => {
const { status } = await getDocumentByToken({
token: recipient.token,
});
expect(status).toBe(DocumentStatus.COMPLETED);
}).toPass();
// Get the completed document
const completedDocument = await prisma.document.findFirstOrThrow({
where: { id: document.id },
include: { documentData: true },
});
const completedDocumentData = await getFile(completedDocument.documentData);
// Load the PDF and check number of pages
const completedPdf = await PDFDocument.load(completedDocumentData);
expect(completedPdf.getPageCount()).toBe(originalPdf.getPageCount());
});
test('team can toggle signing certificate setting', async ({ page }) => {
const team = await seedTeam();
await apiSignin({
page,
email: team.owner.email,
redirectPath: `/t/${team.url}/settings/preferences`,
});
// Toggle signing certificate setting
await page.getByLabel('Include the Signing Certificate in the Document').click();
await page.getByRole('button', { name: /Save/ }).first().click();
await page.waitForTimeout(1000);
// Verify the setting was saved
const updatedTeam = await prisma.team.findFirstOrThrow({
where: { id: team.id },
include: { teamGlobalSettings: true },
});
expect(updatedTeam.teamGlobalSettings?.includeSigningCertificate).toBe(false);
// Toggle the setting back to true
await page.getByLabel('Include the Signing Certificate in the Document').click();
await page.getByRole('button', { name: /Save/ }).first().click();
await page.waitForTimeout(1000);
// Verify the setting was saved
const updatedTeam2 = await prisma.team.findFirstOrThrow({
where: { id: team.id },
include: { teamGlobalSettings: true },
});
expect(updatedTeam2.teamGlobalSettings?.includeSigningCertificate).toBe(true);
});
});

View File

@@ -17,19 +17,17 @@ test('[TEAMS]: update the default document visibility in the team global setting
page, page,
email: team.owner.email, email: team.owner.email,
password: 'password', password: 'password',
redirectPath: `/t/${team.url}/settings`, redirectPath: `/t/${team.url}/settings/preferences`,
}); });
await page.getByRole('combobox').click(); // !: Brittle selector
await page.getByRole('combobox').first().click();
await page.getByRole('option', { name: 'Admin' }).click(); await page.getByRole('option', { name: 'Admin' }).click();
await page.getByRole('button', { name: 'Update team' }).click(); await page.getByRole('button', { name: 'Save' }).first().click();
const toast = page.locator('li[role="status"][data-state="open"]').first(); const toast = page.locator('li[role="status"][data-state="open"]').first();
await expect(toast).toBeVisible(); await expect(toast).toBeVisible();
await expect(toast.getByText('Success', { exact: true })).toBeVisible(); await expect(toast.getByText('Document preferences updated', { exact: true })).toBeVisible();
await expect(
toast.getByText('Your team has been successfully updated.', { exact: true }),
).toBeVisible();
}); });
test('[TEAMS]: update the sender details in the team global settings', async ({ page }) => { test('[TEAMS]: update the sender details in the team global settings', async ({ page }) => {
@@ -41,7 +39,7 @@ test('[TEAMS]: update the sender details in the team global settings', async ({
page, page,
email: team.owner.email, email: team.owner.email,
password: 'password', password: 'password',
redirectPath: `/t/${team.url}/settings`, redirectPath: `/t/${team.url}/settings/preferences`,
}); });
const checkbox = page.getByLabel('Send on Behalf of Team'); const checkbox = page.getByLabel('Send on Behalf of Team');
@@ -49,14 +47,11 @@ test('[TEAMS]: update the sender details in the team global settings', async ({
await expect(checkbox).toBeChecked(); await expect(checkbox).toBeChecked();
await page.getByRole('button', { name: 'Update team' }).click(); await page.getByRole('button', { name: 'Save' }).first().click();
const toast = page.locator('li[role="status"][data-state="open"]').first(); const toast = page.locator('li[role="status"][data-state="open"]').first();
await expect(toast).toBeVisible(); await expect(toast).toBeVisible();
await expect(toast.getByText('Success', { exact: true })).toBeVisible(); await expect(toast.getByText('Document preferences updated', { exact: true })).toBeVisible();
await expect(
toast.getByText('Your team has been successfully updated.', { exact: true }),
).toBeVisible();
await expect(checkbox).toBeChecked(); await expect(checkbox).toBeChecked();
}); });

View File

@@ -7,15 +7,17 @@
"scripts": { "scripts": {
"test:dev": "NODE_OPTIONS=--experimental-require-module playwright test", "test:dev": "NODE_OPTIONS=--experimental-require-module playwright test",
"test-ui:dev": "NODE_OPTIONS=--experimental-require-module playwright test --ui", "test-ui:dev": "NODE_OPTIONS=--experimental-require-module playwright test --ui",
"test:e2e": "NODE_OPTIONS=--experimental-require-module start-server-and-test \"npm run start -w @documenso/web\" http://localhost:3000 \"playwright test\"" "test:e2e": "NODE_OPTIONS=--experimental-require-module start-server-and-test \"npm run start -w @documenso/web\" http://localhost:3000 \"playwright test $E2E_TEST_PATH\""
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.18.1", "@playwright/test": "^1.18.1",
"@types/node": "^20.8.2", "@types/node": "^20.8.2",
"@documenso/lib": "*",
"@documenso/prisma": "*", "@documenso/prisma": "*",
"@documenso/web": "*" "@documenso/web": "*",
"pdf-lib": "^1.17.1"
}, },
"dependencies": { "dependencies": {
"start-server-and-test": "^2.0.1" "start-server-and-test": "^2.0.1"

View File

@@ -9,6 +9,7 @@ export const getDocumentRelatedPrices = async () => {
return await getPricesByPlan([ return await getPricesByPlan([
STRIPE_PLAN_TYPE.REGULAR, STRIPE_PLAN_TYPE.REGULAR,
STRIPE_PLAN_TYPE.COMMUNITY, STRIPE_PLAN_TYPE.COMMUNITY,
STRIPE_PLAN_TYPE.PLATFORM,
STRIPE_PLAN_TYPE.ENTERPRISE, STRIPE_PLAN_TYPE.ENTERPRISE,
]); ]);
}; };

View File

@@ -0,0 +1,13 @@
import { STRIPE_PLAN_TYPE } from '@documenso/lib/constants/billing';
import { getPricesByPlan } from './get-prices-by-plan';
export const getPlatformPlanPrices = async () => {
return await getPricesByPlan(STRIPE_PLAN_TYPE.PLATFORM);
};
export const getPlatformPlanPriceIds = async () => {
const prices = await getPlatformPlanPrices();
return prices.map((price) => price.id);
};

View File

@@ -9,6 +9,7 @@ export const getPrimaryAccountPlanPrices = async () => {
return await getPricesByPlan([ return await getPricesByPlan([
STRIPE_PLAN_TYPE.REGULAR, STRIPE_PLAN_TYPE.REGULAR,
STRIPE_PLAN_TYPE.COMMUNITY, STRIPE_PLAN_TYPE.COMMUNITY,
STRIPE_PLAN_TYPE.PLATFORM,
STRIPE_PLAN_TYPE.ENTERPRISE, STRIPE_PLAN_TYPE.ENTERPRISE,
]); ]);
}; };

View File

@@ -6,7 +6,11 @@ import { getPricesByPlan } from './get-prices-by-plan';
* Returns the Stripe prices of items that affect the amount of teams a user can create. * Returns the Stripe prices of items that affect the amount of teams a user can create.
*/ */
export const getTeamRelatedPrices = async () => { export const getTeamRelatedPrices = async () => {
return await getPricesByPlan([STRIPE_PLAN_TYPE.COMMUNITY, STRIPE_PLAN_TYPE.ENTERPRISE]); return await getPricesByPlan([
STRIPE_PLAN_TYPE.COMMUNITY,
STRIPE_PLAN_TYPE.PLATFORM,
STRIPE_PLAN_TYPE.ENTERPRISE,
]);
}; };
/** /**

View File

@@ -0,0 +1,61 @@
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { subscriptionsContainsActivePlan } from '@documenso/lib/utils/billing';
import { prisma } from '@documenso/prisma';
import type { Document, Subscription } from '@documenso/prisma/client';
import { getPlatformPlanPriceIds } from '../stripe/get-platform-plan-prices';
export type IsDocumentPlatformOptions = Pick<Document, 'id' | 'userId' | 'teamId'>;
/**
* Whether the user is platform, or has permission to use platform features on
* behalf of their team.
*
* It is assumed that the provided user is part of the provided team.
*/
export const isDocumentPlatform = async ({
userId,
teamId,
}: IsDocumentPlatformOptions): Promise<boolean> => {
let subscriptions: Subscription[] = [];
if (!IS_BILLING_ENABLED()) {
return true;
}
if (teamId) {
subscriptions = await prisma.team
.findFirstOrThrow({
where: {
id: teamId,
},
select: {
owner: {
include: {
Subscription: true,
},
},
},
})
.then((team) => team.owner.Subscription);
} else {
subscriptions = await prisma.user
.findFirstOrThrow({
where: {
id: userId,
},
select: {
Subscription: true,
},
})
.then((user) => user.Subscription);
}
if (subscriptions.length === 0) {
return false;
}
const platformPlanPriceIds = await getPlatformPlanPriceIds();
return subscriptionsContainsActivePlan(subscriptions, platformPlanPriceIds);
};

View File

@@ -7,5 +7,6 @@ export enum STRIPE_PLAN_TYPE {
REGULAR = 'regular', REGULAR = 'regular',
TEAM = 'team', TEAM = 'team',
COMMUNITY = 'community', COMMUNITY = 'community',
PLATFORM = 'platform',
ENTERPRISE = 'enterprise', ENTERPRISE = 'enterprise',
} }

View File

@@ -17,12 +17,14 @@ const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
documentVisibility: z.nativeEnum(DocumentVisibility), documentVisibility: z.nativeEnum(DocumentVisibility),
documentLanguage: z.string(), documentLanguage: z.string(),
includeSenderDetails: z.boolean(), includeSenderDetails: z.boolean(),
includeSigningCertificate: z.boolean(),
brandingEnabled: z.boolean(), brandingEnabled: z.boolean(),
brandingLogo: z.string(), brandingLogo: z.string(),
brandingUrl: z.string(), brandingUrl: z.string(),
brandingCompanyDetails: z.string(), brandingCompanyDetails: z.string(),
brandingHidePoweredBy: z.boolean(), brandingHidePoweredBy: z.boolean(),
teamId: z.number(), teamId: z.number(),
typedSignatureEnabled: z.boolean(),
}) })
.nullish(), .nullish(),
}), }),

View File

@@ -57,7 +57,17 @@ export const SEAL_DOCUMENT_JOB_DEFINITION = {
}, },
}, },
include: { include: {
documentMeta: true,
Recipient: true, Recipient: true,
team: {
select: {
teamGlobalSettings: {
select: {
includeSigningCertificate: true,
},
},
},
},
}, },
}); });
@@ -117,7 +127,13 @@ export const SEAL_DOCUMENT_JOB_DEFINITION = {
} }
const pdfData = await getFile(documentData); const pdfData = await getFile(documentData);
const certificateData = await getCertificatePdf({ documentId }).catch(() => null); const certificateData =
(document.team?.teamGlobalSettings?.includeSigningCertificate ?? true)
? await getCertificatePdf({
documentId,
language: document.documentMeta?.language,
}).catch(() => null)
: null;
const newDataId = await io.runTask('decorate-and-sign-pdf', async () => { const newDataId = await io.runTask('decorate-and-sign-pdf', async () => {
const pdfDoc = await PDFDocument.load(pdfData); const pdfDoc = await PDFDocument.load(pdfData);

View File

@@ -51,7 +51,7 @@
"pg": "^8.11.3", "pg": "^8.11.3",
"playwright": "1.43.0", "playwright": "1.43.0",
"react": "^18", "react": "^18",
"remeda": "^2.12.1", "remeda": "^2.17.3",
"sharp": "0.32.6", "sharp": "0.32.6",
"stripe": "^12.7.0", "stripe": "^12.7.0",
"ts-pattern": "^5.0.5", "ts-pattern": "^5.0.5",

View File

@@ -1,21 +1,25 @@
import { base32 } from '@scure/base'; import { base32 } from '@scure/base';
import { TOTPController } from 'oslo/otp'; import { generateHOTP } from 'oslo/otp';
import type { User } from '@documenso/prisma/client'; import type { User } from '@documenso/prisma/client';
import { DOCUMENSO_ENCRYPTION_KEY } from '../../constants/crypto'; import { DOCUMENSO_ENCRYPTION_KEY } from '../../constants/crypto';
import { symmetricDecrypt } from '../../universal/crypto'; import { symmetricDecrypt } from '../../universal/crypto';
const totp = new TOTPController();
type VerifyTwoFactorAuthenticationTokenOptions = { type VerifyTwoFactorAuthenticationTokenOptions = {
user: User; user: User;
totpCode: string; totpCode: string;
// The number of windows to look back
window?: number;
// The duration that the token is valid for in seconds
period?: number;
}; };
export const verifyTwoFactorAuthenticationToken = async ({ export const verifyTwoFactorAuthenticationToken = async ({
user, user,
totpCode, totpCode,
window = 1,
period = 30_000,
}: VerifyTwoFactorAuthenticationTokenOptions) => { }: VerifyTwoFactorAuthenticationTokenOptions) => {
const key = DOCUMENSO_ENCRYPTION_KEY; const key = DOCUMENSO_ENCRYPTION_KEY;
@@ -27,7 +31,21 @@ export const verifyTwoFactorAuthenticationToken = async ({
'utf-8', 'utf-8',
); );
const isValidToken = await totp.verify(totpCode, base32.decode(secret)); const decodedSecret = base32.decode(secret);
return isValidToken; let now = Date.now();
for (let i = 0; i < window; i++) {
const counter = Math.floor(now / period);
const hotp = await generateHOTP(decodedSecret, counter);
if (totpCode === hotp) {
return true;
}
now -= period;
}
return false;
}; };

View File

@@ -13,6 +13,7 @@ export const getDocumentStats = async () => {
[ExtendedDocumentStatus.DRAFT]: 0, [ExtendedDocumentStatus.DRAFT]: 0,
[ExtendedDocumentStatus.PENDING]: 0, [ExtendedDocumentStatus.PENDING]: 0,
[ExtendedDocumentStatus.COMPLETED]: 0, [ExtendedDocumentStatus.COMPLETED]: 0,
[ExtendedDocumentStatus.BIN]: 0,
[ExtendedDocumentStatus.ALL]: 0, [ExtendedDocumentStatus.ALL]: 0,
}; };

View File

@@ -112,6 +112,7 @@ export const createDocument = async ({
documentMeta: { documentMeta: {
create: { create: {
language: team?.teamGlobalSettings?.documentLanguage, language: team?.teamGlobalSettings?.documentLanguage,
typedSignatureEnabled: team?.teamGlobalSettings?.typedSignatureEnabled,
}, },
}, },
}, },

View File

@@ -158,6 +158,16 @@ const handleDocumentOwnerDelete = async ({
}), }),
}); });
// Soft delete for document recipients since the owner is deleting it
await tx.recipient.updateMany({
where: {
documentId: document.id,
},
data: {
documentDeletedAt: new Date().toISOString(),
},
});
return await tx.document.update({ return await tx.document.update({
where: { where: {
id: document.id, id: document.id,

View File

@@ -64,6 +64,7 @@ export const findDocumentAuditLogs = async ({
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED, DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED,
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_CREATED, DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_CREATED,
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED, DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED,
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RESTORED,
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED, DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED,
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED, DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED,
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED, DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED,

View File

@@ -2,15 +2,8 @@ import { DateTime } from 'luxon';
import { P, match } from 'ts-pattern'; import { P, match } from 'ts-pattern';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import { RecipientRole, SigningStatus, TeamMemberRole } from '@documenso/prisma/client'; import type { Document, DocumentSource, Team, TeamEmail, User } from '@documenso/prisma/client';
import type { import { Prisma, RecipientRole, SigningStatus, TeamMemberRole } from '@documenso/prisma/client';
Document,
DocumentSource,
Prisma,
Team,
TeamEmail,
User,
} from '@documenso/prisma/client';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status'; import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
import { DocumentVisibility } from '../../types/document-visibility'; import { DocumentVisibility } from '../../types/document-visibility';
@@ -88,14 +81,12 @@ export const findDocuments = async ({
const teamMemberRole = team?.members[0].role ?? null; const teamMemberRole = team?.members[0].role ?? null;
const termFilters = match(term) const termFilters = match(term)
.with(P.string.minLength(1), () => { .with(P.string.minLength(1), () => ({
return { title: {
title: { contains: term,
contains: term, mode: Prisma.QueryMode.insensitive,
mode: 'insensitive', },
}, }))
} as const;
})
.otherwise(() => undefined); .otherwise(() => undefined);
const searchFilter: Prisma.DocumentWhereInput = { const searchFilter: Prisma.DocumentWhereInput = {
@@ -141,6 +132,8 @@ export const findDocuments = async ({
let filters: Prisma.DocumentWhereInput | null = findDocumentsFilter(status, user); let filters: Prisma.DocumentWhereInput | null = findDocumentsFilter(status, user);
console.log('find documets team', team);
if (team) { if (team) {
filters = findTeamDocumentsFilter(status, team, visibilityFilters); filters = findTeamDocumentsFilter(status, team, visibilityFilters);
} }
@@ -293,19 +286,21 @@ export const findDocuments = async ({
} satisfies FindResultSet<typeof data>; } satisfies FindResultSet<typeof data>;
}; };
const findDocumentsFilter = (status: ExtendedDocumentStatus, user: User) => { export const findDocumentsFilter = (status: ExtendedDocumentStatus, user: User) => {
return match<ExtendedDocumentStatus, Prisma.DocumentWhereInput>(status) return match<ExtendedDocumentStatus, Prisma.DocumentWhereInput>(status)
.with(ExtendedDocumentStatus.ALL, () => ({ .with(ExtendedDocumentStatus.ALL, () => ({
OR: [ OR: [
{ {
userId: user.id, userId: user.id,
teamId: null, teamId: null,
deletedAt: null,
}, },
{ {
status: ExtendedDocumentStatus.COMPLETED, status: ExtendedDocumentStatus.COMPLETED,
Recipient: { Recipient: {
some: { some: {
email: user.email, email: user.email,
documentDeletedAt: null,
}, },
}, },
}, },
@@ -314,6 +309,7 @@ const findDocumentsFilter = (status: ExtendedDocumentStatus, user: User) => {
Recipient: { Recipient: {
some: { some: {
email: user.email, email: user.email,
documentDeletedAt: null,
}, },
}, },
}, },
@@ -330,6 +326,7 @@ const findDocumentsFilter = (status: ExtendedDocumentStatus, user: User) => {
role: { role: {
not: RecipientRole.CC, not: RecipientRole.CC,
}, },
documentDeletedAt: null,
}, },
}, },
})) }))
@@ -344,6 +341,7 @@ const findDocumentsFilter = (status: ExtendedDocumentStatus, user: User) => {
userId: user.id, userId: user.id,
teamId: null, teamId: null,
status: ExtendedDocumentStatus.PENDING, status: ExtendedDocumentStatus.PENDING,
deletedAt: null,
}, },
{ {
status: ExtendedDocumentStatus.PENDING, status: ExtendedDocumentStatus.PENDING,
@@ -354,6 +352,7 @@ const findDocumentsFilter = (status: ExtendedDocumentStatus, user: User) => {
role: { role: {
not: RecipientRole.CC, not: RecipientRole.CC,
}, },
documentDeletedAt: null,
}, },
}, },
}, },
@@ -365,12 +364,49 @@ const findDocumentsFilter = (status: ExtendedDocumentStatus, user: User) => {
userId: user.id, userId: user.id,
teamId: null, teamId: null,
status: ExtendedDocumentStatus.COMPLETED, status: ExtendedDocumentStatus.COMPLETED,
deletedAt: null,
}, },
{ {
status: ExtendedDocumentStatus.COMPLETED, status: ExtendedDocumentStatus.COMPLETED,
Recipient: { Recipient: {
some: { some: {
email: user.email, email: user.email,
documentDeletedAt: null,
},
},
},
],
}))
.with(ExtendedDocumentStatus.BIN, () => ({
OR: [
{
userId: user.id,
teamId: null,
deletedAt: {
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
},
},
{
status: ExtendedDocumentStatus.PENDING,
Recipient: {
some: {
email: user.email,
signingStatus: SigningStatus.SIGNED,
documentDeletedAt: {
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
},
},
},
},
{
status: ExtendedDocumentStatus.COMPLETED,
Recipient: {
some: {
email: user.email,
signingStatus: SigningStatus.SIGNED,
documentDeletedAt: {
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
},
}, },
}, },
}, },
@@ -408,7 +444,7 @@ const findDocumentsFilter = (status: ExtendedDocumentStatus, user: User) => {
* @param team The team to find the documents for. * @param team The team to find the documents for.
* @returns A filter which can be applied to the Prisma Document schema. * @returns A filter which can be applied to the Prisma Document schema.
*/ */
const findTeamDocumentsFilter = ( export const findTeamDocumentsFilter = (
status: ExtendedDocumentStatus, status: ExtendedDocumentStatus,
team: Team & { teamEmail: TeamEmail | null }, team: Team & { teamEmail: TeamEmail | null },
visibilityFilters: Prisma.DocumentWhereInput[], visibilityFilters: Prisma.DocumentWhereInput[],
@@ -418,17 +454,16 @@ const findTeamDocumentsFilter = (
return match<ExtendedDocumentStatus, Prisma.DocumentWhereInput | null>(status) return match<ExtendedDocumentStatus, Prisma.DocumentWhereInput | null>(status)
.with(ExtendedDocumentStatus.ALL, () => { .with(ExtendedDocumentStatus.ALL, () => {
const filter: Prisma.DocumentWhereInput = { const filter: Prisma.DocumentWhereInput = {
// Filter to display all documents that belong to the team.
OR: [ OR: [
{ {
teamId: team.id, teamId: team.id,
deletedAt: null,
OR: visibilityFilters, OR: visibilityFilters,
}, },
], ],
}; };
if (teamEmail && filter.OR) { if (teamEmail && filter.OR) {
// Filter to display all documents received by the team email that are not draft.
filter.OR.push({ filter.OR.push({
status: { status: {
not: ExtendedDocumentStatus.DRAFT, not: ExtendedDocumentStatus.DRAFT,
@@ -438,14 +473,15 @@ const findTeamDocumentsFilter = (
email: teamEmail, email: teamEmail,
}, },
}, },
deletedAt: null,
OR: visibilityFilters, OR: visibilityFilters,
}); });
// Filter to display all documents that have been sent by the team email.
filter.OR.push({ filter.OR.push({
User: { User: {
email: teamEmail, email: teamEmail,
}, },
deletedAt: null,
OR: visibilityFilters, OR: visibilityFilters,
}); });
} }
@@ -453,7 +489,6 @@ const findTeamDocumentsFilter = (
return filter; return filter;
}) })
.with(ExtendedDocumentStatus.INBOX, () => { .with(ExtendedDocumentStatus.INBOX, () => {
// Return a filter that will return nothing.
if (!teamEmail) { if (!teamEmail) {
return null; return null;
} }
@@ -471,6 +506,7 @@ const findTeamDocumentsFilter = (
}, },
}, },
}, },
deletedAt: null,
OR: visibilityFilters, OR: visibilityFilters,
}; };
}) })
@@ -480,6 +516,7 @@ const findTeamDocumentsFilter = (
{ {
teamId: team.id, teamId: team.id,
status: ExtendedDocumentStatus.DRAFT, status: ExtendedDocumentStatus.DRAFT,
deletedAt: null,
OR: visibilityFilters, OR: visibilityFilters,
}, },
], ],
@@ -491,6 +528,7 @@ const findTeamDocumentsFilter = (
User: { User: {
email: teamEmail, email: teamEmail,
}, },
deletedAt: null,
OR: visibilityFilters, OR: visibilityFilters,
}); });
} }
@@ -503,6 +541,7 @@ const findTeamDocumentsFilter = (
{ {
teamId: team.id, teamId: team.id,
status: ExtendedDocumentStatus.PENDING, status: ExtendedDocumentStatus.PENDING,
deletedAt: null,
OR: visibilityFilters, OR: visibilityFilters,
}, },
], ],
@@ -531,6 +570,7 @@ const findTeamDocumentsFilter = (
OR: visibilityFilters, OR: visibilityFilters,
}, },
], ],
deletedAt: null,
}); });
} }
@@ -539,6 +579,7 @@ const findTeamDocumentsFilter = (
.with(ExtendedDocumentStatus.COMPLETED, () => { .with(ExtendedDocumentStatus.COMPLETED, () => {
const filter: Prisma.DocumentWhereInput = { const filter: Prisma.DocumentWhereInput = {
status: ExtendedDocumentStatus.COMPLETED, status: ExtendedDocumentStatus.COMPLETED,
deletedAt: null,
OR: [ OR: [
{ {
teamId: team.id, teamId: team.id,
@@ -568,5 +609,42 @@ const findTeamDocumentsFilter = (
return filter; return filter;
}) })
.with(ExtendedDocumentStatus.BIN, () => {
const filters: Prisma.DocumentWhereInput[] = [
{
teamId: team.id,
deletedAt: {
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
},
},
];
if (teamEmail) {
filters.push(
{
User: {
email: teamEmail,
},
deletedAt: {
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
},
},
{
Recipient: {
some: {
email: teamEmail,
documentDeletedAt: {
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
},
},
},
},
);
}
return {
OR: filters,
};
})
.exhaustive(); .exhaustive();
}; };

View File

@@ -0,0 +1,118 @@
import { DateTime } from 'luxon';
import { match } from 'ts-pattern';
import {
type PeriodSelectorValue,
findDocumentsFilter,
findTeamDocumentsFilter,
} from '@documenso/lib/server-only/document/find-documents';
import { prisma } from '@documenso/prisma';
import type { Prisma, Team, TeamEmail, User } from '@documenso/prisma/client';
import { DocumentVisibility, TeamMemberRole } from '@documenso/prisma/client';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
export type GetStatsInput = {
user: User;
team?: Team & { teamEmail: TeamEmail | null } & { currentTeamMember?: { role: TeamMemberRole } };
period?: PeriodSelectorValue;
search?: string;
};
export const getStats = async ({ user, period, search, ...options }: GetStatsInput) => {
let createdAt: Prisma.DocumentWhereInput['createdAt'];
if (period) {
const daysAgo = parseInt(period.replace(/d$/, ''), 10);
const startOfPeriod = DateTime.now().minus({ days: daysAgo }).startOf('day');
createdAt = {
gte: startOfPeriod.toJSDate(),
};
}
const stats: Record<ExtendedDocumentStatus, number> = {
[ExtendedDocumentStatus.DRAFT]: 0,
[ExtendedDocumentStatus.PENDING]: 0,
[ExtendedDocumentStatus.COMPLETED]: 0,
[ExtendedDocumentStatus.INBOX]: 0,
[ExtendedDocumentStatus.ALL]: 0,
[ExtendedDocumentStatus.BIN]: 0,
};
const searchFilter: Prisma.DocumentWhereInput = search
? {
OR: [
{ title: { contains: search, mode: 'insensitive' } },
{ Recipient: { some: { name: { contains: search, mode: 'insensitive' } } } },
{ Recipient: { some: { email: { contains: search, mode: 'insensitive' } } } },
],
}
: {};
const visibilityFilters = [
match(options.team?.currentTeamMember?.role)
.with(TeamMemberRole.ADMIN, () => ({
visibility: {
in: [
DocumentVisibility.EVERYONE,
DocumentVisibility.MANAGER_AND_ABOVE,
DocumentVisibility.ADMIN,
],
},
}))
.with(TeamMemberRole.MANAGER, () => ({
visibility: {
in: [DocumentVisibility.EVERYONE, DocumentVisibility.MANAGER_AND_ABOVE],
},
}))
.otherwise(() => ({ visibility: DocumentVisibility.EVERYONE })),
];
const statusCounts = await Promise.all(
Object.values(ExtendedDocumentStatus).map(async (status) => {
if (status === ExtendedDocumentStatus.ALL) {
return;
}
const filter = options.team
? findTeamDocumentsFilter(status, options.team, visibilityFilters)
: findDocumentsFilter(status, user);
if (filter === null) {
return { status, count: 0 };
}
const whereClause = {
...filter,
...(createdAt && { createdAt }),
...searchFilter,
};
const count = await prisma.document.count({
where: whereClause,
});
return { status, count };
}),
);
statusCounts.forEach((result) => {
if (result) {
stats[result.status] = result.count;
if (
result.status !== ExtendedDocumentStatus.BIN &&
[
ExtendedDocumentStatus.DRAFT,
ExtendedDocumentStatus.PENDING,
ExtendedDocumentStatus.COMPLETED,
ExtendedDocumentStatus.INBOX,
].includes(result.status)
) {
stats[ExtendedDocumentStatus.ALL] += result.count;
}
}
});
return stats;
};

View File

@@ -1,12 +1,16 @@
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
// eslint-disable-next-line import/no-extraneous-dependencies
import type { PeriodSelectorValue } from '@documenso/lib/server-only/document/find-documents'; import type { PeriodSelectorValue } from '@documenso/lib/server-only/document/find-documents';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import { TeamMemberRole } from '@documenso/prisma/client';
import type { Prisma, User } from '@documenso/prisma/client'; import type { Prisma, User } from '@documenso/prisma/client';
import { SigningStatus } from '@documenso/prisma/client'; import {
import { DocumentVisibility } from '@documenso/prisma/client'; DocumentVisibility,
RecipientRole,
SigningStatus,
TeamMemberRole,
} from '@documenso/prisma/client';
import { isExtendedDocumentStatus } from '@documenso/prisma/guards/is-extended-document-status'; import { isExtendedDocumentStatus } from '@documenso/prisma/guards/is-extended-document-status';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status'; import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
@@ -30,7 +34,7 @@ export const getStats = async ({ user, period, search, ...options }: GetStatsInp
}; };
} }
const [ownerCounts, notSignedCounts, hasSignedCounts] = await (options.team const [ownerCounts, notSignedCounts, hasSignedCounts, deletedCounts] = await (options.team
? getTeamCounts({ ? getTeamCounts({
...options.team, ...options.team,
createdAt, createdAt,
@@ -46,6 +50,7 @@ export const getStats = async ({ user, period, search, ...options }: GetStatsInp
[ExtendedDocumentStatus.COMPLETED]: 0, [ExtendedDocumentStatus.COMPLETED]: 0,
[ExtendedDocumentStatus.INBOX]: 0, [ExtendedDocumentStatus.INBOX]: 0,
[ExtendedDocumentStatus.ALL]: 0, [ExtendedDocumentStatus.ALL]: 0,
[ExtendedDocumentStatus.BIN]: 0,
}; };
ownerCounts.forEach((stat) => { ownerCounts.forEach((stat) => {
@@ -66,6 +71,10 @@ export const getStats = async ({ user, period, search, ...options }: GetStatsInp
} }
}); });
deletedCounts.forEach((stat) => {
stats[ExtendedDocumentStatus.BIN] += stat._count._all;
});
Object.keys(stats).forEach((key) => { Object.keys(stats).forEach((key) => {
if (key !== ExtendedDocumentStatus.ALL && isExtendedDocumentStatus(key)) { if (key !== ExtendedDocumentStatus.ALL && isExtendedDocumentStatus(key)) {
stats[ExtendedDocumentStatus.ALL] += stats[key]; stats[ExtendedDocumentStatus.ALL] += stats[key];
@@ -98,25 +107,45 @@ const getCounts = async ({ user, createdAt, search }: GetCountsOption) => {
_all: true, _all: true,
}, },
where: { where: {
userId: user.id, OR: [
{
userId: user.id,
teamId: null,
deletedAt: null,
},
{
status: {
not: ExtendedDocumentStatus.DRAFT,
},
Recipient: {
some: {
email: user.email,
documentDeletedAt: null,
},
},
},
],
createdAt, createdAt,
teamId: null,
deletedAt: null,
AND: [searchFilter], AND: [searchFilter],
}, },
}), }),
// Not signed counts. // Not signed counts (Inbox).
prisma.document.groupBy({ prisma.document.groupBy({
by: ['status'], by: ['status'],
_count: { _count: {
_all: true, _all: true,
}, },
where: { where: {
status: ExtendedDocumentStatus.PENDING, status: {
not: ExtendedDocumentStatus.DRAFT,
},
Recipient: { Recipient: {
some: { some: {
email: user.email, email: user.email,
signingStatus: SigningStatus.NOT_SIGNED, signingStatus: SigningStatus.NOT_SIGNED,
role: {
not: RecipientRole.CC,
},
documentDeletedAt: null, documentDeletedAt: null,
}, },
}, },
@@ -131,30 +160,81 @@ const getCounts = async ({ user, createdAt, search }: GetCountsOption) => {
_all: true, _all: true,
}, },
where: { where: {
createdAt,
User: {
email: {
not: user.email,
},
},
OR: [ OR: [
{
userId: user.id,
teamId: null,
status: ExtendedDocumentStatus.PENDING,
deletedAt: null,
},
{ {
status: ExtendedDocumentStatus.PENDING, status: ExtendedDocumentStatus.PENDING,
Recipient: { Recipient: {
some: { some: {
email: user.email, email: user.email,
signingStatus: SigningStatus.SIGNED, signingStatus: SigningStatus.SIGNED,
role: {
not: RecipientRole.CC,
},
documentDeletedAt: null, documentDeletedAt: null,
}, },
}, },
}, },
{
userId: user.id,
teamId: null,
status: ExtendedDocumentStatus.COMPLETED,
deletedAt: null,
},
{
status: ExtendedDocumentStatus.COMPLETED,
Recipient: {
some: {
email: user.email,
documentDeletedAt: null,
},
},
},
],
createdAt,
AND: [searchFilter],
},
}),
// Deleted counts.
prisma.document.groupBy({
by: ['status'],
_count: {
_all: true,
},
where: {
OR: [
{
userId: user.id,
teamId: null,
deletedAt: {
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
},
},
{
status: ExtendedDocumentStatus.PENDING,
Recipient: {
some: {
email: user.email,
signingStatus: SigningStatus.SIGNED,
documentDeletedAt: {
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
},
},
},
},
{ {
status: ExtendedDocumentStatus.COMPLETED, status: ExtendedDocumentStatus.COMPLETED,
Recipient: { Recipient: {
some: { some: {
email: user.email, email: user.email,
signingStatus: SigningStatus.SIGNED, documentDeletedAt: {
documentDeletedAt: null, gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
},
}, },
}, },
}, },
@@ -177,9 +257,7 @@ type GetTeamCountsOption = {
}; };
const getTeamCounts = async (options: GetTeamCountsOption) => { const getTeamCounts = async (options: GetTeamCountsOption) => {
const { createdAt, teamId, teamEmail } = options; const { createdAt, teamId, teamEmail, senderIds = [], currentTeamMemberRole, search } = options;
const senderIds = options.senderIds ?? [];
const userIdWhereClause: Prisma.DocumentWhereInput['userId'] = const userIdWhereClause: Prisma.DocumentWhereInput['userId'] =
senderIds.length > 0 senderIds.length > 0
@@ -188,148 +266,226 @@ const getTeamCounts = async (options: GetTeamCountsOption) => {
} }
: undefined; : undefined;
const searchFilter: Prisma.DocumentWhereInput = { const searchFilter: Prisma.DocumentWhereInput = search
OR: [ ? {
{ title: { contains: options.search, mode: 'insensitive' } },
{ Recipient: { some: { name: { contains: options.search, mode: 'insensitive' } } } },
{ Recipient: { some: { email: { contains: options.search, mode: 'insensitive' } } } },
],
};
let ownerCountsWhereInput: Prisma.DocumentWhereInput = {
userId: userIdWhereClause,
createdAt,
teamId,
deletedAt: null,
};
let notSignedCountsGroupByArgs = null;
let hasSignedCountsGroupByArgs = null;
const visibilityFiltersWhereInput: Prisma.DocumentWhereInput = {
AND: [
{ deletedAt: null },
{
OR: [ OR: [
match(options.currentTeamMemberRole) { title: { contains: search, mode: 'insensitive' } },
.with(TeamMemberRole.ADMIN, () => ({ { Recipient: { some: { name: { contains: search, mode: 'insensitive' } } } },
visibility: { { Recipient: { some: { email: { contains: search, mode: 'insensitive' } } } },
in: [
DocumentVisibility.EVERYONE,
DocumentVisibility.MANAGER_AND_ABOVE,
DocumentVisibility.ADMIN,
],
},
}))
.with(TeamMemberRole.MANAGER, () => ({
visibility: {
in: [DocumentVisibility.EVERYONE, DocumentVisibility.MANAGER_AND_ABOVE],
},
}))
.otherwise(() => ({
visibility: {
equals: DocumentVisibility.EVERYONE,
},
})),
{
OR: [
{ userId: options.userId },
{ Recipient: { some: { email: options.currentUserEmail } } },
],
},
], ],
}, }
], : {};
};
ownerCountsWhereInput = { const visibilityFilters = [
...ownerCountsWhereInput, match(currentTeamMemberRole)
...visibilityFiltersWhereInput, .with(TeamMemberRole.ADMIN, () => ({
...searchFilter, visibility: {
}; in: [
DocumentVisibility.EVERYONE,
if (teamEmail) { DocumentVisibility.MANAGER_AND_ABOVE,
ownerCountsWhereInput = { DocumentVisibility.ADMIN,
userId: userIdWhereClause, ],
createdAt,
OR: [
{
teamId,
}, },
{ }))
User: { .with(TeamMemberRole.MANAGER, () => ({
email: teamEmail, visibility: {
}, in: [DocumentVisibility.EVERYONE, DocumentVisibility.MANAGER_AND_ABOVE],
}, },
], }))
deletedAt: null, .otherwise(() => ({ visibility: DocumentVisibility.EVERYONE })),
}; ];
notSignedCountsGroupByArgs = {
by: ['status'],
_count: {
_all: true,
},
where: {
userId: userIdWhereClause,
createdAt,
status: ExtendedDocumentStatus.PENDING,
Recipient: {
some: {
email: teamEmail,
signingStatus: SigningStatus.NOT_SIGNED,
documentDeletedAt: null,
},
},
deletedAt: null,
},
} satisfies Prisma.DocumentGroupByArgs;
hasSignedCountsGroupByArgs = {
by: ['status'],
_count: {
_all: true,
},
where: {
userId: userIdWhereClause,
createdAt,
OR: [
{
status: ExtendedDocumentStatus.PENDING,
Recipient: {
some: {
email: teamEmail,
signingStatus: SigningStatus.SIGNED,
documentDeletedAt: null,
},
},
deletedAt: null,
},
{
status: ExtendedDocumentStatus.COMPLETED,
Recipient: {
some: {
email: teamEmail,
signingStatus: SigningStatus.SIGNED,
documentDeletedAt: null,
},
},
deletedAt: null,
},
],
},
} satisfies Prisma.DocumentGroupByArgs;
}
return Promise.all([ return Promise.all([
// Owner counts (ALL)
prisma.document.groupBy({ prisma.document.groupBy({
by: ['status'], by: ['status'],
_count: { _count: { _all: true },
_all: true, where: {
OR: [
{
teamId,
deletedAt: null,
OR: visibilityFilters,
},
...(teamEmail
? [
{
status: {
not: ExtendedDocumentStatus.DRAFT,
},
Recipient: {
some: {
email: teamEmail,
documentDeletedAt: null,
},
},
deletedAt: null,
OR: visibilityFilters,
},
{
User: {
email: teamEmail,
},
deletedAt: null,
OR: visibilityFilters,
},
]
: []),
],
userId: userIdWhereClause,
createdAt,
...searchFilter,
},
}),
// Not signed counts (INBOX)
prisma.document.groupBy({
by: ['status'],
_count: { _all: true },
where: teamEmail
? {
userId: userIdWhereClause,
createdAt,
status: {
not: ExtendedDocumentStatus.DRAFT,
},
Recipient: {
some: {
email: teamEmail,
signingStatus: SigningStatus.NOT_SIGNED,
role: {
not: RecipientRole.CC,
},
},
},
deletedAt: null,
OR: visibilityFilters,
...searchFilter,
}
: {
userId: userIdWhereClause,
createdAt,
AND: [
{
OR: [{ id: -1 }], // Empty set if no team email
},
searchFilter,
],
},
}),
// Has signed counts (PENDING + COMPLETED)
prisma.document.groupBy({
by: ['status'],
_count: { _all: true },
where: {
userId: userIdWhereClause,
createdAt,
OR: [
{
teamId,
status: ExtendedDocumentStatus.PENDING,
deletedAt: null,
OR: visibilityFilters,
},
{
teamId,
status: ExtendedDocumentStatus.COMPLETED,
deletedAt: null,
OR: visibilityFilters,
},
...(teamEmail
? [
{
status: ExtendedDocumentStatus.PENDING,
OR: [
{
Recipient: {
some: {
email: teamEmail,
signingStatus: SigningStatus.SIGNED,
role: {
not: RecipientRole.CC,
},
documentDeletedAt: null,
},
},
OR: visibilityFilters,
},
{
User: {
email: teamEmail,
},
OR: visibilityFilters,
},
],
deletedAt: null,
},
{
status: ExtendedDocumentStatus.COMPLETED,
OR: [
{
Recipient: {
some: {
email: teamEmail,
documentDeletedAt: null,
},
},
OR: visibilityFilters,
},
{
User: {
email: teamEmail,
},
OR: visibilityFilters,
},
],
deletedAt: null,
},
]
: []),
],
...searchFilter,
},
}),
// Deleted counts (BIN)
prisma.document.groupBy({
by: ['status'],
_count: { _all: true },
where: {
OR: [
{
teamId,
deletedAt: {
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
},
},
...(teamEmail
? [
{
User: {
email: teamEmail,
},
deletedAt: {
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
},
},
{
Recipient: {
some: {
email: teamEmail,
documentDeletedAt: {
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
},
},
},
},
]
: []),
],
...searchFilter,
}, },
where: ownerCountsWhereInput,
}), }),
notSignedCountsGroupByArgs ? prisma.document.groupBy(notSignedCountsGroupByArgs) : [],
hasSignedCountsGroupByArgs ? prisma.document.groupBy(hasSignedCountsGroupByArgs) : [],
]); ]);
}; };

View File

@@ -112,6 +112,7 @@ export const isRecipientAuthorized = async ({
return await verifyTwoFactorAuthenticationToken({ return await verifyTwoFactorAuthenticationToken({
user, user,
totpCode: token, totpCode: token,
window: 10, // 5 minutes worth of tokens
}); });
}) })
.exhaustive(); .exhaustive();

View File

@@ -0,0 +1,149 @@
'use server';
import { prisma } from '@documenso/prisma';
import type { Document, DocumentMeta, Recipient, User } from '@documenso/prisma/client';
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
import type { RequestMetadata } from '../../universal/extract-request-metadata';
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
export type RestoreDocumentOptions = {
id: number;
userId: number;
teamId?: number;
requestMetadata?: RequestMetadata;
};
export const restoreDocument = async ({
id,
userId,
teamId,
requestMetadata,
}: RestoreDocumentOptions) => {
const user = await prisma.user.findUnique({
where: {
id: userId,
},
});
if (!user) {
throw new Error('User not found');
}
const document = await prisma.document.findUnique({
where: {
id,
},
include: {
Recipient: true,
documentMeta: true,
team: {
select: {
members: true,
},
},
},
});
if (!document || (teamId !== undefined && teamId !== document.teamId)) {
throw new Error('Document not found');
}
const isUserOwner = document.userId === userId;
const isUserTeamMember = document.team?.members.some((member) => member.userId === userId);
const userRecipient = document.Recipient.find((recipient) => recipient.email === user.email);
if (!isUserOwner && !isUserTeamMember && !userRecipient) {
throw new Error('Not allowed');
}
// Handle restoring the actual document if user has permission.
if (isUserOwner || isUserTeamMember) {
await handleDocumentOwnerRestore({
document,
user,
requestMetadata,
});
}
// Continue to show the document to the user if they are a recipient.
if (userRecipient?.documentDeletedAt !== null) {
await prisma.recipient
.update({
where: {
id: userRecipient?.id,
},
data: {
documentDeletedAt: null,
},
})
.catch(() => {
// Do nothing.
});
}
// Return partial document for API v1 response.
return {
id: document.id,
userId: document.userId,
teamId: document.teamId,
title: document.title,
status: document.status,
documentDataId: document.documentDataId,
createdAt: document.createdAt,
updatedAt: document.updatedAt,
completedAt: document.completedAt,
};
};
type HandleDocumentOwnerRestoreOptions = {
document: Document & {
Recipient: Recipient[];
documentMeta: DocumentMeta | null;
};
user: User;
requestMetadata?: RequestMetadata;
};
const handleDocumentOwnerRestore = async ({
document,
user,
requestMetadata,
}: HandleDocumentOwnerRestoreOptions) => {
if (!document.deletedAt) {
return;
}
// Restore soft-deleted documents.
return await prisma.$transaction(async (tx) => {
await tx.documentAuditLog.create({
data: createDocumentAuditLogData({
documentId: document.id,
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RESTORED,
user,
requestMetadata,
data: {
type: 'RESTORE',
},
}),
});
await tx.recipient.updateMany({
where: {
documentId: document.id,
},
data: {
documentDeletedAt: null,
},
});
return await tx.document.update({
where: {
id: document.id,
},
data: {
deletedAt: null,
},
});
});
};

View File

@@ -10,7 +10,6 @@ import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/
import { WebhookTriggerEvents } from '@documenso/prisma/client'; import { WebhookTriggerEvents } from '@documenso/prisma/client';
import { signPdf } from '@documenso/signing'; import { signPdf } from '@documenso/signing';
import { ZSupportedLanguageCodeSchema } from '../../constants/i18n';
import type { RequestMetadata } from '../../universal/extract-request-metadata'; import type { RequestMetadata } from '../../universal/extract-request-metadata';
import { getFile } from '../../universal/upload/get-file'; import { getFile } from '../../universal/upload/get-file';
import { putPdfFile } from '../../universal/upload/put-file'; import { putPdfFile } from '../../universal/upload/put-file';
@@ -48,6 +47,15 @@ export const sealDocument = async ({
documentData: true, documentData: true,
documentMeta: true, documentMeta: true,
Recipient: true, Recipient: true,
team: {
select: {
teamGlobalSettings: {
select: {
includeSigningCertificate: true,
},
},
},
},
}, },
}); });
@@ -92,11 +100,13 @@ export const sealDocument = async ({
// !: Need to write the fields onto the document as a hard copy // !: Need to write the fields onto the document as a hard copy
const pdfData = await getFile(documentData); const pdfData = await getFile(documentData);
const documentLanguage = ZSupportedLanguageCodeSchema.parse(document.documentMeta?.language); const certificateData =
(document.team?.teamGlobalSettings?.includeSigningCertificate ?? true)
const certificate = await getCertificatePdf({ documentId, language: documentLanguage }) ? await getCertificatePdf({
.then(async (doc) => PDFDocument.load(doc)) documentId,
.catch(() => null); language: document.documentMeta?.language,
}).catch(() => null)
: null;
const doc = await PDFDocument.load(pdfData); const doc = await PDFDocument.load(pdfData);
@@ -105,7 +115,9 @@ export const sealDocument = async ({
flattenForm(doc); flattenForm(doc);
flattenAnnotations(doc); flattenAnnotations(doc);
if (certificate) { if (certificateData) {
const certificate = await PDFDocument.load(certificateData);
const certificatePages = await doc.copyPages(certificate, certificate.getPageIndices()); const certificatePages = await doc.copyPages(certificate, certificate.getPageIndices());
certificatePages.forEach((page) => { certificatePages.forEach((page) => {

View File

@@ -5,7 +5,11 @@ import { getToken } from 'next-auth/jwt';
import { LOCAL_FEATURE_FLAGS } from '@documenso/lib/constants/feature-flags'; import { LOCAL_FEATURE_FLAGS } from '@documenso/lib/constants/feature-flags';
import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-post-hog-server-client'; import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-post-hog-server-client';
import { NEXT_PUBLIC_MARKETING_URL, NEXT_PUBLIC_WEBAPP_URL, NEXT_PRIVATE_INTERNAL_WEBAPP_URL } from '../../constants/app'; import {
NEXT_PRIVATE_INTERNAL_WEBAPP_URL,
NEXT_PUBLIC_MARKETING_URL,
NEXT_PUBLIC_WEBAPP_URL,
} from '../../constants/app';
import { extractDistinctUserId, mapJwtToFlagProperties } from './get'; import { extractDistinctUserId, mapJwtToFlagProperties } from './get';
/** /**

View File

@@ -7,7 +7,11 @@ import { getToken } from 'next-auth/jwt';
import { LOCAL_FEATURE_FLAGS, extractPostHogConfig } from '@documenso/lib/constants/feature-flags'; import { LOCAL_FEATURE_FLAGS, extractPostHogConfig } from '@documenso/lib/constants/feature-flags';
import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-post-hog-server-client'; import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-post-hog-server-client';
import { NEXT_PUBLIC_MARKETING_URL, NEXT_PUBLIC_WEBAPP_URL, NEXT_PRIVATE_INTERNAL_WEBAPP_URL } from '../../constants/app'; import {
NEXT_PRIVATE_INTERNAL_WEBAPP_URL,
NEXT_PUBLIC_MARKETING_URL,
NEXT_PUBLIC_WEBAPP_URL,
} from '../../constants/app';
/** /**
* Evaluate a single feature flag based on the current user if possible. * Evaluate a single feature flag based on the current user if possible.
@@ -67,7 +71,7 @@ export default async function handleFeatureFlagGet(req: Request) {
if (origin.startsWith(NEXT_PUBLIC_MARKETING_URL() ?? 'http://localhost:3001')) { if (origin.startsWith(NEXT_PUBLIC_MARKETING_URL() ?? 'http://localhost:3001')) {
res.headers.set('Access-Control-Allow-Origin', origin); res.headers.set('Access-Control-Allow-Origin', origin);
} }
if (origin.startsWith(NEXT_PRIVATE_INTERNAL_WEBAPP_URL ?? 'http://localhost:3000')) { if (origin.startsWith(NEXT_PRIVATE_INTERNAL_WEBAPP_URL ?? 'http://localhost:3000')) {
res.headers.set('Access-Control-Allow-Origin', origin); res.headers.set('Access-Control-Allow-Origin', origin);
} }

View File

@@ -177,6 +177,10 @@ export const signFieldWithToken = async ({
throw new Error('Signature field must have a signature'); throw new Error('Signature field must have a signature');
} }
if (isSignatureField && !documentMeta?.typedSignatureEnabled && typedSignature) {
throw new Error('Typed signatures are not allowed. Please draw your signature');
}
return await prisma.$transaction(async (tx) => { return await prisma.$transaction(async (tx) => {
const updatedField = await tx.field.update({ const updatedField = await tx.field.update({
where: { where: {

View File

@@ -2,12 +2,13 @@ import { DateTime } from 'luxon';
import type { Browser } from 'playwright'; import type { Browser } from 'playwright';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app'; import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
import type { SupportedLanguageCodes } from '../../constants/i18n'; import { type SupportedLanguageCodes, isValidLanguageCode } from '../../constants/i18n';
import { encryptSecondaryData } from '../crypto/encrypt'; import { encryptSecondaryData } from '../crypto/encrypt';
export type GetCertificatePdfOptions = { export type GetCertificatePdfOptions = {
documentId: number; documentId: number;
language?: SupportedLanguageCodes; // eslint-disable-next-line @typescript-eslint/ban-types
language?: SupportedLanguageCodes | (string & {});
}; };
export const getCertificatePdf = async ({ documentId, language }: GetCertificatePdfOptions) => { export const getCertificatePdf = async ({ documentId, language }: GetCertificatePdfOptions) => {
@@ -38,15 +39,15 @@ export const getCertificatePdf = async ({ documentId, language }: GetCertificate
const page = await browserContext.newPage(); const page = await browserContext.newPage();
if (language) { const lang = isValidLanguageCode(language) ? language : 'en';
await page.context().addCookies([
{ await page.context().addCookies([
name: 'language', {
value: language, name: 'language',
url: NEXT_PUBLIC_WEBAPP_URL(), value: lang,
}, url: NEXT_PUBLIC_WEBAPP_URL(),
]); },
} ]);
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encryptedId}`, { await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encryptedId}`, {
waitUntil: 'networkidle', waitUntil: 'networkidle',

View File

@@ -82,7 +82,10 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
const fieldX = pageWidth * (Number(field.positionX) / 100); const fieldX = pageWidth * (Number(field.positionX) / 100);
const fieldY = pageHeight * (Number(field.positionY) / 100); const fieldY = pageHeight * (Number(field.positionY) / 100);
const font = await pdf.embedFont(isSignatureField ? fontCaveat : fontNoto); const font = await pdf.embedFont(
isSignatureField ? fontCaveat : fontNoto,
isSignatureField ? { features: { calt: false } } : undefined,
);
if (field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE) { if (field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE) {
await pdf.embedFont(fontCaveat); await pdf.embedFont(fontCaveat);
@@ -92,45 +95,89 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
.with( .with(
{ {
type: P.union(FieldType.SIGNATURE, FieldType.FREE_SIGNATURE), type: P.union(FieldType.SIGNATURE, FieldType.FREE_SIGNATURE),
Signature: { signatureImageAsBase64: P.string },
}, },
async (field) => { async (field) => {
const image = await pdf.embedPng(field.Signature?.signatureImageAsBase64 ?? ''); if (field.Signature?.signatureImageAsBase64) {
const image = await pdf.embedPng(field.Signature?.signatureImageAsBase64 ?? '');
let imageWidth = image.width; let imageWidth = image.width;
let imageHeight = image.height; let imageHeight = image.height;
const scalingFactor = Math.min(fieldWidth / imageWidth, fieldHeight / imageHeight, 1); const scalingFactor = Math.min(fieldWidth / imageWidth, fieldHeight / imageHeight, 1);
imageWidth = imageWidth * scalingFactor; imageWidth = imageWidth * scalingFactor;
imageHeight = imageHeight * scalingFactor; imageHeight = imageHeight * scalingFactor;
let imageX = fieldX + (fieldWidth - imageWidth) / 2; let imageX = fieldX + (fieldWidth - imageWidth) / 2;
let imageY = fieldY + (fieldHeight - imageHeight) / 2; let imageY = fieldY + (fieldHeight - imageHeight) / 2;
// Invert the Y axis since PDFs use a bottom-left coordinate system // Invert the Y axis since PDFs use a bottom-left coordinate system
imageY = pageHeight - imageY - imageHeight; imageY = pageHeight - imageY - imageHeight;
if (pageRotationInDegrees !== 0) { if (pageRotationInDegrees !== 0) {
const adjustedPosition = adjustPositionForRotation( const adjustedPosition = adjustPositionForRotation(
pageWidth, pageWidth,
pageHeight, pageHeight,
imageX, imageX,
imageY, imageY,
pageRotationInDegrees, pageRotationInDegrees,
); );
imageX = adjustedPosition.xPos; imageX = adjustedPosition.xPos;
imageY = adjustedPosition.yPos; imageY = adjustedPosition.yPos;
}
page.drawImage(image, {
x: imageX,
y: imageY,
width: imageWidth,
height: imageHeight,
rotate: degrees(pageRotationInDegrees),
});
} else {
const signatureText = field.Signature?.typedSignature ?? '';
const longestLineInTextForWidth = signatureText
.split('\n')
.sort((a, b) => b.length - a.length)[0];
let fontSize = maxFontSize;
let textWidth = font.widthOfTextAtSize(longestLineInTextForWidth, fontSize);
let textHeight = font.heightAtSize(fontSize);
const scalingFactor = Math.min(fieldWidth / textWidth, fieldHeight / textHeight, 1);
fontSize = Math.max(Math.min(fontSize * scalingFactor, maxFontSize), minFontSize);
textWidth = font.widthOfTextAtSize(longestLineInTextForWidth, fontSize);
textHeight = font.heightAtSize(fontSize);
let textX = fieldX + (fieldWidth - textWidth) / 2;
let textY = fieldY + (fieldHeight - textHeight) / 2;
// Invert the Y axis since PDFs use a bottom-left coordinate system
textY = pageHeight - textY - textHeight;
if (pageRotationInDegrees !== 0) {
const adjustedPosition = adjustPositionForRotation(
pageWidth,
pageHeight,
textX,
textY,
pageRotationInDegrees,
);
textX = adjustedPosition.xPos;
textY = adjustedPosition.yPos;
}
page.drawText(signatureText, {
x: textX,
y: textY,
size: fontSize,
font,
rotate: degrees(pageRotationInDegrees),
});
} }
page.drawImage(image, {
x: imageX,
y: imageY,
width: imageWidth,
height: imageHeight,
rotate: degrees(pageRotationInDegrees),
});
}, },
) )
.with({ type: FieldType.CHECKBOX }, (field) => { .with({ type: FieldType.CHECKBOX }, (field) => {

View File

@@ -42,7 +42,16 @@ export const acceptTeamInvitation = async ({ userId, teamId }: AcceptTeamInvitat
}); });
if (teamMemberInvite.status === TeamMemberInviteStatus.ACCEPTED) { if (teamMemberInvite.status === TeamMemberInviteStatus.ACCEPTED) {
return; const memberExists = await tx.teamMember.findFirst({
where: {
teamId: teamMemberInvite.teamId,
userId: user.id,
},
});
if (memberExists) {
return;
}
} }
const { team } = teamMemberInvite; const { team } = teamMemberInvite;

View File

@@ -12,6 +12,8 @@ export type UpdateTeamDocumentSettingsOptions = {
documentVisibility: DocumentVisibility; documentVisibility: DocumentVisibility;
documentLanguage: SupportedLanguageCodes; documentLanguage: SupportedLanguageCodes;
includeSenderDetails: boolean; includeSenderDetails: boolean;
typedSignatureEnabled: boolean;
includeSigningCertificate: boolean;
}; };
}; };
@@ -20,7 +22,13 @@ export const updateTeamDocumentSettings = async ({
teamId, teamId,
settings, settings,
}: UpdateTeamDocumentSettingsOptions) => { }: UpdateTeamDocumentSettingsOptions) => {
const { documentVisibility, documentLanguage, includeSenderDetails } = settings; const {
documentVisibility,
documentLanguage,
includeSenderDetails,
includeSigningCertificate,
typedSignatureEnabled,
} = settings;
const member = await prisma.teamMember.findFirst({ const member = await prisma.teamMember.findFirst({
where: { where: {
@@ -42,11 +50,15 @@ export const updateTeamDocumentSettings = async ({
documentVisibility, documentVisibility,
documentLanguage, documentLanguage,
includeSenderDetails, includeSenderDetails,
typedSignatureEnabled,
includeSigningCertificate,
}, },
update: { update: {
documentVisibility, documentVisibility,
documentLanguage, documentLanguage,
includeSenderDetails, includeSenderDetails,
typedSignatureEnabled,
includeSigningCertificate,
}, },
}); });
}; };

View File

@@ -4,7 +4,6 @@ import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import { Prisma } from '@documenso/prisma/client'; import { Prisma } from '@documenso/prisma/client';
import type { DocumentVisibility } from '@documenso/prisma/client';
export type UpdateTeamOptions = { export type UpdateTeamOptions = {
userId: number; userId: number;
@@ -12,8 +11,6 @@ export type UpdateTeamOptions = {
data: { data: {
name?: string; name?: string;
url?: string; url?: string;
documentVisibility?: DocumentVisibility;
includeSenderDetails?: boolean;
}; };
}; };
@@ -45,18 +42,6 @@ export const updateTeam = async ({ userId, teamId, data }: UpdateTeamOptions) =>
data: { data: {
url: data.url, url: data.url,
name: data.name, name: data.name,
teamGlobalSettings: {
upsert: {
create: {
documentVisibility: data.documentVisibility,
includeSenderDetails: data.includeSenderDetails,
},
update: {
documentVisibility: data.documentVisibility,
includeSenderDetails: data.includeSenderDetails,
},
},
},
}, },
}); });

View File

@@ -64,6 +64,7 @@ export type CreateDocumentFromTemplateOptions = {
signingOrder?: DocumentSigningOrder; signingOrder?: DocumentSigningOrder;
language?: SupportedLanguageCodes; language?: SupportedLanguageCodes;
distributionMethod?: DocumentDistributionMethod; distributionMethod?: DocumentDistributionMethod;
typedSignatureEnabled?: boolean;
}; };
requestMetadata?: RequestMetadata; requestMetadata?: RequestMetadata;
}; };
@@ -146,7 +147,7 @@ export const createDocumentFromTemplate = async ({
return { return {
templateRecipientId: templateRecipient.id, templateRecipientId: templateRecipient.id,
fields: templateRecipient.Field, fields: templateRecipient.Field,
name: foundRecipient ? foundRecipient.name ?? '' : templateRecipient.name, name: foundRecipient ? (foundRecipient.name ?? '') : templateRecipient.name,
email: foundRecipient ? foundRecipient.email : templateRecipient.email, email: foundRecipient ? foundRecipient.email : templateRecipient.email,
role: templateRecipient.role, role: templateRecipient.role,
signingOrder: foundRecipient?.signingOrder ?? templateRecipient.signingOrder, signingOrder: foundRecipient?.signingOrder ?? templateRecipient.signingOrder,
@@ -196,6 +197,8 @@ export const createDocumentFromTemplate = async ({
override?.language || override?.language ||
template.templateMeta?.language || template.templateMeta?.language ||
template.team?.teamGlobalSettings?.documentLanguage, template.team?.teamGlobalSettings?.documentLanguage,
typedSignatureEnabled:
override?.typedSignatureEnabled ?? template.templateMeta?.typedSignatureEnabled,
}, },
}, },
Recipient: { Recipient: {

View File

@@ -3,7 +3,7 @@ import { hash } from '@node-rs/bcrypt';
import { getStripeCustomerByUser } from '@documenso/ee/server-only/stripe/get-customer'; import { getStripeCustomerByUser } from '@documenso/ee/server-only/stripe/get-customer';
import { updateSubscriptionItemQuantity } from '@documenso/ee/server-only/stripe/update-subscription-item-quantity'; import { updateSubscriptionItemQuantity } from '@documenso/ee/server-only/stripe/update-subscription-item-quantity';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import { IdentityProvider, Prisma, TeamMemberInviteStatus } from '@documenso/prisma/client'; import { IdentityProvider, TeamMemberInviteStatus } from '@documenso/prisma/client';
import { IS_BILLING_ENABLED } from '../../constants/app'; import { IS_BILLING_ENABLED } from '../../constants/app';
import { SALT_ROUNDS } from '../../constants/auth'; import { SALT_ROUNDS } from '../../constants/auth';
@@ -59,11 +59,11 @@ export const createUser = async ({ name, email, password, signature, url }: Crea
const acceptedTeamInvites = await prisma.teamMemberInvite.findMany({ const acceptedTeamInvites = await prisma.teamMemberInvite.findMany({
where: { where: {
status: TeamMemberInviteStatus.ACCEPTED,
email: { email: {
equals: email, equals: email,
mode: Prisma.QueryMode.insensitive, mode: 'insensitive',
}, },
status: TeamMemberInviteStatus.ACCEPTED,
}, },
}); });

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: de\n" "Language: de\n"
"Project-Id-Version: documenso-app\n" "Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-11-12 05:45\n" "PO-Revision-Date: 2024-11-20 11:56\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: German\n" "Language-Team: German\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -135,11 +135,11 @@ msgstr "{prefix} hat das Dokument erstellt"
msgid "{prefix} deleted the document" msgid "{prefix} deleted the document"
msgstr "{prefix} hat das Dokument gelöscht" msgstr "{prefix} hat das Dokument gelöscht"
#: packages/lib/utils/document-audit-logs.ts:335 #: packages/lib/utils/document-audit-logs.ts:339
msgid "{prefix} moved the document to team" msgid "{prefix} moved the document to team"
msgstr "{prefix} hat das Dokument ins Team verschoben" msgstr "{prefix} hat das Dokument ins Team verschoben"
#: packages/lib/utils/document-audit-logs.ts:319 #: packages/lib/utils/document-audit-logs.ts:323
msgid "{prefix} opened the document" msgid "{prefix} opened the document"
msgstr "{prefix} hat das Dokument geöffnet" msgstr "{prefix} hat das Dokument geöffnet"
@@ -151,23 +151,27 @@ msgstr "{prefix} hat ein Feld entfernt"
msgid "{prefix} removed a recipient" msgid "{prefix} removed a recipient"
msgstr "{prefix} hat einen Empfänger entfernt" msgstr "{prefix} hat einen Empfänger entfernt"
#: packages/lib/utils/document-audit-logs.ts:365 #: packages/lib/utils/document-audit-logs.ts:369
msgid "{prefix} resent an email to {0}" msgid "{prefix} resent an email to {0}"
msgstr "{prefix} hat eine E-Mail an {0} erneut gesendet" msgstr "{prefix} hat eine E-Mail an {0} erneut gesendet"
#: packages/lib/utils/document-audit-logs.ts:366 #: packages/lib/utils/document-audit-logs.ts:295
msgid "{prefix} restored the document"
msgstr ""
#: packages/lib/utils/document-audit-logs.ts:370
msgid "{prefix} sent an email to {0}" msgid "{prefix} sent an email to {0}"
msgstr "{prefix} hat eine E-Mail an {0} gesendet" msgstr "{prefix} hat eine E-Mail an {0} gesendet"
#: packages/lib/utils/document-audit-logs.ts:331 #: packages/lib/utils/document-audit-logs.ts:335
msgid "{prefix} sent the document" msgid "{prefix} sent the document"
msgstr "{prefix} hat das Dokument gesendet" msgstr "{prefix} hat das Dokument gesendet"
#: packages/lib/utils/document-audit-logs.ts:295 #: packages/lib/utils/document-audit-logs.ts:299
msgid "{prefix} signed a field" msgid "{prefix} signed a field"
msgstr "{prefix} hat ein Feld unterschrieben" msgstr "{prefix} hat ein Feld unterschrieben"
#: packages/lib/utils/document-audit-logs.ts:299 #: packages/lib/utils/document-audit-logs.ts:303
msgid "{prefix} unsigned a field" msgid "{prefix} unsigned a field"
msgstr "{prefix} hat ein Feld ungültig gemacht" msgstr "{prefix} hat ein Feld ungültig gemacht"
@@ -179,27 +183,27 @@ msgstr "{prefix} hat ein Feld aktualisiert"
msgid "{prefix} updated a recipient" msgid "{prefix} updated a recipient"
msgstr "{prefix} hat einen Empfänger aktualisiert" msgstr "{prefix} hat einen Empfänger aktualisiert"
#: packages/lib/utils/document-audit-logs.ts:315 #: packages/lib/utils/document-audit-logs.ts:319
msgid "{prefix} updated the document" msgid "{prefix} updated the document"
msgstr "{prefix} hat das Dokument aktualisiert" msgstr "{prefix} hat das Dokument aktualisiert"
#: packages/lib/utils/document-audit-logs.ts:307 #: packages/lib/utils/document-audit-logs.ts:311
msgid "{prefix} updated the document access auth requirements" msgid "{prefix} updated the document access auth requirements"
msgstr "{prefix} hat die Anforderungen an die Dokumentenzugriffsautorisierung aktualisiert" msgstr "{prefix} hat die Anforderungen an die Dokumentenzugriffsautorisierung aktualisiert"
#: packages/lib/utils/document-audit-logs.ts:327 #: packages/lib/utils/document-audit-logs.ts:331
msgid "{prefix} updated the document external ID" msgid "{prefix} updated the document external ID"
msgstr "{prefix} hat die externe ID des Dokuments aktualisiert" msgstr "{prefix} hat die externe ID des Dokuments aktualisiert"
#: packages/lib/utils/document-audit-logs.ts:311 #: packages/lib/utils/document-audit-logs.ts:315
msgid "{prefix} updated the document signing auth requirements" msgid "{prefix} updated the document signing auth requirements"
msgstr "{prefix} hat die Authentifizierungsanforderungen für die Dokumentenunterzeichnung aktualisiert" msgstr "{prefix} hat die Authentifizierungsanforderungen für die Dokumentenunterzeichnung aktualisiert"
#: packages/lib/utils/document-audit-logs.ts:323 #: packages/lib/utils/document-audit-logs.ts:327
msgid "{prefix} updated the document title" msgid "{prefix} updated the document title"
msgstr "{prefix} hat den Titel des Dokuments aktualisiert" msgstr "{prefix} hat den Titel des Dokuments aktualisiert"
#: packages/lib/utils/document-audit-logs.ts:303 #: packages/lib/utils/document-audit-logs.ts:307
msgid "{prefix} updated the document visibility" msgid "{prefix} updated the document visibility"
msgstr "{prefix} hat die Sichtbarkeit des Dokuments aktualisiert" msgstr "{prefix} hat die Sichtbarkeit des Dokuments aktualisiert"
@@ -209,11 +213,11 @@ msgstr "{recipientName} {action} ein Dokument, indem Sie einen Ihrer direkten Li
#: packages/email/templates/document-rejected.tsx:27 #: packages/email/templates/document-rejected.tsx:27
msgid "{recipientName} has rejected the document '{documentName}'" msgid "{recipientName} has rejected the document '{documentName}'"
msgstr "" msgstr "{recipientName} hat das Dokument '{documentName}' abgelehnt"
#: packages/email/template-components/template-document-rejected.tsx:25 #: packages/email/template-components/template-document-rejected.tsx:25
msgid "{signerName} has rejected the document \"{documentName}\"." msgid "{signerName} has rejected the document \"{documentName}\"."
msgstr "" msgstr "{signerName} hat das Dokument \"{documentName}\" abgelehnt."
#: packages/email/template-components/template-document-invite.tsx:68 #: packages/email/template-components/template-document-invite.tsx:68
msgid "{teamName} has invited you to {0}" msgid "{teamName} has invited you to {0}"
@@ -227,27 +231,27 @@ msgstr "{teamName} hat Sie eingeladen, {action} {documentName}"
msgid "{teamName} ownership transfer request" msgid "{teamName} ownership transfer request"
msgstr "Anfrage zur Übertragung des Eigentums von {teamName}" msgstr "Anfrage zur Übertragung des Eigentums von {teamName}"
#: packages/lib/utils/document-audit-logs.ts:343 #: packages/lib/utils/document-audit-logs.ts:347
msgid "{userName} approved the document" msgid "{userName} approved the document"
msgstr "{userName} hat das Dokument genehmigt" msgstr "{userName} hat das Dokument genehmigt"
#: packages/lib/utils/document-audit-logs.ts:344 #: packages/lib/utils/document-audit-logs.ts:348
msgid "{userName} CC'd the document" msgid "{userName} CC'd the document"
msgstr "{userName} hat das Dokument in CC gesetzt" msgstr "{userName} hat das Dokument in CC gesetzt"
#: packages/lib/utils/document-audit-logs.ts:345 #: packages/lib/utils/document-audit-logs.ts:349
msgid "{userName} completed their task" msgid "{userName} completed their task"
msgstr "{userName} hat ihre Aufgabe abgeschlossen" msgstr "{userName} hat ihre Aufgabe abgeschlossen"
#: packages/lib/utils/document-audit-logs.ts:355 #: packages/lib/utils/document-audit-logs.ts:359
msgid "{userName} rejected the document" msgid "{userName} rejected the document"
msgstr "" msgstr "{userName} hat das Dokument abgelehnt"
#: packages/lib/utils/document-audit-logs.ts:341 #: packages/lib/utils/document-audit-logs.ts:345
msgid "{userName} signed the document" msgid "{userName} signed the document"
msgstr "{userName} hat das Dokument unterschrieben" msgstr "{userName} hat das Dokument unterschrieben"
#: packages/lib/utils/document-audit-logs.ts:342 #: packages/lib/utils/document-audit-logs.ts:346
msgid "{userName} viewed the document" msgid "{userName} viewed the document"
msgstr "{userName} hat das Dokument angesehen" msgstr "{userName} hat das Dokument angesehen"
@@ -414,11 +418,11 @@ msgstr "Weiteren Wert hinzufügen"
msgid "Add myself" msgid "Add myself"
msgstr "Mich selbst hinzufügen" msgstr "Mich selbst hinzufügen"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:637 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:645
msgid "Add Myself" msgid "Add Myself"
msgstr "Mich hinzufügen" msgstr "Mich hinzufügen"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:623 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:631
msgid "Add Placeholder Recipient" msgid "Add Placeholder Recipient"
msgstr "Platzhalterempfänger hinzufügen" msgstr "Platzhalterempfänger hinzufügen"
@@ -443,8 +447,8 @@ msgstr "Admin"
msgid "Advanced Options" msgid "Advanced Options"
msgstr "Erweiterte Optionen" msgstr "Erweiterte Optionen"
#: packages/ui/primitives/document-flow/add-fields.tsx:573 #: packages/ui/primitives/document-flow/add-fields.tsx:576
#: packages/ui/primitives/template-flow/add-template-fields.tsx:406 #: packages/ui/primitives/template-flow/add-template-fields.tsx:414
msgid "Advanced settings" msgid "Advanced settings"
msgstr "Erweiterte Einstellungen" msgstr "Erweiterte Einstellungen"
@@ -500,11 +504,11 @@ msgstr "Genehmigung"
msgid "Before you get started, please confirm your email address by clicking the button below:" msgid "Before you get started, please confirm your email address by clicking the button below:"
msgstr "Bitte bestätige vor dem Start deine E-Mail-Adresse, indem du auf den Button unten klickst:" msgstr "Bitte bestätige vor dem Start deine E-Mail-Adresse, indem du auf den Button unten klickst:"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:377 #: packages/ui/primitives/signature-pad/signature-pad.tsx:383
msgid "Black" msgid "Black"
msgstr "Schwarz" msgstr "Schwarz"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:391 #: packages/ui/primitives/signature-pad/signature-pad.tsx:397
msgid "Blue" msgid "Blue"
msgstr "Blau" msgstr "Blau"
@@ -550,6 +554,10 @@ msgstr "Ccers"
msgid "Character Limit" msgid "Character Limit"
msgstr "Zeichenbeschränkung" msgstr "Zeichenbeschränkung"
#: packages/ui/primitives/document-flow/types.ts:58
msgid "Checkbox"
msgstr "Checkbox"
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:197 #: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:197
msgid "Checkbox values" msgid "Checkbox values"
msgstr "Checkbox-Werte" msgstr "Checkbox-Werte"
@@ -558,7 +566,7 @@ msgstr "Checkbox-Werte"
msgid "Clear filters" msgid "Clear filters"
msgstr "Filter löschen" msgstr "Filter löschen"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:411 #: packages/ui/primitives/signature-pad/signature-pad.tsx:417
msgid "Clear Signature" msgid "Clear Signature"
msgstr "Unterschrift löschen" msgstr "Unterschrift löschen"
@@ -585,8 +593,8 @@ msgstr "Abgeschlossenes Dokument"
msgid "Configure Direct Recipient" msgid "Configure Direct Recipient"
msgstr "Direkten Empfänger konfigurieren" msgstr "Direkten Empfänger konfigurieren"
#: packages/ui/primitives/document-flow/add-fields.tsx:574 #: packages/ui/primitives/document-flow/add-fields.tsx:577
#: packages/ui/primitives/template-flow/add-template-fields.tsx:407 #: packages/ui/primitives/template-flow/add-template-fields.tsx:415
msgid "Configure the {0} field" msgid "Configure the {0} field"
msgstr "Konfigurieren Sie das Feld {0}" msgstr "Konfigurieren Sie das Feld {0}"
@@ -647,9 +655,9 @@ msgstr "Konto erstellen"
msgid "Custom Text" msgid "Custom Text"
msgstr "Benutzerdefinierter Text" msgstr "Benutzerdefinierter Text"
#: packages/ui/primitives/document-flow/add-fields.tsx:927 #: packages/ui/primitives/document-flow/add-fields.tsx:934
#: packages/ui/primitives/document-flow/types.ts:53 #: packages/ui/primitives/document-flow/types.ts:53
#: packages/ui/primitives/template-flow/add-template-fields.tsx:690 #: packages/ui/primitives/template-flow/add-template-fields.tsx:729
msgid "Date" msgid "Date"
msgstr "Datum" msgstr "Datum"
@@ -666,17 +674,17 @@ msgstr "Ablehnen"
msgid "Didn't request a password change? We are here to help you secure your account, just <0>contact us.</0>" msgid "Didn't request a password change? We are here to help you secure your account, just <0>contact us.</0>"
msgstr "Hast du keinen Passwortwechsel angefordert? Wir helfen dir, dein Konto abzusichern, kontaktiere uns einfach <0>hier.</0>" msgstr "Hast du keinen Passwortwechsel angefordert? Wir helfen dir, dein Konto abzusichern, kontaktiere uns einfach <0>hier.</0>"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:570 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:578
msgid "Direct link receiver" msgid "Direct link receiver"
msgstr "Empfänger des direkten Links" msgstr "Empfänger des direkten Links"
#: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:149 #: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:149
msgid "Document \"{0}\" - Rejected by {1}" msgid "Document \"{0}\" - Rejected by {1}"
msgstr "" msgstr "Dokument \"{0}\" - Abgelehnt von {1}"
#: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:109 #: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:109
msgid "Document \"{0}\" - Rejection Confirmed" msgid "Document \"{0}\" - Rejection Confirmed"
msgstr "" msgstr "Dokument \"{0}\" - Ablehnung Bestätigt"
#: packages/ui/components/document/document-global-auth-access-select.tsx:62 #: packages/ui/components/document/document-global-auth-access-select.tsx:62
#: packages/ui/primitives/document-flow/add-settings.tsx:216 #: packages/ui/primitives/document-flow/add-settings.tsx:216
@@ -684,17 +692,17 @@ msgstr ""
msgid "Document access" msgid "Document access"
msgstr "Dokumentenzugriff" msgstr "Dokumentenzugriff"
#: packages/lib/utils/document-audit-logs.ts:306 #: packages/lib/utils/document-audit-logs.ts:310
msgid "Document access auth updated" msgid "Document access auth updated"
msgstr "Die Authentifizierung für den Dokumentenzugriff wurde aktualisiert" msgstr "Die Authentifizierung für den Dokumentenzugriff wurde aktualisiert"
#: packages/lib/server-only/document/delete-document.ts:246 #: packages/lib/server-only/document/delete-document.ts:256
#: packages/lib/server-only/document/super-delete-document.ts:98 #: packages/lib/server-only/document/super-delete-document.ts:98
msgid "Document Cancelled" msgid "Document Cancelled"
msgstr "Dokument storniert" msgstr "Dokument storniert"
#: packages/lib/utils/document-audit-logs.ts:369 #: packages/lib/utils/document-audit-logs.ts:373
#: packages/lib/utils/document-audit-logs.ts:370 #: packages/lib/utils/document-audit-logs.ts:374
msgid "Document completed" msgid "Document completed"
msgstr "Dokument abgeschlossen" msgstr "Dokument abgeschlossen"
@@ -732,15 +740,15 @@ msgstr "Dokument gelöscht!"
msgid "Document Distribution Method" msgid "Document Distribution Method"
msgstr "Verteilungsmethode für Dokumente" msgstr "Verteilungsmethode für Dokumente"
#: packages/lib/utils/document-audit-logs.ts:326 #: packages/lib/utils/document-audit-logs.ts:330
msgid "Document external ID updated" msgid "Document external ID updated"
msgstr "Externe ID des Dokuments aktualisiert" msgstr "Externe ID des Dokuments aktualisiert"
#: packages/lib/utils/document-audit-logs.ts:334 #: packages/lib/utils/document-audit-logs.ts:338
msgid "Document moved to team" msgid "Document moved to team"
msgstr "Dokument ins Team verschoben" msgstr "Dokument ins Team verschoben"
#: packages/lib/utils/document-audit-logs.ts:318 #: packages/lib/utils/document-audit-logs.ts:322
msgid "Document opened" msgid "Document opened"
msgstr "Dokument geöffnet" msgstr "Dokument geöffnet"
@@ -750,25 +758,32 @@ msgstr "E-Mail über ausstehende Dokumente"
#: packages/email/template-components/template-document-rejected.tsx:21 #: packages/email/template-components/template-document-rejected.tsx:21
msgid "Document Rejected" msgid "Document Rejected"
msgstr "Dokument Abgelehnt"
#~ msgid "Document Rejection Confirmed"
#~ msgstr "Document Rejection Confirmed"
#: packages/lib/utils/document-audit-logs.ts:294
msgid "Document restored"
msgstr "" msgstr ""
#: packages/lib/utils/document-audit-logs.ts:330 #: packages/lib/utils/document-audit-logs.ts:334
msgid "Document sent" msgid "Document sent"
msgstr "Dokument gesendet" msgstr "Dokument gesendet"
#: packages/lib/utils/document-audit-logs.ts:310 #: packages/lib/utils/document-audit-logs.ts:314
msgid "Document signing auth updated" msgid "Document signing auth updated"
msgstr "Dokument unterzeichnen Authentifizierung aktualisiert" msgstr "Dokument unterzeichnen Authentifizierung aktualisiert"
#: packages/lib/utils/document-audit-logs.ts:322 #: packages/lib/utils/document-audit-logs.ts:326
msgid "Document title updated" msgid "Document title updated"
msgstr "Dokumenttitel aktualisiert" msgstr "Dokumenttitel aktualisiert"
#: packages/lib/utils/document-audit-logs.ts:314 #: packages/lib/utils/document-audit-logs.ts:318
msgid "Document updated" msgid "Document updated"
msgstr "Dokument aktualisiert" msgstr "Dokument aktualisiert"
#: packages/lib/utils/document-audit-logs.ts:302 #: packages/lib/utils/document-audit-logs.ts:306
msgid "Document visibility updated" msgid "Document visibility updated"
msgstr "Sichtbarkeit des Dokuments aktualisiert" msgstr "Sichtbarkeit des Dokuments aktualisiert"
@@ -785,8 +800,8 @@ msgstr "Entwurf"
msgid "Drag & drop your PDF here." msgid "Drag & drop your PDF here."
msgstr "Ziehen Sie Ihr PDF hierher." msgstr "Ziehen Sie Ihr PDF hierher."
#: packages/ui/primitives/document-flow/add-fields.tsx:1058 #: packages/ui/primitives/document-flow/add-fields.tsx:1065
#: packages/ui/primitives/template-flow/add-template-fields.tsx:820 #: packages/ui/primitives/template-flow/add-template-fields.tsx:860
msgid "Dropdown" msgid "Dropdown"
msgstr "Dropdown" msgstr "Dropdown"
@@ -795,14 +810,14 @@ msgid "Dropdown options"
msgstr "Dropdown-Optionen" msgstr "Dropdown-Optionen"
#: packages/lib/constants/document.ts:28 #: packages/lib/constants/document.ts:28
#: packages/ui/primitives/document-flow/add-fields.tsx:875 #: packages/ui/primitives/document-flow/add-fields.tsx:882
#: packages/ui/primitives/document-flow/add-signature.tsx:272 #: packages/ui/primitives/document-flow/add-signature.tsx:272
#: packages/ui/primitives/document-flow/add-signers.tsx:512 #: packages/ui/primitives/document-flow/add-signers.tsx:512
#: packages/ui/primitives/document-flow/add-signers.tsx:519 #: packages/ui/primitives/document-flow/add-signers.tsx:519
#: packages/ui/primitives/document-flow/types.ts:54 #: packages/ui/primitives/document-flow/types.ts:54
#: packages/ui/primitives/template-flow/add-template-fields.tsx:638 #: packages/ui/primitives/template-flow/add-template-fields.tsx:677
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:463 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:471
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:470 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:478
msgid "Email" msgid "Email"
msgstr "E-Mail" msgstr "E-Mail"
@@ -814,15 +829,15 @@ msgstr "E-Mail ist erforderlich"
msgid "Email Options" msgid "Email Options"
msgstr "E-Mail-Optionen" msgstr "E-Mail-Optionen"
#: packages/lib/utils/document-audit-logs.ts:363 #: packages/lib/utils/document-audit-logs.ts:367
msgid "Email resent" msgid "Email resent"
msgstr "E-Mail erneut gesendet" msgstr "E-Mail erneut gesendet"
#: packages/lib/utils/document-audit-logs.ts:363 #: packages/lib/utils/document-audit-logs.ts:367
msgid "Email sent" msgid "Email sent"
msgstr "E-Mail gesendet" msgstr "E-Mail gesendet"
#: packages/ui/primitives/document-flow/add-fields.tsx:1123 #: packages/ui/primitives/document-flow/add-fields.tsx:1130
msgid "Empty field" msgid "Empty field"
msgstr "Leeres Feld" msgstr "Leeres Feld"
@@ -831,11 +846,12 @@ msgid "Enable Direct Link Signing"
msgstr "Direktlink-Signierung aktivieren" msgstr "Direktlink-Signierung aktivieren"
#: packages/ui/primitives/document-flow/add-signers.tsx:401 #: packages/ui/primitives/document-flow/add-signers.tsx:401
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:362 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:370
msgid "Enable signing order" msgid "Enable signing order"
msgstr "Aktiviere die Signaturreihenfolge" msgstr "Aktiviere die Signaturreihenfolge"
#: packages/ui/primitives/document-flow/add-fields.tsx:795 #: packages/ui/primitives/document-flow/add-fields.tsx:802
#: packages/ui/primitives/template-flow/add-template-fields.tsx:597
msgid "Enable Typed Signatures" msgid "Enable Typed Signatures"
msgstr "Aktivieren Sie getippte Unterschriften" msgstr "Aktivieren Sie getippte Unterschriften"
@@ -882,11 +898,11 @@ msgstr "Feldbeschriftung"
msgid "Field placeholder" msgid "Field placeholder"
msgstr "Feldplatzhalter" msgstr "Feldplatzhalter"
#: packages/lib/utils/document-audit-logs.ts:294 #: packages/lib/utils/document-audit-logs.ts:298
msgid "Field signed" msgid "Field signed"
msgstr "Feld unterschrieben" msgstr "Feld unterschrieben"
#: packages/lib/utils/document-audit-logs.ts:298 #: packages/lib/utils/document-audit-logs.ts:302
msgid "Field unsigned" msgid "Field unsigned"
msgstr "Feld nicht unterschrieben" msgstr "Feld nicht unterschrieben"
@@ -923,10 +939,13 @@ msgstr "Globale Empfängerauthentifizierung"
msgid "Go Back" msgid "Go Back"
msgstr "Zurück" msgstr "Zurück"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:398 #: packages/ui/primitives/signature-pad/signature-pad.tsx:404
msgid "Green" msgid "Green"
msgstr "Grün" msgstr "Grün"
#~ msgid "Hello {recipientName},"
#~ msgstr "Hello {recipientName},"
#: packages/email/templates/reset-password.tsx:56 #: packages/email/templates/reset-password.tsx:56
msgid "Hi, {userName} <0>({userEmail})</0>" msgid "Hi, {userName} <0>({userEmail})</0>"
msgstr "Hallo, {userName} <0>({userEmail})</0>" msgstr "Hallo, {userName} <0>({userEmail})</0>"
@@ -1010,14 +1029,14 @@ msgstr "Nachricht <0>(Optional)</0>"
msgid "Min" msgid "Min"
msgstr "Min" msgstr "Min"
#: packages/ui/primitives/document-flow/add-fields.tsx:901 #: packages/ui/primitives/document-flow/add-fields.tsx:908
#: packages/ui/primitives/document-flow/add-signature.tsx:298 #: packages/ui/primitives/document-flow/add-signature.tsx:298
#: packages/ui/primitives/document-flow/add-signers.tsx:550 #: packages/ui/primitives/document-flow/add-signers.tsx:550
#: packages/ui/primitives/document-flow/add-signers.tsx:556 #: packages/ui/primitives/document-flow/add-signers.tsx:556
#: packages/ui/primitives/document-flow/types.ts:55 #: packages/ui/primitives/document-flow/types.ts:55
#: packages/ui/primitives/template-flow/add-template-fields.tsx:664 #: packages/ui/primitives/template-flow/add-template-fields.tsx:703
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:498 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:506
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:504 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:512
msgid "Name" msgid "Name"
msgstr "Name" msgstr "Name"
@@ -1033,8 +1052,8 @@ msgstr "Muss unterzeichnen"
msgid "Needs to view" msgid "Needs to view"
msgstr "Muss sehen" msgstr "Muss sehen"
#: packages/ui/primitives/document-flow/add-fields.tsx:686 #: packages/ui/primitives/document-flow/add-fields.tsx:693
#: packages/ui/primitives/template-flow/add-template-fields.tsx:504 #: packages/ui/primitives/template-flow/add-template-fields.tsx:516
msgid "No recipient matching this description was found." msgid "No recipient matching this description was found."
msgstr "Kein passender Empfänger mit dieser Beschreibung gefunden." msgstr "Kein passender Empfänger mit dieser Beschreibung gefunden."
@@ -1042,8 +1061,8 @@ msgstr "Kein passender Empfänger mit dieser Beschreibung gefunden."
msgid "No recipients" msgid "No recipients"
msgstr "Keine Empfänger" msgstr "Keine Empfänger"
#: packages/ui/primitives/document-flow/add-fields.tsx:701 #: packages/ui/primitives/document-flow/add-fields.tsx:708
#: packages/ui/primitives/template-flow/add-template-fields.tsx:519 #: packages/ui/primitives/template-flow/add-template-fields.tsx:531
msgid "No recipients with this role" msgid "No recipients with this role"
msgstr "Keine Empfänger mit dieser Rolle" msgstr "Keine Empfänger mit dieser Rolle"
@@ -1071,9 +1090,9 @@ msgstr "Kein Wert gefunden."
msgid "None" msgid "None"
msgstr "Keine" msgstr "Keine"
#: packages/ui/primitives/document-flow/add-fields.tsx:979 #: packages/ui/primitives/document-flow/add-fields.tsx:986
#: packages/ui/primitives/document-flow/types.ts:56 #: packages/ui/primitives/document-flow/types.ts:56
#: packages/ui/primitives/template-flow/add-template-fields.tsx:742 #: packages/ui/primitives/template-flow/add-template-fields.tsx:781
msgid "Number" msgid "Number"
msgstr "Nummer" msgstr "Nummer"
@@ -1164,7 +1183,7 @@ msgstr "Bitte bestätige deine E-Mail-Adresse"
msgid "Please try again or contact our support." msgid "Please try again or contact our support."
msgstr "Bitte versuchen Sie es erneut oder kontaktieren Sie unseren Support." msgstr "Bitte versuchen Sie es erneut oder kontaktieren Sie unseren Support."
#: packages/ui/primitives/template-flow/add-template-fields.tsx:768 #: packages/ui/primitives/document-flow/types.ts:57
msgid "Radio" msgid "Radio"
msgstr "Radio" msgstr "Radio"
@@ -1182,14 +1201,14 @@ msgstr "Nur lesen"
#: packages/email/template-components/template-document-rejected.tsx:32 #: packages/email/template-components/template-document-rejected.tsx:32
msgid "Reason for rejection: {rejectionReason}" msgid "Reason for rejection: {rejectionReason}"
msgstr "" msgstr "Grund für die Ablehnung: {rejectionReason}"
#: packages/ui/components/recipient/recipient-role-select.tsx:95 #: packages/ui/components/recipient/recipient-role-select.tsx:95
msgid "Receives copy" msgid "Receives copy"
msgstr "Erhält Kopie" msgstr "Erhält Kopie"
#: packages/lib/utils/document-audit-logs.ts:338 #: packages/lib/utils/document-audit-logs.ts:342
#: packages/lib/utils/document-audit-logs.ts:353 #: packages/lib/utils/document-audit-logs.ts:357
msgid "Recipient" msgid "Recipient"
msgstr "Empfänger" msgstr "Empfänger"
@@ -1207,7 +1226,7 @@ msgstr "E-Mail des entfernten Empfängers"
msgid "Recipient signing request email" msgid "Recipient signing request email"
msgstr "E-Mail zur Unterzeichnungsanfrage des Empfängers" msgstr "E-Mail zur Unterzeichnungsanfrage des Empfängers"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:384 #: packages/ui/primitives/signature-pad/signature-pad.tsx:390
msgid "Red" msgid "Red"
msgstr "Rot" msgstr "Rot"
@@ -1218,15 +1237,15 @@ msgstr "Weiterleitungs-URL"
#: packages/email/template-components/template-document-invite.tsx:96 #: packages/email/template-components/template-document-invite.tsx:96
msgid "Reject Document" msgid "Reject Document"
msgstr "" msgstr "Dokument Ablehnen"
#: packages/email/template-components/template-document-rejection-confirmed.tsx:22 #: packages/email/template-components/template-document-rejection-confirmed.tsx:22
msgid "Rejection Confirmed" msgid "Rejection Confirmed"
msgstr "" msgstr "Ablehnung Bestätigt"
#: packages/email/template-components/template-document-rejection-confirmed.tsx:34 #: packages/email/template-components/template-document-rejection-confirmed.tsx:34
msgid "Rejection reason: {reason}" msgid "Rejection reason: {reason}"
msgstr "" msgstr "Ablehnungsgrund: {reason}"
#: packages/lib/server-only/document/resend-document.tsx:192 #: packages/lib/server-only/document/resend-document.tsx:192
msgid "Reminder: {0}" msgid "Reminder: {0}"
@@ -1244,7 +1263,7 @@ msgstr "Erinnerung: Bitte {recipientActionVerb} dieses Dokument"
msgid "Reminder: Please {recipientActionVerb} your document" msgid "Reminder: Please {recipientActionVerb} your document"
msgstr "Erinnerung: Bitte {recipientActionVerb} dein Dokument" msgstr "Erinnerung: Bitte {recipientActionVerb} dein Dokument"
#: packages/ui/primitives/document-flow/add-fields.tsx:1110 #: packages/ui/primitives/document-flow/add-fields.tsx:1117
msgid "Remove" msgid "Remove"
msgstr "Entfernen" msgstr "Entfernen"
@@ -1276,7 +1295,7 @@ msgstr "Zeilen pro Seite"
msgid "Save" msgid "Save"
msgstr "Speichern" msgstr "Speichern"
#: packages/ui/primitives/template-flow/add-template-fields.tsx:854 #: packages/ui/primitives/template-flow/add-template-fields.tsx:893
msgid "Save Template" msgid "Save Template"
msgstr "Vorlage speichern" msgstr "Vorlage speichern"
@@ -1285,6 +1304,7 @@ msgid "Search languages..."
msgstr "Sprachen suchen..." msgstr "Sprachen suchen..."
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:115 #: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:115
#: packages/ui/primitives/document-flow/types.ts:59
msgid "Select" msgid "Select"
msgstr "Auswählen" msgstr "Auswählen"
@@ -1348,7 +1368,7 @@ msgid "Share your signing experience!"
msgstr "Teilen Sie Ihre Unterzeichnungserfahrung!" msgstr "Teilen Sie Ihre Unterzeichnungserfahrung!"
#: packages/ui/primitives/document-flow/add-signers.tsx:709 #: packages/ui/primitives/document-flow/add-signers.tsx:709
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:655 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:663
msgid "Show advanced settings" msgid "Show advanced settings"
msgstr "Erweiterte Einstellungen anzeigen" msgstr "Erweiterte Einstellungen anzeigen"
@@ -1364,11 +1384,11 @@ msgstr "Dokument signieren"
msgid "Sign In" msgid "Sign In"
msgstr "Anmelden" msgstr "Anmelden"
#: packages/ui/primitives/document-flow/add-fields.tsx:823 #: packages/ui/primitives/document-flow/add-fields.tsx:830
#: packages/ui/primitives/document-flow/add-signature.tsx:323 #: packages/ui/primitives/document-flow/add-signature.tsx:323
#: packages/ui/primitives/document-flow/field-icon.tsx:52 #: packages/ui/primitives/document-flow/field-icon.tsx:52
#: packages/ui/primitives/document-flow/types.ts:49 #: packages/ui/primitives/document-flow/types.ts:49
#: packages/ui/primitives/template-flow/add-template-fields.tsx:586 #: packages/ui/primitives/template-flow/add-template-fields.tsx:625
msgid "Signature" msgid "Signature"
msgstr "Unterschrift" msgstr "Unterschrift"
@@ -1451,9 +1471,9 @@ msgstr "Team-E-Mail für {teamName} auf Documenso entfernt"
msgid "Template title" msgid "Template title"
msgstr "Vorlagentitel" msgstr "Vorlagentitel"
#: packages/ui/primitives/document-flow/add-fields.tsx:953 #: packages/ui/primitives/document-flow/add-fields.tsx:960
#: packages/ui/primitives/document-flow/types.ts:52 #: packages/ui/primitives/document-flow/types.ts:52
#: packages/ui/primitives/template-flow/add-template-fields.tsx:716 #: packages/ui/primitives/template-flow/add-template-fields.tsx:755
msgid "Text" msgid "Text"
msgstr "Text" msgstr "Text"
@@ -1473,9 +1493,12 @@ msgstr "Die Authentifizierung, die erforderlich ist, damit Empfänger das Signat
msgid "The authentication required for recipients to view the document." msgid "The authentication required for recipients to view the document."
msgstr "Die Authentifizierung, die erforderlich ist, damit Empfänger das Dokument anzeigen können." msgstr "Die Authentifizierung, die erforderlich ist, damit Empfänger das Dokument anzeigen können."
#~ msgid "The document owner has been notified of this rejection. No further action is required from you at this time."
#~ msgstr "The document owner has been notified of this rejection. No further action is required from you at this time."
#: packages/email/template-components/template-document-rejection-confirmed.tsx:39 #: packages/email/template-components/template-document-rejection-confirmed.tsx:39
msgid "The document owner has been notified of this rejection. No further action is required from you at this time. The document owner may contact you with any questions regarding this rejection." msgid "The document owner has been notified of this rejection. No further action is required from you at this time. The document owner may contact you with any questions regarding this rejection."
msgstr "" msgstr "Der Dokumenteninhaber wurde über diese Ablehnung informiert. Es sind derzeit keine weiteren Maßnahmen von Ihnen erforderlich. Der Dokumenteninhaber kann Sie bei Fragen zu dieser Ablehnung kontaktieren."
#: packages/ui/components/document/document-send-email-message-helper.tsx:31 #: packages/ui/components/document/document-send-email-message-helper.tsx:31
msgid "The document's name" msgid "The document's name"
@@ -1545,7 +1568,7 @@ msgstr "Dies kann überschrieben werden, indem die Authentifizierungsanforderung
msgid "This document can not be recovered, if you would like to dispute the reason for future documents please contact support." msgid "This document can not be recovered, if you would like to dispute the reason for future documents please contact support."
msgstr "Dieses Dokument kann nicht wiederhergestellt werden. Wenn du den Grund für zukünftige Dokumente anfechten möchtest, kontaktiere bitte den Support." msgstr "Dieses Dokument kann nicht wiederhergestellt werden. Wenn du den Grund für zukünftige Dokumente anfechten möchtest, kontaktiere bitte den Support."
#: packages/ui/primitives/document-flow/add-fields.tsx:757 #: packages/ui/primitives/document-flow/add-fields.tsx:764
msgid "This document has already been sent to this recipient. You can no longer edit this recipient." msgid "This document has already been sent to this recipient. You can no longer edit this recipient."
msgstr "Dieses Dokument wurde bereits an diesen Empfänger gesendet. Sie können diesen Empfänger nicht mehr bearbeiten." msgstr "Dieses Dokument wurde bereits an diesen Empfänger gesendet. Sie können diesen Empfänger nicht mehr bearbeiten."
@@ -1557,9 +1580,12 @@ msgstr "Dieses Dokument ist durch ein Passwort geschützt. Bitte geben Sie das P
msgid "This document was sent using <0>Documenso.</0>" msgid "This document was sent using <0>Documenso.</0>"
msgstr "Dieses Dokument wurde mit <0>Documenso.</0> gesendet" msgstr "Dieses Dokument wurde mit <0>Documenso.</0> gesendet"
#~ msgid "This email confirms that you have rejected the document \"{documentName}\" sent by {documentOwnerName}."
#~ msgstr "This email confirms that you have rejected the document \"{documentName}\" sent by {documentOwnerName}."
#: packages/email/template-components/template-document-rejection-confirmed.tsx:26 #: packages/email/template-components/template-document-rejection-confirmed.tsx:26
msgid "This email confirms that you have rejected the document <0>\"{documentName}\"</0> sent by {documentOwnerName}." msgid "This email confirms that you have rejected the document <0>\"{documentName}\"</0> sent by {documentOwnerName}."
msgstr "" msgstr "Diese E-Mail bestätigt, dass Sie das Dokument <0>\"{documentName}\"</0> abgelehnt haben, das von {documentOwnerName} gesendet wurde."
#: packages/ui/components/document/document-email-checkboxes.tsx:94 #: packages/ui/components/document/document-email-checkboxes.tsx:94
msgid "This email is sent to the recipient if they are removed from a pending document." msgid "This email is sent to the recipient if they are removed from a pending document."
@@ -1573,7 +1599,7 @@ msgstr "Diese E-Mail wird an den Empfänger gesendet und fordert ihn auf, das Do
msgid "This email will be sent to the recipient who has just signed the document, if there are still other recipients who have not signed yet." msgid "This email will be sent to the recipient who has just signed the document, if there are still other recipients who have not signed yet."
msgstr "Diese E-Mail wird an den Empfänger gesendet, der das Dokument gerade unterschrieben hat, wenn es noch andere Empfänger gibt, die noch nicht unterschrieben haben." msgstr "Diese E-Mail wird an den Empfänger gesendet, der das Dokument gerade unterschrieben hat, wenn es noch andere Empfänger gibt, die noch nicht unterschrieben haben."
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:573 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:581
msgid "This field cannot be modified or deleted. When you share this template's direct link or add it to your public profile, anyone who accesses it can input their name and email, and fill in the fields assigned to them." msgid "This field cannot be modified or deleted. When you share this template's direct link or add it to your public profile, anyone who accesses it can input their name and email, and fill in the fields assigned to them."
msgstr "Dieses Feld kann nicht geändert oder gelöscht werden. Wenn Sie den direkten Link dieser Vorlage teilen oder zu Ihrem öffentlichen Profil hinzufügen, kann jeder, der darauf zugreift, seinen Namen und seine E-Mail-Adresse eingeben und die ihm zugewiesenen Felder ausfüllen." msgstr "Dieses Feld kann nicht geändert oder gelöscht werden. Wenn Sie den direkten Link dieser Vorlage teilen oder zu Ihrem öffentlichen Profil hinzufügen, kann jeder, der darauf zugreift, seinen Namen und seine E-Mail-Adresse eingeben und die ihm zugewiesenen Felder ausfüllen."
@@ -1581,7 +1607,7 @@ msgstr "Dieses Feld kann nicht geändert oder gelöscht werden. Wenn Sie den dir
msgid "This is how the document will reach the recipients once the document is ready for signing." msgid "This is how the document will reach the recipients once the document is ready for signing."
msgstr "So wird das Dokument die Empfänger erreichen, sobald es zum Unterschreiben bereit ist." msgstr "So wird das Dokument die Empfänger erreichen, sobald es zum Unterschreiben bereit ist."
#: packages/ui/primitives/document-flow/add-fields.tsx:1090 #: packages/ui/primitives/document-flow/add-fields.tsx:1097
msgid "This recipient can no longer be modified as they have signed a field, or completed the document." msgid "This recipient can no longer be modified as they have signed a field, or completed the document."
msgstr "Dieser Empfänger kann nicht mehr bearbeitet werden, da er ein Feld unterschrieben oder das Dokument abgeschlossen hat." msgstr "Dieser Empfänger kann nicht mehr bearbeitet werden, da er ein Feld unterschrieben oder das Dokument abgeschlossen hat."
@@ -1610,8 +1636,8 @@ msgstr "Zeitzone"
msgid "Title" msgid "Title"
msgstr "Titel" msgstr "Titel"
#: packages/ui/primitives/document-flow/add-fields.tsx:1073 #: packages/ui/primitives/document-flow/add-fields.tsx:1080
#: packages/ui/primitives/template-flow/add-template-fields.tsx:834 #: packages/ui/primitives/template-flow/add-template-fields.tsx:873
msgid "To proceed further, please set at least one value for the {0} field." msgid "To proceed further, please set at least one value for the {0} field."
msgstr "Um fortzufahren, legen Sie bitte mindestens einen Wert für das Feld {0} fest." msgstr "Um fortzufahren, legen Sie bitte mindestens einen Wert für das Feld {0} fest."
@@ -1738,7 +1764,7 @@ msgstr "Sie können die folgenden Variablen in Ihrer Nachricht verwenden:"
#: packages/email/template-components/template-document-rejected.tsx:37 #: packages/email/template-components/template-document-rejected.tsx:37
msgid "You can view the document and its status by clicking the button below." msgid "You can view the document and its status by clicking the button below."
msgstr "" msgstr "Sie können das Dokument und seinen Status einsehen, indem Sie auf die Schaltfläche unten klicken."
#: packages/ui/primitives/document-dropzone.tsx:43 #: packages/ui/primitives/document-dropzone.tsx:43
msgid "You cannot upload documents at this time." msgid "You cannot upload documents at this time."
@@ -1775,7 +1801,10 @@ msgstr "Sie haben Ihr Dokumentenlimit erreicht."
#: packages/email/templates/document-rejection-confirmed.tsx:27 #: packages/email/templates/document-rejection-confirmed.tsx:27
msgid "You have rejected the document '{documentName}'" msgid "You have rejected the document '{documentName}'"
msgstr "" msgstr "Sie haben das Dokument '{documentName}' abgelehnt"
#~ msgid "You have rejected the document \"{documentName}\""
#~ msgstr "You have rejected the document \"{documentName}\""
#: packages/email/template-components/template-document-self-signed.tsx:42 #: packages/email/template-components/template-document-self-signed.tsx:42
msgid "You have signed “{documentName}”" msgid "You have signed “{documentName}”"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: de\n" "Language: de\n"
"Project-Id-Version: documenso-app\n" "Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-11-12 05:45\n" "PO-Revision-Date: 2024-11-20 11:56\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: German\n" "Language-Team: German\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"

File diff suppressed because it is too large Load Diff

View File

@@ -130,11 +130,11 @@ msgstr "{prefix} created the document"
msgid "{prefix} deleted the document" msgid "{prefix} deleted the document"
msgstr "{prefix} deleted the document" msgstr "{prefix} deleted the document"
#: packages/lib/utils/document-audit-logs.ts:335 #: packages/lib/utils/document-audit-logs.ts:339
msgid "{prefix} moved the document to team" msgid "{prefix} moved the document to team"
msgstr "{prefix} moved the document to team" msgstr "{prefix} moved the document to team"
#: packages/lib/utils/document-audit-logs.ts:319 #: packages/lib/utils/document-audit-logs.ts:323
msgid "{prefix} opened the document" msgid "{prefix} opened the document"
msgstr "{prefix} opened the document" msgstr "{prefix} opened the document"
@@ -146,23 +146,27 @@ msgstr "{prefix} removed a field"
msgid "{prefix} removed a recipient" msgid "{prefix} removed a recipient"
msgstr "{prefix} removed a recipient" msgstr "{prefix} removed a recipient"
#: packages/lib/utils/document-audit-logs.ts:365 #: packages/lib/utils/document-audit-logs.ts:369
msgid "{prefix} resent an email to {0}" msgid "{prefix} resent an email to {0}"
msgstr "{prefix} resent an email to {0}" msgstr "{prefix} resent an email to {0}"
#: packages/lib/utils/document-audit-logs.ts:366 #: packages/lib/utils/document-audit-logs.ts:295
msgid "{prefix} restored the document"
msgstr "{prefix} restored the document"
#: packages/lib/utils/document-audit-logs.ts:370
msgid "{prefix} sent an email to {0}" msgid "{prefix} sent an email to {0}"
msgstr "{prefix} sent an email to {0}" msgstr "{prefix} sent an email to {0}"
#: packages/lib/utils/document-audit-logs.ts:331 #: packages/lib/utils/document-audit-logs.ts:335
msgid "{prefix} sent the document" msgid "{prefix} sent the document"
msgstr "{prefix} sent the document" msgstr "{prefix} sent the document"
#: packages/lib/utils/document-audit-logs.ts:295 #: packages/lib/utils/document-audit-logs.ts:299
msgid "{prefix} signed a field" msgid "{prefix} signed a field"
msgstr "{prefix} signed a field" msgstr "{prefix} signed a field"
#: packages/lib/utils/document-audit-logs.ts:299 #: packages/lib/utils/document-audit-logs.ts:303
msgid "{prefix} unsigned a field" msgid "{prefix} unsigned a field"
msgstr "{prefix} unsigned a field" msgstr "{prefix} unsigned a field"
@@ -174,27 +178,27 @@ msgstr "{prefix} updated a field"
msgid "{prefix} updated a recipient" msgid "{prefix} updated a recipient"
msgstr "{prefix} updated a recipient" msgstr "{prefix} updated a recipient"
#: packages/lib/utils/document-audit-logs.ts:315 #: packages/lib/utils/document-audit-logs.ts:319
msgid "{prefix} updated the document" msgid "{prefix} updated the document"
msgstr "{prefix} updated the document" msgstr "{prefix} updated the document"
#: packages/lib/utils/document-audit-logs.ts:307 #: packages/lib/utils/document-audit-logs.ts:311
msgid "{prefix} updated the document access auth requirements" msgid "{prefix} updated the document access auth requirements"
msgstr "{prefix} updated the document access auth requirements" msgstr "{prefix} updated the document access auth requirements"
#: packages/lib/utils/document-audit-logs.ts:327 #: packages/lib/utils/document-audit-logs.ts:331
msgid "{prefix} updated the document external ID" msgid "{prefix} updated the document external ID"
msgstr "{prefix} updated the document external ID" msgstr "{prefix} updated the document external ID"
#: packages/lib/utils/document-audit-logs.ts:311 #: packages/lib/utils/document-audit-logs.ts:315
msgid "{prefix} updated the document signing auth requirements" msgid "{prefix} updated the document signing auth requirements"
msgstr "{prefix} updated the document signing auth requirements" msgstr "{prefix} updated the document signing auth requirements"
#: packages/lib/utils/document-audit-logs.ts:323 #: packages/lib/utils/document-audit-logs.ts:327
msgid "{prefix} updated the document title" msgid "{prefix} updated the document title"
msgstr "{prefix} updated the document title" msgstr "{prefix} updated the document title"
#: packages/lib/utils/document-audit-logs.ts:303 #: packages/lib/utils/document-audit-logs.ts:307
msgid "{prefix} updated the document visibility" msgid "{prefix} updated the document visibility"
msgstr "{prefix} updated the document visibility" msgstr "{prefix} updated the document visibility"
@@ -222,27 +226,27 @@ msgstr "{teamName} has invited you to {action} {documentName}"
msgid "{teamName} ownership transfer request" msgid "{teamName} ownership transfer request"
msgstr "{teamName} ownership transfer request" msgstr "{teamName} ownership transfer request"
#: packages/lib/utils/document-audit-logs.ts:343 #: packages/lib/utils/document-audit-logs.ts:347
msgid "{userName} approved the document" msgid "{userName} approved the document"
msgstr "{userName} approved the document" msgstr "{userName} approved the document"
#: packages/lib/utils/document-audit-logs.ts:344 #: packages/lib/utils/document-audit-logs.ts:348
msgid "{userName} CC'd the document" msgid "{userName} CC'd the document"
msgstr "{userName} CC'd the document" msgstr "{userName} CC'd the document"
#: packages/lib/utils/document-audit-logs.ts:345 #: packages/lib/utils/document-audit-logs.ts:349
msgid "{userName} completed their task" msgid "{userName} completed their task"
msgstr "{userName} completed their task" msgstr "{userName} completed their task"
#: packages/lib/utils/document-audit-logs.ts:355 #: packages/lib/utils/document-audit-logs.ts:359
msgid "{userName} rejected the document" msgid "{userName} rejected the document"
msgstr "{userName} rejected the document" msgstr "{userName} rejected the document"
#: packages/lib/utils/document-audit-logs.ts:341 #: packages/lib/utils/document-audit-logs.ts:345
msgid "{userName} signed the document" msgid "{userName} signed the document"
msgstr "{userName} signed the document" msgstr "{userName} signed the document"
#: packages/lib/utils/document-audit-logs.ts:342 #: packages/lib/utils/document-audit-logs.ts:346
msgid "{userName} viewed the document" msgid "{userName} viewed the document"
msgstr "{userName} viewed the document" msgstr "{userName} viewed the document"
@@ -409,11 +413,11 @@ msgstr "Add another value"
msgid "Add myself" msgid "Add myself"
msgstr "Add myself" msgstr "Add myself"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:637 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:645
msgid "Add Myself" msgid "Add Myself"
msgstr "Add Myself" msgstr "Add Myself"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:623 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:631
msgid "Add Placeholder Recipient" msgid "Add Placeholder Recipient"
msgstr "Add Placeholder Recipient" msgstr "Add Placeholder Recipient"
@@ -438,8 +442,8 @@ msgstr "Admin"
msgid "Advanced Options" msgid "Advanced Options"
msgstr "Advanced Options" msgstr "Advanced Options"
#: packages/ui/primitives/document-flow/add-fields.tsx:573 #: packages/ui/primitives/document-flow/add-fields.tsx:576
#: packages/ui/primitives/template-flow/add-template-fields.tsx:406 #: packages/ui/primitives/template-flow/add-template-fields.tsx:414
msgid "Advanced settings" msgid "Advanced settings"
msgstr "Advanced settings" msgstr "Advanced settings"
@@ -495,11 +499,11 @@ msgstr "Approving"
msgid "Before you get started, please confirm your email address by clicking the button below:" msgid "Before you get started, please confirm your email address by clicking the button below:"
msgstr "Before you get started, please confirm your email address by clicking the button below:" msgstr "Before you get started, please confirm your email address by clicking the button below:"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:377 #: packages/ui/primitives/signature-pad/signature-pad.tsx:383
msgid "Black" msgid "Black"
msgstr "Black" msgstr "Black"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:391 #: packages/ui/primitives/signature-pad/signature-pad.tsx:397
msgid "Blue" msgid "Blue"
msgstr "Blue" msgstr "Blue"
@@ -545,6 +549,10 @@ msgstr "Ccers"
msgid "Character Limit" msgid "Character Limit"
msgstr "Character Limit" msgstr "Character Limit"
#: packages/ui/primitives/document-flow/types.ts:58
msgid "Checkbox"
msgstr "Checkbox"
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:197 #: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:197
msgid "Checkbox values" msgid "Checkbox values"
msgstr "Checkbox values" msgstr "Checkbox values"
@@ -553,7 +561,7 @@ msgstr "Checkbox values"
msgid "Clear filters" msgid "Clear filters"
msgstr "Clear filters" msgstr "Clear filters"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:411 #: packages/ui/primitives/signature-pad/signature-pad.tsx:417
msgid "Clear Signature" msgid "Clear Signature"
msgstr "Clear Signature" msgstr "Clear Signature"
@@ -580,8 +588,8 @@ msgstr "Completed Document"
msgid "Configure Direct Recipient" msgid "Configure Direct Recipient"
msgstr "Configure Direct Recipient" msgstr "Configure Direct Recipient"
#: packages/ui/primitives/document-flow/add-fields.tsx:574 #: packages/ui/primitives/document-flow/add-fields.tsx:577
#: packages/ui/primitives/template-flow/add-template-fields.tsx:407 #: packages/ui/primitives/template-flow/add-template-fields.tsx:415
msgid "Configure the {0} field" msgid "Configure the {0} field"
msgstr "Configure the {0} field" msgstr "Configure the {0} field"
@@ -642,9 +650,9 @@ msgstr "Create account"
msgid "Custom Text" msgid "Custom Text"
msgstr "Custom Text" msgstr "Custom Text"
#: packages/ui/primitives/document-flow/add-fields.tsx:927 #: packages/ui/primitives/document-flow/add-fields.tsx:934
#: packages/ui/primitives/document-flow/types.ts:53 #: packages/ui/primitives/document-flow/types.ts:53
#: packages/ui/primitives/template-flow/add-template-fields.tsx:690 #: packages/ui/primitives/template-flow/add-template-fields.tsx:729
msgid "Date" msgid "Date"
msgstr "Date" msgstr "Date"
@@ -661,7 +669,7 @@ msgstr "Decline"
msgid "Didn't request a password change? We are here to help you secure your account, just <0>contact us.</0>" msgid "Didn't request a password change? We are here to help you secure your account, just <0>contact us.</0>"
msgstr "Didn't request a password change? We are here to help you secure your account, just <0>contact us.</0>" msgstr "Didn't request a password change? We are here to help you secure your account, just <0>contact us.</0>"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:570 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:578
msgid "Direct link receiver" msgid "Direct link receiver"
msgstr "Direct link receiver" msgstr "Direct link receiver"
@@ -679,17 +687,17 @@ msgstr "Document \"{0}\" - Rejection Confirmed"
msgid "Document access" msgid "Document access"
msgstr "Document access" msgstr "Document access"
#: packages/lib/utils/document-audit-logs.ts:306 #: packages/lib/utils/document-audit-logs.ts:310
msgid "Document access auth updated" msgid "Document access auth updated"
msgstr "Document access auth updated" msgstr "Document access auth updated"
#: packages/lib/server-only/document/delete-document.ts:246 #: packages/lib/server-only/document/delete-document.ts:256
#: packages/lib/server-only/document/super-delete-document.ts:98 #: packages/lib/server-only/document/super-delete-document.ts:98
msgid "Document Cancelled" msgid "Document Cancelled"
msgstr "Document Cancelled" msgstr "Document Cancelled"
#: packages/lib/utils/document-audit-logs.ts:369 #: packages/lib/utils/document-audit-logs.ts:373
#: packages/lib/utils/document-audit-logs.ts:370 #: packages/lib/utils/document-audit-logs.ts:374
msgid "Document completed" msgid "Document completed"
msgstr "Document completed" msgstr "Document completed"
@@ -727,15 +735,15 @@ msgstr "Document Deleted!"
msgid "Document Distribution Method" msgid "Document Distribution Method"
msgstr "Document Distribution Method" msgstr "Document Distribution Method"
#: packages/lib/utils/document-audit-logs.ts:326 #: packages/lib/utils/document-audit-logs.ts:330
msgid "Document external ID updated" msgid "Document external ID updated"
msgstr "Document external ID updated" msgstr "Document external ID updated"
#: packages/lib/utils/document-audit-logs.ts:334 #: packages/lib/utils/document-audit-logs.ts:338
msgid "Document moved to team" msgid "Document moved to team"
msgstr "Document moved to team" msgstr "Document moved to team"
#: packages/lib/utils/document-audit-logs.ts:318 #: packages/lib/utils/document-audit-logs.ts:322
msgid "Document opened" msgid "Document opened"
msgstr "Document opened" msgstr "Document opened"
@@ -750,23 +758,27 @@ msgstr "Document Rejected"
#~ msgid "Document Rejection Confirmed" #~ msgid "Document Rejection Confirmed"
#~ msgstr "Document Rejection Confirmed" #~ msgstr "Document Rejection Confirmed"
#: packages/lib/utils/document-audit-logs.ts:330 #: packages/lib/utils/document-audit-logs.ts:294
msgid "Document restored"
msgstr "Document restored"
#: packages/lib/utils/document-audit-logs.ts:334
msgid "Document sent" msgid "Document sent"
msgstr "Document sent" msgstr "Document sent"
#: packages/lib/utils/document-audit-logs.ts:310 #: packages/lib/utils/document-audit-logs.ts:314
msgid "Document signing auth updated" msgid "Document signing auth updated"
msgstr "Document signing auth updated" msgstr "Document signing auth updated"
#: packages/lib/utils/document-audit-logs.ts:322 #: packages/lib/utils/document-audit-logs.ts:326
msgid "Document title updated" msgid "Document title updated"
msgstr "Document title updated" msgstr "Document title updated"
#: packages/lib/utils/document-audit-logs.ts:314 #: packages/lib/utils/document-audit-logs.ts:318
msgid "Document updated" msgid "Document updated"
msgstr "Document updated" msgstr "Document updated"
#: packages/lib/utils/document-audit-logs.ts:302 #: packages/lib/utils/document-audit-logs.ts:306
msgid "Document visibility updated" msgid "Document visibility updated"
msgstr "Document visibility updated" msgstr "Document visibility updated"
@@ -783,8 +795,8 @@ msgstr "Draft"
msgid "Drag & drop your PDF here." msgid "Drag & drop your PDF here."
msgstr "Drag & drop your PDF here." msgstr "Drag & drop your PDF here."
#: packages/ui/primitives/document-flow/add-fields.tsx:1058 #: packages/ui/primitives/document-flow/add-fields.tsx:1065
#: packages/ui/primitives/template-flow/add-template-fields.tsx:820 #: packages/ui/primitives/template-flow/add-template-fields.tsx:860
msgid "Dropdown" msgid "Dropdown"
msgstr "Dropdown" msgstr "Dropdown"
@@ -793,14 +805,14 @@ msgid "Dropdown options"
msgstr "Dropdown options" msgstr "Dropdown options"
#: packages/lib/constants/document.ts:28 #: packages/lib/constants/document.ts:28
#: packages/ui/primitives/document-flow/add-fields.tsx:875 #: packages/ui/primitives/document-flow/add-fields.tsx:882
#: packages/ui/primitives/document-flow/add-signature.tsx:272 #: packages/ui/primitives/document-flow/add-signature.tsx:272
#: packages/ui/primitives/document-flow/add-signers.tsx:512 #: packages/ui/primitives/document-flow/add-signers.tsx:512
#: packages/ui/primitives/document-flow/add-signers.tsx:519 #: packages/ui/primitives/document-flow/add-signers.tsx:519
#: packages/ui/primitives/document-flow/types.ts:54 #: packages/ui/primitives/document-flow/types.ts:54
#: packages/ui/primitives/template-flow/add-template-fields.tsx:638 #: packages/ui/primitives/template-flow/add-template-fields.tsx:677
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:463 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:471
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:470 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:478
msgid "Email" msgid "Email"
msgstr "Email" msgstr "Email"
@@ -812,15 +824,15 @@ msgstr "Email is required"
msgid "Email Options" msgid "Email Options"
msgstr "Email Options" msgstr "Email Options"
#: packages/lib/utils/document-audit-logs.ts:363 #: packages/lib/utils/document-audit-logs.ts:367
msgid "Email resent" msgid "Email resent"
msgstr "Email resent" msgstr "Email resent"
#: packages/lib/utils/document-audit-logs.ts:363 #: packages/lib/utils/document-audit-logs.ts:367
msgid "Email sent" msgid "Email sent"
msgstr "Email sent" msgstr "Email sent"
#: packages/ui/primitives/document-flow/add-fields.tsx:1123 #: packages/ui/primitives/document-flow/add-fields.tsx:1130
msgid "Empty field" msgid "Empty field"
msgstr "Empty field" msgstr "Empty field"
@@ -829,11 +841,12 @@ msgid "Enable Direct Link Signing"
msgstr "Enable Direct Link Signing" msgstr "Enable Direct Link Signing"
#: packages/ui/primitives/document-flow/add-signers.tsx:401 #: packages/ui/primitives/document-flow/add-signers.tsx:401
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:362 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:370
msgid "Enable signing order" msgid "Enable signing order"
msgstr "Enable signing order" msgstr "Enable signing order"
#: packages/ui/primitives/document-flow/add-fields.tsx:795 #: packages/ui/primitives/document-flow/add-fields.tsx:802
#: packages/ui/primitives/template-flow/add-template-fields.tsx:597
msgid "Enable Typed Signatures" msgid "Enable Typed Signatures"
msgstr "Enable Typed Signatures" msgstr "Enable Typed Signatures"
@@ -880,11 +893,11 @@ msgstr "Field label"
msgid "Field placeholder" msgid "Field placeholder"
msgstr "Field placeholder" msgstr "Field placeholder"
#: packages/lib/utils/document-audit-logs.ts:294 #: packages/lib/utils/document-audit-logs.ts:298
msgid "Field signed" msgid "Field signed"
msgstr "Field signed" msgstr "Field signed"
#: packages/lib/utils/document-audit-logs.ts:298 #: packages/lib/utils/document-audit-logs.ts:302
msgid "Field unsigned" msgid "Field unsigned"
msgstr "Field unsigned" msgstr "Field unsigned"
@@ -921,7 +934,7 @@ msgstr "Global recipient action authentication"
msgid "Go Back" msgid "Go Back"
msgstr "Go Back" msgstr "Go Back"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:398 #: packages/ui/primitives/signature-pad/signature-pad.tsx:404
msgid "Green" msgid "Green"
msgstr "Green" msgstr "Green"
@@ -1011,14 +1024,14 @@ msgstr "Message <0>(Optional)</0>"
msgid "Min" msgid "Min"
msgstr "Min" msgstr "Min"
#: packages/ui/primitives/document-flow/add-fields.tsx:901 #: packages/ui/primitives/document-flow/add-fields.tsx:908
#: packages/ui/primitives/document-flow/add-signature.tsx:298 #: packages/ui/primitives/document-flow/add-signature.tsx:298
#: packages/ui/primitives/document-flow/add-signers.tsx:550 #: packages/ui/primitives/document-flow/add-signers.tsx:550
#: packages/ui/primitives/document-flow/add-signers.tsx:556 #: packages/ui/primitives/document-flow/add-signers.tsx:556
#: packages/ui/primitives/document-flow/types.ts:55 #: packages/ui/primitives/document-flow/types.ts:55
#: packages/ui/primitives/template-flow/add-template-fields.tsx:664 #: packages/ui/primitives/template-flow/add-template-fields.tsx:703
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:498 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:506
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:504 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:512
msgid "Name" msgid "Name"
msgstr "Name" msgstr "Name"
@@ -1034,8 +1047,8 @@ msgstr "Needs to sign"
msgid "Needs to view" msgid "Needs to view"
msgstr "Needs to view" msgstr "Needs to view"
#: packages/ui/primitives/document-flow/add-fields.tsx:686 #: packages/ui/primitives/document-flow/add-fields.tsx:693
#: packages/ui/primitives/template-flow/add-template-fields.tsx:504 #: packages/ui/primitives/template-flow/add-template-fields.tsx:516
msgid "No recipient matching this description was found." msgid "No recipient matching this description was found."
msgstr "No recipient matching this description was found." msgstr "No recipient matching this description was found."
@@ -1043,8 +1056,8 @@ msgstr "No recipient matching this description was found."
msgid "No recipients" msgid "No recipients"
msgstr "No recipients" msgstr "No recipients"
#: packages/ui/primitives/document-flow/add-fields.tsx:701 #: packages/ui/primitives/document-flow/add-fields.tsx:708
#: packages/ui/primitives/template-flow/add-template-fields.tsx:519 #: packages/ui/primitives/template-flow/add-template-fields.tsx:531
msgid "No recipients with this role" msgid "No recipients with this role"
msgstr "No recipients with this role" msgstr "No recipients with this role"
@@ -1072,9 +1085,9 @@ msgstr "No value found."
msgid "None" msgid "None"
msgstr "None" msgstr "None"
#: packages/ui/primitives/document-flow/add-fields.tsx:979 #: packages/ui/primitives/document-flow/add-fields.tsx:986
#: packages/ui/primitives/document-flow/types.ts:56 #: packages/ui/primitives/document-flow/types.ts:56
#: packages/ui/primitives/template-flow/add-template-fields.tsx:742 #: packages/ui/primitives/template-flow/add-template-fields.tsx:781
msgid "Number" msgid "Number"
msgstr "Number" msgstr "Number"
@@ -1165,7 +1178,7 @@ msgstr "Please confirm your email address"
msgid "Please try again or contact our support." msgid "Please try again or contact our support."
msgstr "Please try again or contact our support." msgstr "Please try again or contact our support."
#: packages/ui/primitives/template-flow/add-template-fields.tsx:768 #: packages/ui/primitives/document-flow/types.ts:57
msgid "Radio" msgid "Radio"
msgstr "Radio" msgstr "Radio"
@@ -1189,8 +1202,8 @@ msgstr "Reason for rejection: {rejectionReason}"
msgid "Receives copy" msgid "Receives copy"
msgstr "Receives copy" msgstr "Receives copy"
#: packages/lib/utils/document-audit-logs.ts:338 #: packages/lib/utils/document-audit-logs.ts:342
#: packages/lib/utils/document-audit-logs.ts:353 #: packages/lib/utils/document-audit-logs.ts:357
msgid "Recipient" msgid "Recipient"
msgstr "Recipient" msgstr "Recipient"
@@ -1208,7 +1221,7 @@ msgstr "Recipient removed email"
msgid "Recipient signing request email" msgid "Recipient signing request email"
msgstr "Recipient signing request email" msgstr "Recipient signing request email"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:384 #: packages/ui/primitives/signature-pad/signature-pad.tsx:390
msgid "Red" msgid "Red"
msgstr "Red" msgstr "Red"
@@ -1245,7 +1258,7 @@ msgstr "Reminder: Please {recipientActionVerb} this document"
msgid "Reminder: Please {recipientActionVerb} your document" msgid "Reminder: Please {recipientActionVerb} your document"
msgstr "Reminder: Please {recipientActionVerb} your document" msgstr "Reminder: Please {recipientActionVerb} your document"
#: packages/ui/primitives/document-flow/add-fields.tsx:1110 #: packages/ui/primitives/document-flow/add-fields.tsx:1117
msgid "Remove" msgid "Remove"
msgstr "Remove" msgstr "Remove"
@@ -1277,7 +1290,7 @@ msgstr "Rows per page"
msgid "Save" msgid "Save"
msgstr "Save" msgstr "Save"
#: packages/ui/primitives/template-flow/add-template-fields.tsx:854 #: packages/ui/primitives/template-flow/add-template-fields.tsx:893
msgid "Save Template" msgid "Save Template"
msgstr "Save Template" msgstr "Save Template"
@@ -1286,6 +1299,7 @@ msgid "Search languages..."
msgstr "Search languages..." msgstr "Search languages..."
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:115 #: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:115
#: packages/ui/primitives/document-flow/types.ts:59
msgid "Select" msgid "Select"
msgstr "Select" msgstr "Select"
@@ -1349,7 +1363,7 @@ msgid "Share your signing experience!"
msgstr "Share your signing experience!" msgstr "Share your signing experience!"
#: packages/ui/primitives/document-flow/add-signers.tsx:709 #: packages/ui/primitives/document-flow/add-signers.tsx:709
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:655 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:663
msgid "Show advanced settings" msgid "Show advanced settings"
msgstr "Show advanced settings" msgstr "Show advanced settings"
@@ -1365,11 +1379,11 @@ msgstr "Sign Document"
msgid "Sign In" msgid "Sign In"
msgstr "Sign In" msgstr "Sign In"
#: packages/ui/primitives/document-flow/add-fields.tsx:823 #: packages/ui/primitives/document-flow/add-fields.tsx:830
#: packages/ui/primitives/document-flow/add-signature.tsx:323 #: packages/ui/primitives/document-flow/add-signature.tsx:323
#: packages/ui/primitives/document-flow/field-icon.tsx:52 #: packages/ui/primitives/document-flow/field-icon.tsx:52
#: packages/ui/primitives/document-flow/types.ts:49 #: packages/ui/primitives/document-flow/types.ts:49
#: packages/ui/primitives/template-flow/add-template-fields.tsx:586 #: packages/ui/primitives/template-flow/add-template-fields.tsx:625
msgid "Signature" msgid "Signature"
msgstr "Signature" msgstr "Signature"
@@ -1452,9 +1466,9 @@ msgstr "Team email removed for {teamName} on Documenso"
msgid "Template title" msgid "Template title"
msgstr "Template title" msgstr "Template title"
#: packages/ui/primitives/document-flow/add-fields.tsx:953 #: packages/ui/primitives/document-flow/add-fields.tsx:960
#: packages/ui/primitives/document-flow/types.ts:52 #: packages/ui/primitives/document-flow/types.ts:52
#: packages/ui/primitives/template-flow/add-template-fields.tsx:716 #: packages/ui/primitives/template-flow/add-template-fields.tsx:755
msgid "Text" msgid "Text"
msgstr "Text" msgstr "Text"
@@ -1549,7 +1563,7 @@ msgstr "This can be overriden by setting the authentication requirements directl
msgid "This document can not be recovered, if you would like to dispute the reason for future documents please contact support." msgid "This document can not be recovered, if you would like to dispute the reason for future documents please contact support."
msgstr "This document can not be recovered, if you would like to dispute the reason for future documents please contact support." msgstr "This document can not be recovered, if you would like to dispute the reason for future documents please contact support."
#: packages/ui/primitives/document-flow/add-fields.tsx:757 #: packages/ui/primitives/document-flow/add-fields.tsx:764
msgid "This document has already been sent to this recipient. You can no longer edit this recipient." msgid "This document has already been sent to this recipient. You can no longer edit this recipient."
msgstr "This document has already been sent to this recipient. You can no longer edit this recipient." msgstr "This document has already been sent to this recipient. You can no longer edit this recipient."
@@ -1580,7 +1594,7 @@ msgstr "This email is sent to the recipient requesting them to sign the document
msgid "This email will be sent to the recipient who has just signed the document, if there are still other recipients who have not signed yet." msgid "This email will be sent to the recipient who has just signed the document, if there are still other recipients who have not signed yet."
msgstr "This email will be sent to the recipient who has just signed the document, if there are still other recipients who have not signed yet." msgstr "This email will be sent to the recipient who has just signed the document, if there are still other recipients who have not signed yet."
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:573 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:581
msgid "This field cannot be modified or deleted. When you share this template's direct link or add it to your public profile, anyone who accesses it can input their name and email, and fill in the fields assigned to them." msgid "This field cannot be modified or deleted. When you share this template's direct link or add it to your public profile, anyone who accesses it can input their name and email, and fill in the fields assigned to them."
msgstr "This field cannot be modified or deleted. When you share this template's direct link or add it to your public profile, anyone who accesses it can input their name and email, and fill in the fields assigned to them." msgstr "This field cannot be modified or deleted. When you share this template's direct link or add it to your public profile, anyone who accesses it can input their name and email, and fill in the fields assigned to them."
@@ -1588,7 +1602,7 @@ msgstr "This field cannot be modified or deleted. When you share this template's
msgid "This is how the document will reach the recipients once the document is ready for signing." msgid "This is how the document will reach the recipients once the document is ready for signing."
msgstr "This is how the document will reach the recipients once the document is ready for signing." msgstr "This is how the document will reach the recipients once the document is ready for signing."
#: packages/ui/primitives/document-flow/add-fields.tsx:1090 #: packages/ui/primitives/document-flow/add-fields.tsx:1097
msgid "This recipient can no longer be modified as they have signed a field, or completed the document." msgid "This recipient can no longer be modified as they have signed a field, or completed the document."
msgstr "This recipient can no longer be modified as they have signed a field, or completed the document." msgstr "This recipient can no longer be modified as they have signed a field, or completed the document."
@@ -1617,8 +1631,8 @@ msgstr "Time Zone"
msgid "Title" msgid "Title"
msgstr "Title" msgstr "Title"
#: packages/ui/primitives/document-flow/add-fields.tsx:1073 #: packages/ui/primitives/document-flow/add-fields.tsx:1080
#: packages/ui/primitives/template-flow/add-template-fields.tsx:834 #: packages/ui/primitives/template-flow/add-template-fields.tsx:873
msgid "To proceed further, please set at least one value for the {0} field." msgid "To proceed further, please set at least one value for the {0} field."
msgstr "To proceed further, please set at least one value for the {0} field." msgstr "To proceed further, please set at least one value for the {0} field."

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: es\n" "Language: es\n"
"Project-Id-Version: documenso-app\n" "Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-11-12 05:45\n" "PO-Revision-Date: 2024-11-20 11:56\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Spanish\n" "Language-Team: Spanish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -135,11 +135,11 @@ msgstr "{prefix} creó el documento"
msgid "{prefix} deleted the document" msgid "{prefix} deleted the document"
msgstr "{prefix} eliminó el documento" msgstr "{prefix} eliminó el documento"
#: packages/lib/utils/document-audit-logs.ts:335 #: packages/lib/utils/document-audit-logs.ts:339
msgid "{prefix} moved the document to team" msgid "{prefix} moved the document to team"
msgstr "{prefix} movió el documento al equipo" msgstr "{prefix} movió el documento al equipo"
#: packages/lib/utils/document-audit-logs.ts:319 #: packages/lib/utils/document-audit-logs.ts:323
msgid "{prefix} opened the document" msgid "{prefix} opened the document"
msgstr "{prefix} abrió el documento" msgstr "{prefix} abrió el documento"
@@ -151,23 +151,27 @@ msgstr "{prefix} eliminó un campo"
msgid "{prefix} removed a recipient" msgid "{prefix} removed a recipient"
msgstr "{prefix} eliminó un destinatario" msgstr "{prefix} eliminó un destinatario"
#: packages/lib/utils/document-audit-logs.ts:365 #: packages/lib/utils/document-audit-logs.ts:369
msgid "{prefix} resent an email to {0}" msgid "{prefix} resent an email to {0}"
msgstr "{prefix} reenviaron un correo electrónico a {0}" msgstr "{prefix} reenviaron un correo electrónico a {0}"
#: packages/lib/utils/document-audit-logs.ts:366 #: packages/lib/utils/document-audit-logs.ts:295
msgid "{prefix} restored the document"
msgstr ""
#: packages/lib/utils/document-audit-logs.ts:370
msgid "{prefix} sent an email to {0}" msgid "{prefix} sent an email to {0}"
msgstr "{prefix} envió un correo electrónico a {0}" msgstr "{prefix} envió un correo electrónico a {0}"
#: packages/lib/utils/document-audit-logs.ts:331 #: packages/lib/utils/document-audit-logs.ts:335
msgid "{prefix} sent the document" msgid "{prefix} sent the document"
msgstr "{prefix} envió el documento" msgstr "{prefix} envió el documento"
#: packages/lib/utils/document-audit-logs.ts:295 #: packages/lib/utils/document-audit-logs.ts:299
msgid "{prefix} signed a field" msgid "{prefix} signed a field"
msgstr "{prefix} firmó un campo" msgstr "{prefix} firmó un campo"
#: packages/lib/utils/document-audit-logs.ts:299 #: packages/lib/utils/document-audit-logs.ts:303
msgid "{prefix} unsigned a field" msgid "{prefix} unsigned a field"
msgstr "{prefix} no firmó un campo" msgstr "{prefix} no firmó un campo"
@@ -179,27 +183,27 @@ msgstr "{prefix} actualizó un campo"
msgid "{prefix} updated a recipient" msgid "{prefix} updated a recipient"
msgstr "{prefix} actualizó un destinatario" msgstr "{prefix} actualizó un destinatario"
#: packages/lib/utils/document-audit-logs.ts:315 #: packages/lib/utils/document-audit-logs.ts:319
msgid "{prefix} updated the document" msgid "{prefix} updated the document"
msgstr "{prefix} actualizó el documento" msgstr "{prefix} actualizó el documento"
#: packages/lib/utils/document-audit-logs.ts:307 #: packages/lib/utils/document-audit-logs.ts:311
msgid "{prefix} updated the document access auth requirements" msgid "{prefix} updated the document access auth requirements"
msgstr "{prefix} actualizó los requisitos de autorización de acceso al documento" msgstr "{prefix} actualizó los requisitos de autorización de acceso al documento"
#: packages/lib/utils/document-audit-logs.ts:327 #: packages/lib/utils/document-audit-logs.ts:331
msgid "{prefix} updated the document external ID" msgid "{prefix} updated the document external ID"
msgstr "{prefix} actualizó el ID externo del documento" msgstr "{prefix} actualizó el ID externo del documento"
#: packages/lib/utils/document-audit-logs.ts:311 #: packages/lib/utils/document-audit-logs.ts:315
msgid "{prefix} updated the document signing auth requirements" msgid "{prefix} updated the document signing auth requirements"
msgstr "{prefix} actualizó los requisitos de autenticación para la firma del documento" msgstr "{prefix} actualizó los requisitos de autenticación para la firma del documento"
#: packages/lib/utils/document-audit-logs.ts:323 #: packages/lib/utils/document-audit-logs.ts:327
msgid "{prefix} updated the document title" msgid "{prefix} updated the document title"
msgstr "{prefix} actualizó el título del documento" msgstr "{prefix} actualizó el título del documento"
#: packages/lib/utils/document-audit-logs.ts:303 #: packages/lib/utils/document-audit-logs.ts:307
msgid "{prefix} updated the document visibility" msgid "{prefix} updated the document visibility"
msgstr "{prefix} actualizó la visibilidad del documento" msgstr "{prefix} actualizó la visibilidad del documento"
@@ -209,11 +213,11 @@ msgstr "{recipientName} {action} un documento utilizando uno de tus enlaces dire
#: packages/email/templates/document-rejected.tsx:27 #: packages/email/templates/document-rejected.tsx:27
msgid "{recipientName} has rejected the document '{documentName}'" msgid "{recipientName} has rejected the document '{documentName}'"
msgstr "" msgstr "{recipientName} ha rechazado el documento '{documentName}'"
#: packages/email/template-components/template-document-rejected.tsx:25 #: packages/email/template-components/template-document-rejected.tsx:25
msgid "{signerName} has rejected the document \"{documentName}\"." msgid "{signerName} has rejected the document \"{documentName}\"."
msgstr "" msgstr "{signerName} ha rechazado el documento \"{documentName}\"."
#: packages/email/template-components/template-document-invite.tsx:68 #: packages/email/template-components/template-document-invite.tsx:68
msgid "{teamName} has invited you to {0}" msgid "{teamName} has invited you to {0}"
@@ -227,27 +231,27 @@ msgstr "{teamName} te ha invitado a {action} {documentName}"
msgid "{teamName} ownership transfer request" msgid "{teamName} ownership transfer request"
msgstr "solicitud de transferencia de propiedad de {teamName}" msgstr "solicitud de transferencia de propiedad de {teamName}"
#: packages/lib/utils/document-audit-logs.ts:343 #: packages/lib/utils/document-audit-logs.ts:347
msgid "{userName} approved the document" msgid "{userName} approved the document"
msgstr "{userName} aprobó el documento" msgstr "{userName} aprobó el documento"
#: packages/lib/utils/document-audit-logs.ts:344 #: packages/lib/utils/document-audit-logs.ts:348
msgid "{userName} CC'd the document" msgid "{userName} CC'd the document"
msgstr "{userName} envió una copia del documento" msgstr "{userName} envió una copia del documento"
#: packages/lib/utils/document-audit-logs.ts:345 #: packages/lib/utils/document-audit-logs.ts:349
msgid "{userName} completed their task" msgid "{userName} completed their task"
msgstr "{userName} completó su tarea" msgstr "{userName} completó su tarea"
#: packages/lib/utils/document-audit-logs.ts:355 #: packages/lib/utils/document-audit-logs.ts:359
msgid "{userName} rejected the document" msgid "{userName} rejected the document"
msgstr "" msgstr "{userName} rechazó el documento"
#: packages/lib/utils/document-audit-logs.ts:341 #: packages/lib/utils/document-audit-logs.ts:345
msgid "{userName} signed the document" msgid "{userName} signed the document"
msgstr "{userName} firmó el documento" msgstr "{userName} firmó el documento"
#: packages/lib/utils/document-audit-logs.ts:342 #: packages/lib/utils/document-audit-logs.ts:346
msgid "{userName} viewed the document" msgid "{userName} viewed the document"
msgstr "{userName} vio el documento" msgstr "{userName} vio el documento"
@@ -414,11 +418,11 @@ msgstr "Agregar otro valor"
msgid "Add myself" msgid "Add myself"
msgstr "Agregame" msgstr "Agregame"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:637 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:645
msgid "Add Myself" msgid "Add Myself"
msgstr "Agregame" msgstr "Agregame"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:623 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:631
msgid "Add Placeholder Recipient" msgid "Add Placeholder Recipient"
msgstr "Agregar destinatario de marcador de posición" msgstr "Agregar destinatario de marcador de posición"
@@ -443,8 +447,8 @@ msgstr "Admin"
msgid "Advanced Options" msgid "Advanced Options"
msgstr "Opciones avanzadas" msgstr "Opciones avanzadas"
#: packages/ui/primitives/document-flow/add-fields.tsx:573 #: packages/ui/primitives/document-flow/add-fields.tsx:576
#: packages/ui/primitives/template-flow/add-template-fields.tsx:406 #: packages/ui/primitives/template-flow/add-template-fields.tsx:414
msgid "Advanced settings" msgid "Advanced settings"
msgstr "Configuraciones avanzadas" msgstr "Configuraciones avanzadas"
@@ -500,11 +504,11 @@ msgstr "Aprobando"
msgid "Before you get started, please confirm your email address by clicking the button below:" msgid "Before you get started, please confirm your email address by clicking the button below:"
msgstr "Antes de comenzar, por favor confirma tu dirección de correo electrónico haciendo clic en el botón de abajo:" msgstr "Antes de comenzar, por favor confirma tu dirección de correo electrónico haciendo clic en el botón de abajo:"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:377 #: packages/ui/primitives/signature-pad/signature-pad.tsx:383
msgid "Black" msgid "Black"
msgstr "Negro" msgstr "Negro"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:391 #: packages/ui/primitives/signature-pad/signature-pad.tsx:397
msgid "Blue" msgid "Blue"
msgstr "Azul" msgstr "Azul"
@@ -550,6 +554,10 @@ msgstr "Ccers"
msgid "Character Limit" msgid "Character Limit"
msgstr "Límite de caracteres" msgstr "Límite de caracteres"
#: packages/ui/primitives/document-flow/types.ts:58
msgid "Checkbox"
msgstr "Checkbox"
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:197 #: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:197
msgid "Checkbox values" msgid "Checkbox values"
msgstr "Valores de Checkbox" msgstr "Valores de Checkbox"
@@ -558,7 +566,7 @@ msgstr "Valores de Checkbox"
msgid "Clear filters" msgid "Clear filters"
msgstr "Limpiar filtros" msgstr "Limpiar filtros"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:411 #: packages/ui/primitives/signature-pad/signature-pad.tsx:417
msgid "Clear Signature" msgid "Clear Signature"
msgstr "Limpiar firma" msgstr "Limpiar firma"
@@ -585,8 +593,8 @@ msgstr "Documento completado"
msgid "Configure Direct Recipient" msgid "Configure Direct Recipient"
msgstr "Configurar destinatario directo" msgstr "Configurar destinatario directo"
#: packages/ui/primitives/document-flow/add-fields.tsx:574 #: packages/ui/primitives/document-flow/add-fields.tsx:577
#: packages/ui/primitives/template-flow/add-template-fields.tsx:407 #: packages/ui/primitives/template-flow/add-template-fields.tsx:415
msgid "Configure the {0} field" msgid "Configure the {0} field"
msgstr "Configurar el campo {0}" msgstr "Configurar el campo {0}"
@@ -647,9 +655,9 @@ msgstr "Crear cuenta"
msgid "Custom Text" msgid "Custom Text"
msgstr "Texto personalizado" msgstr "Texto personalizado"
#: packages/ui/primitives/document-flow/add-fields.tsx:927 #: packages/ui/primitives/document-flow/add-fields.tsx:934
#: packages/ui/primitives/document-flow/types.ts:53 #: packages/ui/primitives/document-flow/types.ts:53
#: packages/ui/primitives/template-flow/add-template-fields.tsx:690 #: packages/ui/primitives/template-flow/add-template-fields.tsx:729
msgid "Date" msgid "Date"
msgstr "Fecha" msgstr "Fecha"
@@ -666,17 +674,17 @@ msgstr "Rechazar"
msgid "Didn't request a password change? We are here to help you secure your account, just <0>contact us.</0>" msgid "Didn't request a password change? We are here to help you secure your account, just <0>contact us.</0>"
msgstr "¿No solicitaste un cambio de contraseña? Estamos aquí para ayudarte a asegurar tu cuenta, solo <0>contáctanos.</0>" msgstr "¿No solicitaste un cambio de contraseña? Estamos aquí para ayudarte a asegurar tu cuenta, solo <0>contáctanos.</0>"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:570 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:578
msgid "Direct link receiver" msgid "Direct link receiver"
msgstr "Receptor de enlace directo" msgstr "Receptor de enlace directo"
#: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:149 #: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:149
msgid "Document \"{0}\" - Rejected by {1}" msgid "Document \"{0}\" - Rejected by {1}"
msgstr "" msgstr "Documento \"{0}\" - Rechazado por {1}"
#: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:109 #: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:109
msgid "Document \"{0}\" - Rejection Confirmed" msgid "Document \"{0}\" - Rejection Confirmed"
msgstr "" msgstr "Documento \"{0}\" - Rechazo confirmado"
#: packages/ui/components/document/document-global-auth-access-select.tsx:62 #: packages/ui/components/document/document-global-auth-access-select.tsx:62
#: packages/ui/primitives/document-flow/add-settings.tsx:216 #: packages/ui/primitives/document-flow/add-settings.tsx:216
@@ -684,17 +692,17 @@ msgstr ""
msgid "Document access" msgid "Document access"
msgstr "Acceso al documento" msgstr "Acceso al documento"
#: packages/lib/utils/document-audit-logs.ts:306 #: packages/lib/utils/document-audit-logs.ts:310
msgid "Document access auth updated" msgid "Document access auth updated"
msgstr "Se actualizó la autenticación de acceso al documento" msgstr "Se actualizó la autenticación de acceso al documento"
#: packages/lib/server-only/document/delete-document.ts:246 #: packages/lib/server-only/document/delete-document.ts:256
#: packages/lib/server-only/document/super-delete-document.ts:98 #: packages/lib/server-only/document/super-delete-document.ts:98
msgid "Document Cancelled" msgid "Document Cancelled"
msgstr "Documento cancelado" msgstr "Documento cancelado"
#: packages/lib/utils/document-audit-logs.ts:369 #: packages/lib/utils/document-audit-logs.ts:373
#: packages/lib/utils/document-audit-logs.ts:370 #: packages/lib/utils/document-audit-logs.ts:374
msgid "Document completed" msgid "Document completed"
msgstr "Documento completado" msgstr "Documento completado"
@@ -732,15 +740,15 @@ msgstr "¡Documento eliminado!"
msgid "Document Distribution Method" msgid "Document Distribution Method"
msgstr "Método de distribución de documentos" msgstr "Método de distribución de documentos"
#: packages/lib/utils/document-audit-logs.ts:326 #: packages/lib/utils/document-audit-logs.ts:330
msgid "Document external ID updated" msgid "Document external ID updated"
msgstr "ID externo del documento actualizado" msgstr "ID externo del documento actualizado"
#: packages/lib/utils/document-audit-logs.ts:334 #: packages/lib/utils/document-audit-logs.ts:338
msgid "Document moved to team" msgid "Document moved to team"
msgstr "Documento movido al equipo" msgstr "Documento movido al equipo"
#: packages/lib/utils/document-audit-logs.ts:318 #: packages/lib/utils/document-audit-logs.ts:322
msgid "Document opened" msgid "Document opened"
msgstr "Documento abierto" msgstr "Documento abierto"
@@ -750,25 +758,32 @@ msgstr "Correo electrónico de documento pendiente"
#: packages/email/template-components/template-document-rejected.tsx:21 #: packages/email/template-components/template-document-rejected.tsx:21
msgid "Document Rejected" msgid "Document Rejected"
msgstr "Documento Rechazado"
#~ msgid "Document Rejection Confirmed"
#~ msgstr "Document Rejection Confirmed"
#: packages/lib/utils/document-audit-logs.ts:294
msgid "Document restored"
msgstr "" msgstr ""
#: packages/lib/utils/document-audit-logs.ts:330 #: packages/lib/utils/document-audit-logs.ts:334
msgid "Document sent" msgid "Document sent"
msgstr "Documento enviado" msgstr "Documento enviado"
#: packages/lib/utils/document-audit-logs.ts:310 #: packages/lib/utils/document-audit-logs.ts:314
msgid "Document signing auth updated" msgid "Document signing auth updated"
msgstr "Se actualizó la autenticación de firma del documento" msgstr "Se actualizó la autenticación de firma del documento"
#: packages/lib/utils/document-audit-logs.ts:322 #: packages/lib/utils/document-audit-logs.ts:326
msgid "Document title updated" msgid "Document title updated"
msgstr "Título del documento actualizado" msgstr "Título del documento actualizado"
#: packages/lib/utils/document-audit-logs.ts:314 #: packages/lib/utils/document-audit-logs.ts:318
msgid "Document updated" msgid "Document updated"
msgstr "Documento actualizado" msgstr "Documento actualizado"
#: packages/lib/utils/document-audit-logs.ts:302 #: packages/lib/utils/document-audit-logs.ts:306
msgid "Document visibility updated" msgid "Document visibility updated"
msgstr "Visibilidad del documento actualizada" msgstr "Visibilidad del documento actualizada"
@@ -785,8 +800,8 @@ msgstr "Borrador"
msgid "Drag & drop your PDF here." msgid "Drag & drop your PDF here."
msgstr "Arrastre y suelte su PDF aquí." msgstr "Arrastre y suelte su PDF aquí."
#: packages/ui/primitives/document-flow/add-fields.tsx:1058 #: packages/ui/primitives/document-flow/add-fields.tsx:1065
#: packages/ui/primitives/template-flow/add-template-fields.tsx:820 #: packages/ui/primitives/template-flow/add-template-fields.tsx:860
msgid "Dropdown" msgid "Dropdown"
msgstr "Menú desplegable" msgstr "Menú desplegable"
@@ -795,14 +810,14 @@ msgid "Dropdown options"
msgstr "Opciones de menú desplegable" msgstr "Opciones de menú desplegable"
#: packages/lib/constants/document.ts:28 #: packages/lib/constants/document.ts:28
#: packages/ui/primitives/document-flow/add-fields.tsx:875 #: packages/ui/primitives/document-flow/add-fields.tsx:882
#: packages/ui/primitives/document-flow/add-signature.tsx:272 #: packages/ui/primitives/document-flow/add-signature.tsx:272
#: packages/ui/primitives/document-flow/add-signers.tsx:512 #: packages/ui/primitives/document-flow/add-signers.tsx:512
#: packages/ui/primitives/document-flow/add-signers.tsx:519 #: packages/ui/primitives/document-flow/add-signers.tsx:519
#: packages/ui/primitives/document-flow/types.ts:54 #: packages/ui/primitives/document-flow/types.ts:54
#: packages/ui/primitives/template-flow/add-template-fields.tsx:638 #: packages/ui/primitives/template-flow/add-template-fields.tsx:677
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:463 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:471
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:470 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:478
msgid "Email" msgid "Email"
msgstr "Correo electrónico" msgstr "Correo electrónico"
@@ -814,15 +829,15 @@ msgstr "Se requiere email"
msgid "Email Options" msgid "Email Options"
msgstr "Opciones de correo electrónico" msgstr "Opciones de correo electrónico"
#: packages/lib/utils/document-audit-logs.ts:363 #: packages/lib/utils/document-audit-logs.ts:367
msgid "Email resent" msgid "Email resent"
msgstr "Correo electrónico reeenviado" msgstr "Correo electrónico reeenviado"
#: packages/lib/utils/document-audit-logs.ts:363 #: packages/lib/utils/document-audit-logs.ts:367
msgid "Email sent" msgid "Email sent"
msgstr "Correo electrónico enviado" msgstr "Correo electrónico enviado"
#: packages/ui/primitives/document-flow/add-fields.tsx:1123 #: packages/ui/primitives/document-flow/add-fields.tsx:1130
msgid "Empty field" msgid "Empty field"
msgstr "Campo vacío" msgstr "Campo vacío"
@@ -831,11 +846,12 @@ msgid "Enable Direct Link Signing"
msgstr "Habilitar firma de enlace directo" msgstr "Habilitar firma de enlace directo"
#: packages/ui/primitives/document-flow/add-signers.tsx:401 #: packages/ui/primitives/document-flow/add-signers.tsx:401
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:362 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:370
msgid "Enable signing order" msgid "Enable signing order"
msgstr "Habilitar orden de firma" msgstr "Habilitar orden de firma"
#: packages/ui/primitives/document-flow/add-fields.tsx:795 #: packages/ui/primitives/document-flow/add-fields.tsx:802
#: packages/ui/primitives/template-flow/add-template-fields.tsx:597
msgid "Enable Typed Signatures" msgid "Enable Typed Signatures"
msgstr "Habilitar firmas escritas" msgstr "Habilitar firmas escritas"
@@ -882,11 +898,11 @@ msgstr "Etiqueta de campo"
msgid "Field placeholder" msgid "Field placeholder"
msgstr "Marcador de posición de campo" msgstr "Marcador de posición de campo"
#: packages/lib/utils/document-audit-logs.ts:294 #: packages/lib/utils/document-audit-logs.ts:298
msgid "Field signed" msgid "Field signed"
msgstr "Campo firmado" msgstr "Campo firmado"
#: packages/lib/utils/document-audit-logs.ts:298 #: packages/lib/utils/document-audit-logs.ts:302
msgid "Field unsigned" msgid "Field unsigned"
msgstr "Campo no firmado" msgstr "Campo no firmado"
@@ -923,10 +939,13 @@ msgstr "Autenticación de acción de destinatario global"
msgid "Go Back" msgid "Go Back"
msgstr "Regresar" msgstr "Regresar"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:398 #: packages/ui/primitives/signature-pad/signature-pad.tsx:404
msgid "Green" msgid "Green"
msgstr "Verde" msgstr "Verde"
#~ msgid "Hello {recipientName},"
#~ msgstr "Hello {recipientName},"
#: packages/email/templates/reset-password.tsx:56 #: packages/email/templates/reset-password.tsx:56
msgid "Hi, {userName} <0>({userEmail})</0>" msgid "Hi, {userName} <0>({userEmail})</0>"
msgstr "Hola, {userName} <0>({userEmail})</0>" msgstr "Hola, {userName} <0>({userEmail})</0>"
@@ -1010,14 +1029,14 @@ msgstr "Mensaje <0>(Opcional)</0>"
msgid "Min" msgid "Min"
msgstr "Mín" msgstr "Mín"
#: packages/ui/primitives/document-flow/add-fields.tsx:901 #: packages/ui/primitives/document-flow/add-fields.tsx:908
#: packages/ui/primitives/document-flow/add-signature.tsx:298 #: packages/ui/primitives/document-flow/add-signature.tsx:298
#: packages/ui/primitives/document-flow/add-signers.tsx:550 #: packages/ui/primitives/document-flow/add-signers.tsx:550
#: packages/ui/primitives/document-flow/add-signers.tsx:556 #: packages/ui/primitives/document-flow/add-signers.tsx:556
#: packages/ui/primitives/document-flow/types.ts:55 #: packages/ui/primitives/document-flow/types.ts:55
#: packages/ui/primitives/template-flow/add-template-fields.tsx:664 #: packages/ui/primitives/template-flow/add-template-fields.tsx:703
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:498 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:506
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:504 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:512
msgid "Name" msgid "Name"
msgstr "Nombre" msgstr "Nombre"
@@ -1033,8 +1052,8 @@ msgstr "Necesita firmar"
msgid "Needs to view" msgid "Needs to view"
msgstr "Necesita ver" msgstr "Necesita ver"
#: packages/ui/primitives/document-flow/add-fields.tsx:686 #: packages/ui/primitives/document-flow/add-fields.tsx:693
#: packages/ui/primitives/template-flow/add-template-fields.tsx:504 #: packages/ui/primitives/template-flow/add-template-fields.tsx:516
msgid "No recipient matching this description was found." msgid "No recipient matching this description was found."
msgstr "No se encontró ningún destinatario que coincidiera con esta descripción." msgstr "No se encontró ningún destinatario que coincidiera con esta descripción."
@@ -1042,8 +1061,8 @@ msgstr "No se encontró ningún destinatario que coincidiera con esta descripci
msgid "No recipients" msgid "No recipients"
msgstr "Sin destinatarios" msgstr "Sin destinatarios"
#: packages/ui/primitives/document-flow/add-fields.tsx:701 #: packages/ui/primitives/document-flow/add-fields.tsx:708
#: packages/ui/primitives/template-flow/add-template-fields.tsx:519 #: packages/ui/primitives/template-flow/add-template-fields.tsx:531
msgid "No recipients with this role" msgid "No recipients with this role"
msgstr "No hay destinatarios con este rol" msgstr "No hay destinatarios con este rol"
@@ -1071,9 +1090,9 @@ msgstr "No se encontró valor."
msgid "None" msgid "None"
msgstr "Ninguno" msgstr "Ninguno"
#: packages/ui/primitives/document-flow/add-fields.tsx:979 #: packages/ui/primitives/document-flow/add-fields.tsx:986
#: packages/ui/primitives/document-flow/types.ts:56 #: packages/ui/primitives/document-flow/types.ts:56
#: packages/ui/primitives/template-flow/add-template-fields.tsx:742 #: packages/ui/primitives/template-flow/add-template-fields.tsx:781
msgid "Number" msgid "Number"
msgstr "Número" msgstr "Número"
@@ -1164,7 +1183,7 @@ msgstr "Por favor confirma tu dirección de correo electrónico"
msgid "Please try again or contact our support." msgid "Please try again or contact our support."
msgstr "Por favor, inténtalo de nuevo o contacta a nuestro soporte." msgstr "Por favor, inténtalo de nuevo o contacta a nuestro soporte."
#: packages/ui/primitives/template-flow/add-template-fields.tsx:768 #: packages/ui/primitives/document-flow/types.ts:57
msgid "Radio" msgid "Radio"
msgstr "Radio" msgstr "Radio"
@@ -1182,14 +1201,14 @@ msgstr "Solo lectura"
#: packages/email/template-components/template-document-rejected.tsx:32 #: packages/email/template-components/template-document-rejected.tsx:32
msgid "Reason for rejection: {rejectionReason}" msgid "Reason for rejection: {rejectionReason}"
msgstr "" msgstr "Razón del rechazo: {rejectionReason}"
#: packages/ui/components/recipient/recipient-role-select.tsx:95 #: packages/ui/components/recipient/recipient-role-select.tsx:95
msgid "Receives copy" msgid "Receives copy"
msgstr "Recibe copia" msgstr "Recibe copia"
#: packages/lib/utils/document-audit-logs.ts:338 #: packages/lib/utils/document-audit-logs.ts:342
#: packages/lib/utils/document-audit-logs.ts:353 #: packages/lib/utils/document-audit-logs.ts:357
msgid "Recipient" msgid "Recipient"
msgstr "Destinatario" msgstr "Destinatario"
@@ -1207,7 +1226,7 @@ msgstr "Correo electrónico de destinatario eliminado"
msgid "Recipient signing request email" msgid "Recipient signing request email"
msgstr "Correo electrónico de solicitud de firma de destinatario" msgstr "Correo electrónico de solicitud de firma de destinatario"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:384 #: packages/ui/primitives/signature-pad/signature-pad.tsx:390
msgid "Red" msgid "Red"
msgstr "Rojo" msgstr "Rojo"
@@ -1218,15 +1237,15 @@ msgstr "URL de redirección"
#: packages/email/template-components/template-document-invite.tsx:96 #: packages/email/template-components/template-document-invite.tsx:96
msgid "Reject Document" msgid "Reject Document"
msgstr "" msgstr "Rechazar Documento"
#: packages/email/template-components/template-document-rejection-confirmed.tsx:22 #: packages/email/template-components/template-document-rejection-confirmed.tsx:22
msgid "Rejection Confirmed" msgid "Rejection Confirmed"
msgstr "" msgstr "Rechazo Confirmado"
#: packages/email/template-components/template-document-rejection-confirmed.tsx:34 #: packages/email/template-components/template-document-rejection-confirmed.tsx:34
msgid "Rejection reason: {reason}" msgid "Rejection reason: {reason}"
msgstr "" msgstr "Razón del rechazo: {reason}"
#: packages/lib/server-only/document/resend-document.tsx:192 #: packages/lib/server-only/document/resend-document.tsx:192
msgid "Reminder: {0}" msgid "Reminder: {0}"
@@ -1244,7 +1263,7 @@ msgstr "Recordatorio: Por favor {recipientActionVerb} este documento"
msgid "Reminder: Please {recipientActionVerb} your document" msgid "Reminder: Please {recipientActionVerb} your document"
msgstr "Recordatorio: Por favor {recipientActionVerb} tu documento" msgstr "Recordatorio: Por favor {recipientActionVerb} tu documento"
#: packages/ui/primitives/document-flow/add-fields.tsx:1110 #: packages/ui/primitives/document-flow/add-fields.tsx:1117
msgid "Remove" msgid "Remove"
msgstr "Eliminar" msgstr "Eliminar"
@@ -1276,7 +1295,7 @@ msgstr "Filas por página"
msgid "Save" msgid "Save"
msgstr "Guardar" msgstr "Guardar"
#: packages/ui/primitives/template-flow/add-template-fields.tsx:854 #: packages/ui/primitives/template-flow/add-template-fields.tsx:893
msgid "Save Template" msgid "Save Template"
msgstr "Guardar plantilla" msgstr "Guardar plantilla"
@@ -1285,6 +1304,7 @@ msgid "Search languages..."
msgstr "Buscar idiomas..." msgstr "Buscar idiomas..."
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:115 #: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:115
#: packages/ui/primitives/document-flow/types.ts:59
msgid "Select" msgid "Select"
msgstr "Seleccionar" msgstr "Seleccionar"
@@ -1348,7 +1368,7 @@ msgid "Share your signing experience!"
msgstr "¡Comparte tu experiencia de firma!" msgstr "¡Comparte tu experiencia de firma!"
#: packages/ui/primitives/document-flow/add-signers.tsx:709 #: packages/ui/primitives/document-flow/add-signers.tsx:709
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:655 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:663
msgid "Show advanced settings" msgid "Show advanced settings"
msgstr "Mostrar configuraciones avanzadas" msgstr "Mostrar configuraciones avanzadas"
@@ -1364,11 +1384,11 @@ msgstr "Firmar Documento"
msgid "Sign In" msgid "Sign In"
msgstr "Iniciar sesión" msgstr "Iniciar sesión"
#: packages/ui/primitives/document-flow/add-fields.tsx:823 #: packages/ui/primitives/document-flow/add-fields.tsx:830
#: packages/ui/primitives/document-flow/add-signature.tsx:323 #: packages/ui/primitives/document-flow/add-signature.tsx:323
#: packages/ui/primitives/document-flow/field-icon.tsx:52 #: packages/ui/primitives/document-flow/field-icon.tsx:52
#: packages/ui/primitives/document-flow/types.ts:49 #: packages/ui/primitives/document-flow/types.ts:49
#: packages/ui/primitives/template-flow/add-template-fields.tsx:586 #: packages/ui/primitives/template-flow/add-template-fields.tsx:625
msgid "Signature" msgid "Signature"
msgstr "Firma" msgstr "Firma"
@@ -1451,9 +1471,9 @@ msgstr "Correo electrónico del equipo eliminado para {teamName} en Documenso"
msgid "Template title" msgid "Template title"
msgstr "Título de plantilla" msgstr "Título de plantilla"
#: packages/ui/primitives/document-flow/add-fields.tsx:953 #: packages/ui/primitives/document-flow/add-fields.tsx:960
#: packages/ui/primitives/document-flow/types.ts:52 #: packages/ui/primitives/document-flow/types.ts:52
#: packages/ui/primitives/template-flow/add-template-fields.tsx:716 #: packages/ui/primitives/template-flow/add-template-fields.tsx:755
msgid "Text" msgid "Text"
msgstr "Texto" msgstr "Texto"
@@ -1473,9 +1493,12 @@ msgstr "La autenticación requerida para que los destinatarios firmen el campo d
msgid "The authentication required for recipients to view the document." msgid "The authentication required for recipients to view the document."
msgstr "La autenticación requerida para que los destinatarios vean el documento." msgstr "La autenticación requerida para que los destinatarios vean el documento."
#~ msgid "The document owner has been notified of this rejection. No further action is required from you at this time."
#~ msgstr "The document owner has been notified of this rejection. No further action is required from you at this time."
#: packages/email/template-components/template-document-rejection-confirmed.tsx:39 #: packages/email/template-components/template-document-rejection-confirmed.tsx:39
msgid "The document owner has been notified of this rejection. No further action is required from you at this time. The document owner may contact you with any questions regarding this rejection." msgid "The document owner has been notified of this rejection. No further action is required from you at this time. The document owner may contact you with any questions regarding this rejection."
msgstr "" msgstr "El propietario del documento ha sido notificado de este rechazo. No se requiere ninguna acción adicional de su parte en este momento. El propietario del documento puede contactarlo con cualquier pregunta relacionada con este rechazo."
#: packages/ui/components/document/document-send-email-message-helper.tsx:31 #: packages/ui/components/document/document-send-email-message-helper.tsx:31
msgid "The document's name" msgid "The document's name"
@@ -1545,7 +1568,7 @@ msgstr "Esto se puede anular configurando los requisitos de autenticación direc
msgid "This document can not be recovered, if you would like to dispute the reason for future documents please contact support." msgid "This document can not be recovered, if you would like to dispute the reason for future documents please contact support."
msgstr "Este documento no se puede recuperar, si deseas impugnar la razón para documentos futuros, por favor contacta con el soporte." msgstr "Este documento no se puede recuperar, si deseas impugnar la razón para documentos futuros, por favor contacta con el soporte."
#: packages/ui/primitives/document-flow/add-fields.tsx:757 #: packages/ui/primitives/document-flow/add-fields.tsx:764
msgid "This document has already been sent to this recipient. You can no longer edit this recipient." msgid "This document has already been sent to this recipient. You can no longer edit this recipient."
msgstr "Este documento ya ha sido enviado a este destinatario. Ya no puede editar a este destinatario." msgstr "Este documento ya ha sido enviado a este destinatario. Ya no puede editar a este destinatario."
@@ -1557,9 +1580,12 @@ msgstr "Este documento está protegido por contraseña. Por favor ingrese la con
msgid "This document was sent using <0>Documenso.</0>" msgid "This document was sent using <0>Documenso.</0>"
msgstr "Este documento fue enviado usando <0>Documenso.</0>" msgstr "Este documento fue enviado usando <0>Documenso.</0>"
#~ msgid "This email confirms that you have rejected the document \"{documentName}\" sent by {documentOwnerName}."
#~ msgstr "This email confirms that you have rejected the document \"{documentName}\" sent by {documentOwnerName}."
#: packages/email/template-components/template-document-rejection-confirmed.tsx:26 #: packages/email/template-components/template-document-rejection-confirmed.tsx:26
msgid "This email confirms that you have rejected the document <0>\"{documentName}\"</0> sent by {documentOwnerName}." msgid "This email confirms that you have rejected the document <0>\"{documentName}\"</0> sent by {documentOwnerName}."
msgstr "" msgstr "Este correo electrónico confirma que ha rechazado el documento <0>\"{documentName}\"</0> enviado por {documentOwnerName}."
#: packages/ui/components/document/document-email-checkboxes.tsx:94 #: packages/ui/components/document/document-email-checkboxes.tsx:94
msgid "This email is sent to the recipient if they are removed from a pending document." msgid "This email is sent to the recipient if they are removed from a pending document."
@@ -1573,7 +1599,7 @@ msgstr "Este correo electrónico se envía al destinatario solicitando que firme
msgid "This email will be sent to the recipient who has just signed the document, if there are still other recipients who have not signed yet." msgid "This email will be sent to the recipient who has just signed the document, if there are still other recipients who have not signed yet."
msgstr "Este correo electrónico se enviará al destinatario que acaba de firmar el documento, si todavía hay otros destinatarios que no han firmado." msgstr "Este correo electrónico se enviará al destinatario que acaba de firmar el documento, si todavía hay otros destinatarios que no han firmado."
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:573 #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:581
msgid "This field cannot be modified or deleted. When you share this template's direct link or add it to your public profile, anyone who accesses it can input their name and email, and fill in the fields assigned to them." msgid "This field cannot be modified or deleted. When you share this template's direct link or add it to your public profile, anyone who accesses it can input their name and email, and fill in the fields assigned to them."
msgstr "Este campo no se puede modificar ni eliminar. Cuando comparta el enlace directo de esta plantilla o lo agregue a su perfil público, cualquiera que acceda podrá ingresar su nombre y correo electrónico, y completar los campos que se le hayan asignado." msgstr "Este campo no se puede modificar ni eliminar. Cuando comparta el enlace directo de esta plantilla o lo agregue a su perfil público, cualquiera que acceda podrá ingresar su nombre y correo electrónico, y completar los campos que se le hayan asignado."
@@ -1581,7 +1607,7 @@ msgstr "Este campo no se puede modificar ni eliminar. Cuando comparta el enlace
msgid "This is how the document will reach the recipients once the document is ready for signing." msgid "This is how the document will reach the recipients once the document is ready for signing."
msgstr "Así es como el documento llegará a los destinatarios una vez que esté listo para firmarse." msgstr "Así es como el documento llegará a los destinatarios una vez que esté listo para firmarse."
#: packages/ui/primitives/document-flow/add-fields.tsx:1090 #: packages/ui/primitives/document-flow/add-fields.tsx:1097
msgid "This recipient can no longer be modified as they have signed a field, or completed the document." msgid "This recipient can no longer be modified as they have signed a field, or completed the document."
msgstr "Este destinatario ya no puede ser modificado ya que ha firmado un campo o completado el documento." msgstr "Este destinatario ya no puede ser modificado ya que ha firmado un campo o completado el documento."
@@ -1610,8 +1636,8 @@ msgstr "Zona horaria"
msgid "Title" msgid "Title"
msgstr "Título" msgstr "Título"
#: packages/ui/primitives/document-flow/add-fields.tsx:1073 #: packages/ui/primitives/document-flow/add-fields.tsx:1080
#: packages/ui/primitives/template-flow/add-template-fields.tsx:834 #: packages/ui/primitives/template-flow/add-template-fields.tsx:873
msgid "To proceed further, please set at least one value for the {0} field." msgid "To proceed further, please set at least one value for the {0} field."
msgstr "Para continuar, por favor establezca al menos un valor para el campo {0}." msgstr "Para continuar, por favor establezca al menos un valor para el campo {0}."
@@ -1738,7 +1764,7 @@ msgstr "Puede usar las siguientes variables en su mensaje:"
#: packages/email/template-components/template-document-rejected.tsx:37 #: packages/email/template-components/template-document-rejected.tsx:37
msgid "You can view the document and its status by clicking the button below." msgid "You can view the document and its status by clicking the button below."
msgstr "" msgstr "Puede ver el documento y su estado haciendo clic en el botón de abajo."
#: packages/ui/primitives/document-dropzone.tsx:43 #: packages/ui/primitives/document-dropzone.tsx:43
msgid "You cannot upload documents at this time." msgid "You cannot upload documents at this time."
@@ -1775,7 +1801,10 @@ msgstr "Ha alcanzado su límite de documentos."
#: packages/email/templates/document-rejection-confirmed.tsx:27 #: packages/email/templates/document-rejection-confirmed.tsx:27
msgid "You have rejected the document '{documentName}'" msgid "You have rejected the document '{documentName}'"
msgstr "" msgstr "Ha rechazado el documento '{documentName}'"
#~ msgid "You have rejected the document \"{documentName}\""
#~ msgstr "You have rejected the document \"{documentName}\""
#: packages/email/template-components/template-document-self-signed.tsx:42 #: packages/email/template-components/template-document-self-signed.tsx:42
msgid "You have signed “{documentName}”" msgid "You have signed “{documentName}”"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: es\n" "Language: es\n"
"Project-Id-Version: documenso-app\n" "Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-11-12 05:45\n" "PO-Revision-Date: 2024-11-20 11:56\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Spanish\n" "Language-Team: Spanish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More