Compare commits

..

2 Commits

Author SHA1 Message Date
David Nguyen 3e1ef92596 fix: missing trans tags 2024-11-12 15:59:04 +07:00
David Nguyen c305944014 feat: add polish translations 2024-11-12 15:50:47 +07:00
143 changed files with 2437 additions and 5220 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ name: Publish Docker
on:
push:
branches: ['release']
branches: ["release"]
jobs:
build_and_publish_platform_containers:
+3
View File
@@ -27,6 +27,9 @@
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
"postcss": "^8",
"tailwindcss": "^3.3.0",
"typescript": "^5"
}
}
@@ -11,10 +11,6 @@ Digitally signing documents requires a signing certificate in `.p12` format. You
Follow the steps below to create a free, self-signed certificate for local development.
<Callout type="warning">
These steps should be run on a UNIX based system, otherwise you may run into an error.
</Callout>
<Steps>
### Generate Private Key
-53
View File
@@ -8,59 +8,6 @@ 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
We're excited to introduce Documenso v1.7.1, bringing you improved control over your document signing process. Here are the key updates:
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@documenso/marketing",
"version": "1.8.1-rc.1",
"version": "1.8.0-rc.0",
"private": true,
"license": "AGPL-3.0",
"scripts": {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

@@ -163,7 +163,6 @@ export const SinglePlayerClient = () => {
expired: null,
signedAt: null,
readStatus: 'OPENED',
rejectionReason: null,
documentDeletedAt: null,
signingStatus: 'NOT_SIGNED',
sendStatus: 'NOT_SENT',
+2 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@documenso/web",
"version": "1.8.1-rc.1",
"version": "1.8.0-rc.0",
"private": true,
"license": "AGPL-3.0",
"scripts": {
@@ -28,7 +28,6 @@
"@simplewebauthn/browser": "^9.0.1",
"@simplewebauthn/server": "^9.0.3",
"@tanstack/react-query": "^4.29.5",
"colord": "^2.9.3",
"cookie-es": "^1.0.0",
"formidable": "^2.1.1",
"framer-motion": "^10.12.8",
@@ -54,7 +53,7 @@
"react-icons": "^4.11.0",
"react-rnd": "^10.4.1",
"recharts": "^2.7.2",
"remeda": "^2.17.3",
"remeda": "^2.12.1",
"sharp": "0.32.6",
"ts-pattern": "^5.0.5",
"ua-parser-js": "^1.0.37",
@@ -4,7 +4,7 @@ import { useMemo } from 'react';
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { AlertTriangle, CheckCheckIcon, CheckIcon, Loader, MailOpen } from 'lucide-react';
import { CheckCheckIcon, CheckIcon, Loader, MailOpen } from 'lucide-react';
import { DateTime } from 'luxon';
import { match } from 'ts-pattern';
@@ -133,11 +133,6 @@ export const DocumentPageViewRecentActivity = ({
<CheckIcon className="h-3 w-3" aria-hidden="true" />
</div>
))
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED, () => (
<div className="bg-widget rounded-full border border-gray-300 p-1 dark:border-neutral-600">
<AlertTriangle className="h-3 w-3" aria-hidden="true" />
</div>
))
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED, () => (
<div className="bg-widget rounded-full border border-gray-300 p-1 dark:border-neutral-600">
<MailOpen className="h-3 w-3" aria-hidden="true" />
@@ -4,15 +4,7 @@ import Link from 'next/link';
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import {
AlertTriangle,
CheckIcon,
Clock,
MailIcon,
MailOpenIcon,
PenIcon,
PlusIcon,
} from 'lucide-react';
import { CheckIcon, Clock, MailIcon, MailOpenIcon, PenIcon, PlusIcon } from 'lucide-react';
import { match } from 'ts-pattern';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
@@ -23,7 +15,6 @@ import { CopyTextButton } from '@documenso/ui/components/common/copy-text-button
import { SignatureIcon } from '@documenso/ui/icons/signature';
import { AvatarWithText } from '@documenso/ui/primitives/avatar';
import { Badge } from '@documenso/ui/primitives/badge';
import { PopoverHover } from '@documenso/ui/primitives/popover';
import { useToast } from '@documenso/ui/primitives/use-toast';
export type DocumentPageViewRecipientsProps = {
@@ -132,26 +123,6 @@ export const DocumentPageViewRecipients = ({
</Badge>
)}
{document.status !== DocumentStatus.DRAFT &&
recipient.signingStatus === SigningStatus.REJECTED && (
<PopoverHover
trigger={
<Badge variant="destructive">
<AlertTriangle className="mr-1 h-3 w-3" />
<Trans>Rejected</Trans>
</Badge>
}
>
<p className="text-sm">
<Trans>Reason for rejection: </Trans>
</p>
<p className="text-muted-foreground mt-1 text-sm">
{recipient.rejectionReason}
</p>
</PopoverHover>
)}
{document.status === DocumentStatus.PENDING &&
recipient.signingStatus === SigningStatus.NOT_SIGNED &&
recipient.role !== RecipientRole.CC && (
@@ -78,7 +78,7 @@ export const DocumentsDataTable = ({
{
header: _(msg`Status`),
accessorKey: 'status',
cell: ({ row }) => <DocumentStatus status={row.original.status} />,
cell: ({ row }) => <DocumentStatus status={row.getValue('status')} />,
size: 140,
},
{
@@ -47,8 +47,6 @@ export const DeleteDocumentDialog = ({
const { refreshLimits } = useLimits();
const { _ } = useLingui();
const deleteMessage = msg`delete`;
const [inputValue, setInputValue] = useState('');
const [isDeleteEnabled, setIsDeleteEnabled] = useState(status === DocumentStatus.DRAFT);
@@ -89,7 +87,7 @@ export const DeleteDocumentDialog = ({
const onInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(event.target.value);
setIsDeleteEnabled(event.target.value === _(deleteMessage));
setIsDeleteEnabled(event.target.value === _(msg`delete`));
};
return (
@@ -183,7 +181,7 @@ export const DeleteDocumentDialog = ({
type="text"
value={inputValue}
onChange={onInputChange}
placeholder={_(msg`Please type ${`'${_(deleteMessage)}'`} to confirm`)}
placeholder={_(msg`Type 'delete' to confirm`)}
/>
)}
@@ -12,10 +12,9 @@ import { createBillingPortal } from './create-billing-portal.action';
export type BillingPortalButtonProps = {
buttonProps?: React.ComponentProps<typeof Button>;
children?: React.ReactNode;
};
export const BillingPortalButton = ({ buttonProps, children }: BillingPortalButtonProps) => {
export const BillingPortalButton = ({ buttonProps }: BillingPortalButtonProps) => {
const { _ } = useLingui();
const { toast } = useToast();
@@ -64,7 +63,7 @@ export const BillingPortalButton = ({ buttonProps, children }: BillingPortalButt
onClick={async () => handleFetchPortalUrl()}
loading={isFetchingPortalUrl}
>
{children || <Trans>Manage Subscription</Trans>}
<Trans>Manage Subscription</Trans>
</Button>
);
};
@@ -68,74 +68,60 @@ export default async function BillingSettingsPage() {
return (
<div>
<div className="flex flex-row items-end justify-between">
<div>
<h3 className="text-2xl font-semibold">
<Trans>Billing</Trans>
</h3>
<h3 className="text-2xl font-semibold">
<Trans>Billing</Trans>
</h3>
<div className="text-muted-foreground mt-2 text-sm">
{isMissingOrInactiveOrFreePlan && (
<div className="text-muted-foreground mt-2 text-sm">
{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>
<Trans>
You are currently on the <span className="font-semibold">Free Plan</span>.
</Trans>
</p>
)}
{subscriptionProduct ? (
<span>
You are currently subscribed to{' '}
<span className="font-semibold">{subscriptionProduct.name}</span>
</span>
) : (
<span>You currently have an active plan</span>
)}
{/* Todo: Translation */}
{!isMissingOrInactiveOrFreePlan &&
match(subscription.status)
.with('ACTIVE', () => (
<p>
{subscriptionProduct ? (
{subscription.periodEnd && (
<span>
{' '}
which is set to{' '}
{subscription.cancelAtPeriodEnd ? (
<span>
You are currently subscribed to{' '}
<span className="font-semibold">{subscriptionProduct.name}</span>
end on{' '}
<span className="font-semibold">{i18n.date(subscription.periodEnd)}.</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>
)}
automatically renew on{' '}
<span className="font-semibold">{i18n.date(subscription.periodEnd)}.</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>
)}
</span>
)}
</p>
))
.with('PAST_DUE', () => (
<p>
<Trans>
Your current plan is past due. Please update your payment information.
</Trans>
</p>
))
.otherwise(() => null)}
</div>
<hr className="my-4" />
@@ -141,23 +141,6 @@ 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) => {
try {
await updateTemplateSettings({
@@ -228,12 +211,6 @@ export const EditTemplateForm = ({
fields: data.fields,
});
await updateTypedSignature({
templateId: template.id,
teamId: team?.id,
typedSignatureEnabled: data.typedSignatureEnabled,
});
// Clear all field data from localStorage
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
@@ -248,13 +225,14 @@ export const EditTemplateForm = ({
duration: 5000,
});
// Router refresh is here to clear the router cache for when navigating to /documents.
router.refresh();
router.push(templateRootPath);
} catch (err) {
console.error(err);
toast({
title: _(msg`Error`),
description: _(msg`An error occurred while adding fields.`),
description: _(msg`An error occurred while adding signers.`),
variant: 'destructive',
});
}
@@ -323,7 +301,6 @@ export const EditTemplateForm = ({
fields={fields}
onSubmit={onAddFieldsFormSubmit}
teamId={team?.id}
typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled}
/>
</Stepper>
</DocumentFlowFormContainer>
@@ -73,6 +73,7 @@ export const TemplatePageView = async ({ params, team }: TemplatePageViewProps)
const mockedDocumentMeta = templateMeta
? {
typedSignatureEnabled: false,
...templateMeta,
signingOrder: templateMeta.signingOrder || DocumentSigningOrder.SEQUENTIAL,
documentId: 0,
@@ -154,7 +155,7 @@ export const TemplatePageView = async ({ params, team }: TemplatePageViewProps)
</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>
</p>
@@ -209,19 +209,11 @@ 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)`,
}}
>
{signature.Signature?.signatureImageAsBase64 && (
<img
src={`${signature.Signature?.signatureImageAsBase64}`}
alt="Signature"
className="max-h-12 max-w-full"
/>
)}
{signature.Signature?.typedSignature && (
<p className="font-signature text-center text-sm">
{signature.Signature?.typedSignature}
</p>
)}
<img
src={`${signature.Signature?.signatureImageAsBase64}`}
alt="Signature"
className="max-h-12 max-w-full"
/>
</div>
<p className="text-muted-foreground mt-2 text-sm print:text-xs">
@@ -102,9 +102,9 @@ export const SignDirectTemplateForm = ({
created: new Date(),
recipientId: 1,
fieldId: 1,
signatureImageAsBase64: value.value.startsWith('data:') ? value.value : null,
typedSignature: value.value.startsWith('data:') ? null : value.value,
} satisfies Signature;
signatureImageAsBase64: value.value,
typedSignature: null,
};
}
if (field.type === FieldType.DATE) {
@@ -1,237 +0,0 @@
'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>
);
};
@@ -144,13 +144,13 @@ export const DateField = ({
)}
{!field.inserted && (
<p className="group-hover:text-primary text-muted-foreground text-[clamp(0.425rem,25cqw,0.825rem)] duration-200 group-hover:text-yellow-300">
<p className="group-hover:text-primary text-muted-foreground duration-200 group-hover:text-yellow-300">
<Trans>Date</Trans>
</p>
)}
{field.inserted && (
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.425rem,25cqw,0.825rem)] duration-200">
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.625rem,1cqw,0.825rem)] duration-200">
{localDateString}
</p>
)}
@@ -178,7 +178,7 @@ export const DropdownField = ({
)}
{!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}>
<SelectTrigger
className={cn(
@@ -190,7 +190,7 @@ export const DropdownField = ({
)}
>
<SelectValue
className="text-[clamp(0.425rem,25cqw,0.825rem)]"
className="text-[clamp(0.625rem,1cqw,0.825rem)]"
placeholder={`${_(msg`Select`)}`}
/>
</SelectTrigger>
@@ -206,7 +206,7 @@ export const DropdownField = ({
)}
{field.inserted && (
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.425rem,25cqw,0.825rem)] duration-200">
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.625rem,1cqw,0.825rem)] duration-200">
{field.customText}
</p>
)}
@@ -122,13 +122,13 @@ export const EmailField = ({ field, recipient, onSignField, onUnsignField }: Ema
)}
{!field.inserted && (
<p className="group-hover:text-primary text-muted-foreground text-[clamp(0.425rem,25cqw,0.825rem)] duration-200 group-hover:text-yellow-300">
<p className="group-hover:text-primary text-muted-foreground duration-200 group-hover:text-yellow-300">
<Trans>Email</Trans>
</p>
)}
{field.inserted && (
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.425rem,25cqw,0.825rem)] duration-200">
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.625rem,1cqw,0.825rem)] duration-200">
{field.customText}
</p>
)}
@@ -128,13 +128,13 @@ export const InitialsField = ({
)}
{!field.inserted && (
<p className="group-hover:text-primary text-muted-foreground text-[clamp(0.425rem,25cqw,0.825rem)] duration-200 group-hover:text-yellow-300">
<p className="group-hover:text-primary text-muted-foreground duration-200 group-hover:text-yellow-300">
<Trans>Initials</Trans>
</p>
)}
{field.inserted && (
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.425rem,25cqw,0.825rem)] duration-200">
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.625rem,1cqw,0.825rem)] duration-200">
{field.customText}
</p>
)}
@@ -172,7 +172,7 @@ export const NameField = ({ field, recipient, onSignField, onUnsignField }: Name
)}
{field.inserted && (
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.425rem,25cqw,0.825rem)] duration-200">
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.625rem,1cqw,0.825rem)] duration-200">
{field.customText}
</p>
)}
@@ -252,15 +252,14 @@ export const NumberField = ({ field, recipient, onSignField, onUnsignField }: Nu
},
)}
>
<span className="flex items-center justify-center gap-x-1">
<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 className="flex items-center justify-center gap-x-1 text-sm">
<Hash className="h-4 w-4" /> {fieldDisplayName}
</span>
</p>
)}
{field.inserted && (
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.425rem,25cqw,0.825rem)] duration-200">
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.625rem,1cqw,0.825rem)] duration-200">
{field.customText}
</p>
)}
@@ -99,10 +99,6 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
const { documentMeta } = document;
if (recipient.signingStatus === SigningStatus.REJECTED) {
return redirect(`/sign/${token}/rejected`);
}
if (
document.status === DocumentStatus.COMPLETED ||
recipient.signingStatus === SigningStatus.SIGNED
@@ -1,6 +1,6 @@
'use client';
import { createContext, useContext, useEffect, useState } from 'react';
import { createContext, useContext, useState } from 'react';
export type SigningContextValue = {
fullName: string;
@@ -44,12 +44,6 @@ export const SigningProvider = ({
const [email, setEmail] = useState(initialEmail || '');
const [signature, setSignature] = useState(initialSignature || null);
useEffect(() => {
if (initialSignature) {
setSignature(initialSignature);
}
}, [initialSignature]);
return (
<SigningContext.Provider
value={{
@@ -1,170 +0,0 @@
'use client';
import { useEffect, useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, msg } from '@lingui/macro';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import type { Document } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Textarea } from '@documenso/ui/primitives/textarea';
import { useToast } from '@documenso/ui/primitives/use-toast';
const ZRejectDocumentFormSchema = z.object({
reason: z
.string()
.min(5, msg`Please provide a reason`)
.max(500, msg`Reason must be less than 500 characters`),
});
type TRejectDocumentFormSchema = z.infer<typeof ZRejectDocumentFormSchema>;
export interface RejectDocumentDialogProps {
document: Pick<Document, 'id'>;
token: string;
}
export function RejectDocumentDialog({ document, token }: RejectDocumentDialogProps) {
const { toast } = useToast();
const router = useRouter();
const searchParams = useSearchParams();
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync: rejectDocumentWithToken } =
trpc.recipient.rejectDocumentWithToken.useMutation();
const form = useForm<TRejectDocumentFormSchema>({
resolver: zodResolver(ZRejectDocumentFormSchema),
defaultValues: {
reason: '',
},
});
const onRejectDocument = async ({ reason }: TRejectDocumentFormSchema) => {
try {
// TODO: Add trpc mutation here
await rejectDocumentWithToken({
documentId: document.id,
token,
reason,
});
toast({
title: 'Document rejected',
description: 'The document has been successfully rejected.',
duration: 5000,
});
setIsOpen(false);
router.push(`/sign/${token}/rejected`);
} catch (err) {
toast({
title: 'Error',
description: 'An error occurred while rejecting the document. Please try again.',
variant: 'destructive',
duration: 5000,
});
}
};
useEffect(() => {
if (searchParams?.get('reject') === 'true') {
setIsOpen(true);
}
}, []);
useEffect(() => {
if (!isOpen) {
form.reset();
}
}, [isOpen]);
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="outline">
<Trans>Reject Document</Trans>
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>
<Trans>Reject Document</Trans>
</DialogTitle>
<DialogDescription>
<Trans>
Are you sure you want to reject this document? This action cannot be undone.
</Trans>
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onRejectDocument)} className="space-y-4">
<FormField
control={form.control}
name="reason"
render={({ field }) => (
<FormItem>
<FormControl>
<Textarea
{...field}
rows={4}
placeholder="Please provide a reason for rejecting this document"
disabled={form.formState.isSubmitting}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
<Button
type="button"
variant="ghost"
onClick={() => setIsOpen(false)}
disabled={form.formState.isSubmitting}
>
<Trans>Cancel</Trans>
</Button>
<Button
type="submit"
variant="destructive"
loading={form.formState.isSubmitting}
disabled={!form.formState.isValid}
>
<Trans>Reject Document</Trans>
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
}
@@ -1,110 +0,0 @@
import Link from 'next/link';
import { notFound } from 'next/navigation';
import { Trans } from '@lingui/macro';
import { XCircle } from 'lucide-react';
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
import { isRecipientAuthorized } from '@documenso/lib/server-only/document/is-recipient-authorized';
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 { FieldType } from '@documenso/prisma/client';
import { Badge } from '@documenso/ui/primitives/badge';
import { Button } from '@documenso/ui/primitives/button';
import { truncateTitle } from '~/helpers/truncate-title';
import { SigningAuthPageView } from '../signing-auth-page';
export type RejectedSigningPageProps = {
params: {
token?: string;
};
};
export default async function RejectedSigningPage({ params: { token } }: RejectedSigningPageProps) {
await setupI18nSSR();
if (!token) {
return notFound();
}
const { user } = await getServerComponentSession();
const document = await getDocumentAndSenderByToken({
token,
requireAccessAuth: false,
}).catch(() => null);
if (!document) {
return notFound();
}
const truncatedTitle = truncateTitle(document.title);
const [fields, recipient] = await Promise.all([
getFieldsForToken({ token }),
getRecipientByToken({ token }).catch(() => null),
]);
if (!recipient) {
return notFound();
}
const isDocumentAccessValid = await isRecipientAuthorized({
type: 'ACCESS',
documentAuthOptions: document.authOptions,
recipient,
userId: user?.id,
});
if (!isDocumentAccessValid) {
return <SigningAuthPageView email={recipient.email} />;
}
const recipientName =
recipient.name ||
fields.find((field) => field.type === FieldType.NAME)?.customText ||
recipient.email;
return (
<div className="flex flex-col items-center pt-24 lg:pt-36 xl:pt-44">
<Badge variant="neutral" size="default" className="mb-6 rounded-xl border bg-transparent">
{truncatedTitle}
</Badge>
<div className="flex flex-col items-center">
<div className="flex items-center gap-x-4">
<XCircle className="text-destructive h-10 w-10" />
<h2 className="max-w-[35ch] text-center text-2xl font-semibold leading-normal md:text-3xl lg:text-4xl">
<Trans>Document Rejected</Trans>
</h2>
</div>
<div className="text-destructive mt-4 flex items-center text-center text-sm">
<Trans>You have rejected this document</Trans>
</div>
<p className="text-muted-foreground mt-6 max-w-[60ch] text-center text-sm">
<Trans>
The document owner has been notified of your decision. They may contact you with further
instructions if necessary.
</Trans>
</p>
<p className="text-muted-foreground mt-2 max-w-[60ch] text-center text-sm">
<Trans>No further action is required from you at this time.</Trans>
</p>
{user && (
<Button className="mt-6" asChild>
<Link href={`/`}>Return Home</Link>
</Button>
)}
</div>
</div>
);
}
@@ -1,6 +1,6 @@
'use client';
import { useLayoutEffect, useMemo, useRef, useState, useTransition } from 'react';
import { useMemo, useState, useTransition } from 'react';
import { useRouter } from 'next/navigation';
@@ -51,10 +51,6 @@ export const SignatureField = ({
const { _ } = useLingui();
const { toast } = useToast();
const signatureRef = useRef<HTMLParagraphElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const [fontSize, setFontSize] = useState(2);
const { signature: providedSignature, setSignature: setProvidedSignature } =
useRequiredSigningContext();
@@ -112,7 +108,6 @@ export const SignatureField = ({
actionTarget: field.type,
});
};
const onSign = async (authOptions?: TRecipientActionAuth, signature?: string) => {
try {
const value = signature || providedSignature;
@@ -122,23 +117,11 @@ export const SignatureField = ({
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 = {
token: recipient.token,
fieldId: field.id,
value,
isBase64: !isTypedSignature,
isBase64: true,
authOptions,
};
@@ -193,41 +176,6 @@ 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 (
<SigningFieldContainer
field={field}
@@ -243,7 +191,7 @@ export const SignatureField = ({
)}
{state === 'empty' && (
<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">
<p className="group-hover:text-primary font-signature text-muted-foreground text-xl duration-200 group-hover:text-yellow-300">
<Trans>Signature</Trans>
</p>
)}
@@ -257,15 +205,10 @@ export const SignatureField = ({
)}
{state === 'signed-text' && (
<div ref={containerRef} className="flex h-full w-full items-center justify-center p-2">
<p
ref={signatureRef}
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>
<p className="font-signature text-muted-foreground dark:text-background text-lg duration-200 sm:text-xl md:text-2xl lg:text-3xl">
{/* This optional chaining is intentional, we don't want to move the check into the condition above */}
{signature?.typedSignature}
</p>
)}
<Dialog open={showSignatureModal} onOpenChange={setShowSignatureModal}>
@@ -22,7 +22,6 @@ import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
import { DocumentReadOnlyFields } from '~/components/document/document-read-only-fields';
import { AutoSign } from './auto-sign';
import { CheckboxField } from './checkbox-field';
import { DateField } from './date-field';
import { DropdownField } from './dropdown-field';
@@ -32,7 +31,6 @@ import { InitialsField } from './initials-field';
import { NameField } from './name-field';
import { NumberField } from './number-field';
import { RadioField } from './radio-field';
import { RejectDocumentDialog } from './reject-document-dialog';
import { SignatureField } from './signature-field';
import { TextField } from './text-field';
@@ -59,33 +57,29 @@ export const SigningPageView = ({
{document.title}
</h1>
<div className="mt-2.5 flex flex-wrap items-center justify-between gap-x-6">
<div>
<p
className="text-muted-foreground truncate"
title={document.User.name ? document.User.name : ''}
>
{document.User.name}
</p>
<p className="text-muted-foreground">
{match(recipient.role)
.with(RecipientRole.VIEWER, () => (
<Trans>({document.User.email}) has invited you to view this document</Trans>
))
.with(RecipientRole.SIGNER, () => (
<Trans>({document.User.email}) has invited you to sign this document</Trans>
))
.with(RecipientRole.APPROVER, () => (
<Trans>({document.User.email}) has invited you to approve this document</Trans>
))
.otherwise(() => null)}
</p>
</div>
<RejectDocumentDialog document={document} token={recipient.token} />
<div className="mt-2.5 flex items-center gap-x-6">
<p
className="text-muted-foreground truncate"
title={document.User.name ? document.User.name : ''}
>
{document.User.name}
</p>
</div>
<p className="text-muted-foreground">
{match(recipient.role)
.with(RecipientRole.VIEWER, () => (
<Trans>({document.User.email}) has invited you to view this document</Trans>
))
.with(RecipientRole.SIGNER, () => (
<Trans>({document.User.email}) has invited you to sign this document</Trans>
))
.with(RecipientRole.APPROVER, () => (
<Trans>({document.User.email}) has invited you to approve this document</Trans>
))
.otherwise(() => null)}
</p>
<div className="mt-8 grid grid-cols-12 gap-y-8 lg:gap-x-8 lg:gap-y-0">
<Card
className="col-span-12 rounded-xl before:rounded-xl lg:col-span-7 xl:col-span-8"
@@ -114,8 +108,6 @@ export const SigningPageView = ({
<DocumentReadOnlyFields fields={completedFields} />
<AutoSign recipient={recipient} fields={fields} />
<ElementVisible target={PDF_VIEWER_PAGE_SELECTOR}>
{fields.map((field) =>
match(field.type)
@@ -252,16 +252,14 @@ export const TextField = ({ field, recipient, onSignField, onUnsignField }: Text
)}
>
<span className="flex items-center justify-center gap-x-1">
<Type 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 || <Trans>Text</Trans>}
</span>
<Type />
{fieldDisplayName || <Trans>Text</Trans>}
</span>
</p>
)}
{field.inserted && (
<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">
<p className="text-muted-foreground dark:text-background/80 flex items-center justify-center gap-x-1 duration-200">
{field.customText.length < 20
? field.customText
: field.customText.substring(0, 15) + '...'}
@@ -52,7 +52,13 @@ export default async function TeamsSettingsPage({ params }: TeamsSettingsPagePro
<AvatarImageForm className="mb-8" team={team} user={session.user} />
<UpdateTeamForm teamId={team.id} teamName={team.name} teamUrl={team.url} />
<UpdateTeamForm
teamId={team.id}
teamName={team.name}
teamUrl={team.url}
documentVisibility={team.teamGlobalSettings?.documentVisibility}
includeSenderDetails={team.teamGlobalSettings?.includeSenderDetails}
/>
<section className="mt-6 space-y-6">
{(team.teamEmail || team.emailVerification) && (
@@ -39,8 +39,6 @@ const ZTeamDocumentPreferencesFormSchema = z.object({
documentVisibility: z.nativeEnum(DocumentVisibility),
documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES),
includeSenderDetails: z.boolean(),
typedSignatureEnabled: z.boolean(),
includeSigningCertificate: z.boolean(),
});
type TTeamDocumentPreferencesFormSchema = z.infer<typeof ZTeamDocumentPreferencesFormSchema>;
@@ -70,8 +68,6 @@ export const TeamDocumentPreferencesForm = ({
? settings?.documentLanguage
: 'en',
includeSenderDetails: settings?.includeSenderDetails ?? false,
typedSignatureEnabled: settings?.typedSignatureEnabled ?? true,
includeSigningCertificate: settings?.includeSigningCertificate ?? true,
},
resolver: zodResolver(ZTeamDocumentPreferencesFormSchema),
});
@@ -80,13 +76,7 @@ export const TeamDocumentPreferencesForm = ({
const onSubmit = async (data: TTeamDocumentPreferencesFormSchema) => {
try {
const {
documentVisibility,
documentLanguage,
includeSenderDetails,
includeSigningCertificate,
typedSignatureEnabled,
} = data;
const { documentVisibility, documentLanguage, includeSenderDetails } = data;
await updateTeamDocumentPreferences({
teamId: team.id,
@@ -94,8 +84,6 @@ export const TeamDocumentPreferencesForm = ({
documentVisibility,
documentLanguage,
includeSenderDetails,
typedSignatureEnabled,
includeSigningCertificate,
},
});
@@ -117,7 +105,7 @@ export const TeamDocumentPreferencesForm = ({
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<fieldset
className="flex h-full max-w-xl flex-col gap-y-6"
className="flex h-full max-w-xl flex-col gap-y-4"
disabled={form.formState.isSubmitting}
>
<FormField
@@ -239,67 +227,6 @@ 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">
<Button type="submit" loading={form.formState.isSubmitting}>
<Trans>Save</Trans>
-4
View File
@@ -1,12 +1,8 @@
import { z } from 'zod';
import { ZCssVarsSchema } from './css-vars';
export const ZBaseEmbedDataSchema = z.object({
darkModeDisabled: z.boolean().optional().default(false),
css: z
.string()
.optional()
.transform((value) => value || undefined),
cssVars: ZCssVarsSchema.optional().default({}),
});
-1
View File
@@ -10,7 +10,6 @@ export type EmbedDocumentCompletedPageProps = {
};
export const EmbedDocumentCompleted = ({ name, signature }: EmbedDocumentCompletedPageProps) => {
console.log({ signature });
return (
<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">
-59
View File
@@ -1,59 +0,0 @@
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;
};
@@ -1,6 +1,6 @@
'use client';
import { useEffect, useLayoutEffect, useState } from 'react';
import { useEffect, useState } from 'react';
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 { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
import { validateFieldsInserted } from '@documenso/lib/utils/fields';
import type { DocumentMeta, Recipient, Signature, TemplateMeta } from '@documenso/prisma/client';
import type { DocumentMeta, Recipient, TemplateMeta } from '@documenso/prisma/client';
import { type DocumentData, type Field, FieldType } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react';
import type {
@@ -38,7 +38,6 @@ import { Logo } from '~/components/branding/logo';
import { EmbedClientLoading } from '../../client-loading';
import { EmbedDocumentCompleted } from '../../completed';
import { EmbedDocumentFields } from '../../document-fields';
import { injectCss } from '../../util';
import { ZDirectTemplateEmbedDataSchema } from './schema';
export type EmbedDirectTemplateClientPageProps = {
@@ -48,8 +47,6 @@ export type EmbedDirectTemplateClientPageProps = {
recipient: Recipient;
fields: Field[];
metadata?: DocumentMeta | TemplateMeta | null;
hidePoweredBy?: boolean;
isPlatformOrEnterprise?: boolean;
};
export const EmbedDirectTemplateClientPage = ({
@@ -59,8 +56,6 @@ export const EmbedDirectTemplateClientPage = ({
recipient,
fields,
metadata,
hidePoweredBy = false,
isPlatformOrEnterprise = false,
}: EmbedDirectTemplateClientPageProps) => {
const { _ } = useLingui();
const { toast } = useToast();
@@ -113,9 +108,9 @@ export const EmbedDirectTemplateClientPage = ({
created: new Date(),
recipientId: 1,
fieldId: 1,
signatureImageAsBase64: payload.value.startsWith('data:') ? payload.value : null,
typedSignature: payload.value.startsWith('data:') ? null : payload.value,
} satisfies Signature;
signatureImageAsBase64: payload.value,
typedSignature: null,
};
}
if (field.type === FieldType.DATE) {
@@ -254,7 +249,7 @@ export const EmbedDirectTemplateClientPage = ({
}
};
useLayoutEffect(() => {
useEffect(() => {
const hash = window.location.hash.slice(1);
try {
@@ -269,17 +264,6 @@ export const EmbedDirectTemplateClientPage = ({
setFullName(data.name);
setIsNameLocked(!!data.lockName);
}
if (data.darkModeDisabled) {
document.documentElement.classList.add('dark-mode-disabled');
}
if (isPlatformOrEnterprise) {
injectCss({
css: data.css,
cssVars: data.cssVars,
});
}
} catch (err) {
console.error(err);
}
@@ -312,8 +296,8 @@ export const EmbedDirectTemplateClientPage = ({
fieldId: 1,
recipientId: 1,
created: new Date(),
signatureImageAsBase64: signature?.startsWith('data:') ? signature : null,
typedSignature: signature?.startsWith('data:') ? null : signature,
typedSignature: null,
signatureImageAsBase64: signature,
}}
/>
);
@@ -468,12 +452,10 @@ export const EmbedDirectTemplateClientPage = ({
/>
</div>
{!hidePoweredBy && (
<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">
<span>Powered by</span>
<Logo className="ml-2 inline-block h-[14px]" />
</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">
<span>Powered by</span>
<Logo className="ml-2 inline-block h-[14px]" />
</div>
</div>
);
};
@@ -2,11 +2,8 @@ import { notFound } from 'next/navigation';
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 { 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 { DocumentAccessAuth } from '@documenso/lib/types/document-auth';
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
@@ -54,14 +51,6 @@ export default async function EmbedDirectTemplatePage({ params }: EmbedDirectTem
documentAuth: template.authOptions,
});
const [isPlatformDocument, isEnterpriseDocument] = await Promise.all([
isDocumentPlatform(template),
isUserEnterprise({
userId: template.userId,
teamId: template.teamId ?? undefined,
}),
]);
const isAccessAuthValid = match(derivedRecipientAccessAuth)
.with(DocumentAccessAuth.ACCOUNT, () => user !== null)
.with(null, () => true)
@@ -83,12 +72,6 @@ export default async function EmbedDirectTemplatePage({ params }: EmbedDirectTem
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 (
<SigningProvider email={user?.email} fullName={user?.name} signature={user?.signature}>
<DocumentAuthProvider
@@ -103,8 +86,6 @@ export default async function EmbedDirectTemplatePage({ params }: EmbedDirectTem
recipient={recipient}
fields={fields}
metadata={template.templateMeta}
hidePoweredBy={isPlatformDocument || isEnterpriseDocument || hidePoweredBy}
isPlatformOrEnterprise={isPlatformDocument || isEnterpriseDocument}
/>
</DocumentAuthProvider>
</SigningProvider>
@@ -58,7 +58,6 @@ export const EmbedDocumentFields = ({
recipient={recipient}
onSignField={onSignField}
onUnsignField={onUnsignField}
typedSignatureEnabled={metadata?.typedSignatureEnabled}
/>
))
.with(FieldType.INITIALS, () => (
@@ -1,6 +1,6 @@
'use client';
import { useEffect, useLayoutEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
@@ -28,7 +28,6 @@ import { Logo } from '~/components/branding/logo';
import { EmbedClientLoading } from '../../client-loading';
import { EmbedDocumentCompleted } from '../../completed';
import { EmbedDocumentFields } from '../../document-fields';
import { injectCss } from '../../util';
import { ZSignDocumentEmbedDataSchema } from './schema';
export type EmbedSignDocumentClientPageProps = {
@@ -39,8 +38,6 @@ export type EmbedSignDocumentClientPageProps = {
fields: Field[];
metadata?: DocumentMeta | TemplateMeta | null;
isCompleted?: boolean;
hidePoweredBy?: boolean;
isPlatformOrEnterprise?: boolean;
};
export const EmbedSignDocumentClientPage = ({
@@ -51,8 +48,6 @@ export const EmbedSignDocumentClientPage = ({
fields,
metadata,
isCompleted,
hidePoweredBy = false,
isPlatformOrEnterprise = false,
}: EmbedSignDocumentClientPageProps) => {
const { _ } = useLingui();
const { toast } = useToast();
@@ -136,7 +131,7 @@ export const EmbedSignDocumentClientPage = ({
}
};
useLayoutEffect(() => {
useEffect(() => {
const hash = window.location.hash.slice(1);
try {
@@ -149,17 +144,6 @@ export const EmbedSignDocumentClientPage = ({
// 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.
setIsNameLocked(!!data.lockName);
if (data.darkModeDisabled) {
document.documentElement.classList.add('dark-mode-disabled');
}
if (isPlatformOrEnterprise) {
injectCss({
css: data.css,
cssVars: data.cssVars,
});
}
} catch (err) {
console.error(err);
}
@@ -192,8 +176,8 @@ export const EmbedSignDocumentClientPage = ({
fieldId: 1,
recipientId: 1,
created: new Date(),
signatureImageAsBase64: signature?.startsWith('data:') ? signature : null,
typedSignature: signature?.startsWith('data:') ? null : signature,
typedSignature: null,
signatureImageAsBase64: signature,
}}
/>
);
@@ -218,7 +202,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"
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 */}
<div>
<div className="flex items-center justify-between gap-x-2">
@@ -341,12 +325,10 @@ export const EmbedSignDocumentClientPage = ({
<EmbedDocumentFields recipient={recipient} fields={fields} metadata={metadata} />
</div>
{!hidePoweredBy && (
<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">
<span>Powered by</span>
<Logo className="ml-2 inline-block h-[14px]" />
</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">
<span>Powered by</span>
<Logo className="ml-2 inline-block h-[14px]" />
</div>
</div>
);
};
@@ -2,14 +2,11 @@ import { notFound } from 'next/navigation';
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 { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
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 { 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 { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
import { DocumentStatus } from '@documenso/prisma/client';
@@ -59,14 +56,6 @@ export default async function EmbedSignDocumentPage({ params }: EmbedSignDocumen
return <EmbedPaywall />;
}
const [isPlatformDocument, isEnterpriseDocument] = await Promise.all([
isDocumentPlatform(document),
isUserEnterprise({
userId: document.userId,
teamId: document.teamId ?? undefined,
}),
]);
const { derivedRecipientAccessAuth } = extractDocumentAuthMethods({
documentAuth: document.authOptions,
});
@@ -85,12 +74,6 @@ 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 (
<SigningProvider
email={recipient.email}
@@ -110,8 +93,6 @@ export default async function EmbedSignDocumentPage({ params }: EmbedSignDocumen
fields={fields}
metadata={document.documentMeta}
isCompleted={document.status === DocumentStatus.COMPLETED}
hidePoweredBy={isPlatformDocument || isEnterpriseDocument || hidePoweredBy}
isPlatformOrEnterprise={isPlatformDocument || isEnterpriseDocument}
/>
</DocumentAuthProvider>
</SigningProvider>
-20
View File
@@ -1,20 +0,0 @@
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);
}
}
};
@@ -1,4 +1,3 @@
import { RecipientStatusType } from '@documenso/lib/client-only/recipient-type';
import { Avatar, AvatarFallback } from '@documenso/ui/primitives/avatar';
const ZIndexes: { [key: string]: string } = {
@@ -13,7 +12,7 @@ export type StackAvatarProps = {
first?: boolean;
zIndex?: string;
fallbackText?: string;
type: RecipientStatusType;
type: 'unsigned' | 'waiting' | 'opened' | 'completed';
};
export const StackAvatar = ({ first, zIndex, fallbackText = '', type }: StackAvatarProps) => {
@@ -26,21 +25,18 @@ export const StackAvatar = ({ first, zIndex, fallbackText = '', type }: StackAva
}
switch (type) {
case RecipientStatusType.UNSIGNED:
case 'unsigned':
classes = 'bg-dawn-200 text-dawn-900';
break;
case RecipientStatusType.OPENED:
case 'opened':
classes = 'bg-yellow-200 text-yellow-700';
break;
case RecipientStatusType.WAITING:
case 'waiting':
classes = 'bg-water text-water-700';
break;
case RecipientStatusType.COMPLETED:
case 'completed':
classes = 'bg-documenso-200 text-documenso-800';
break;
case RecipientStatusType.REJECTED:
classes = 'bg-red-200 text-red-800';
break;
default:
break;
}
@@ -8,7 +8,7 @@ import { useLingui } from '@lingui/react';
import { RecipientStatusType, getRecipientType } from '@documenso/lib/client-only/recipient-type';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
import { type DocumentStatus, type Recipient } from '@documenso/prisma/client';
import type { DocumentStatus, Recipient } from '@documenso/prisma/client';
import { PopoverHover } from '@documenso/ui/primitives/popover';
import { AvatarWithRecipient } from './avatar-with-recipient';
@@ -46,22 +46,7 @@ export const StackAvatarsWithTooltip = ({
(recipient) => getRecipientType(recipient) === RecipientStatusType.UNSIGNED,
);
const rejectedRecipients = recipients.filter(
(recipient) => getRecipientType(recipient) === RecipientStatusType.REJECTED,
);
const sortedRecipients = useMemo(() => {
const otherRecipients = recipients.filter(
(recipient) => getRecipientType(recipient) !== RecipientStatusType.REJECTED,
);
return [
...rejectedRecipients.sort((a, b) => a.id - b.id),
...otherRecipients.sort((a, b) => {
return a.id - b.id;
}),
];
}, [recipients]);
const sortedRecipients = useMemo(() => recipients.sort((a, b) => a.id - b.id), [recipients]);
return (
<PopoverHover
@@ -95,30 +80,6 @@ export const StackAvatarsWithTooltip = ({
</div>
)}
{rejectedRecipients.length > 0 && (
<div>
<h1 className="text-base font-medium">
<Trans>Rejected</Trans>
</h1>
{rejectedRecipients.map((recipient: Recipient) => (
<div key={recipient.id} className="my-1 flex items-center gap-2">
<StackAvatar
first={true}
key={recipient.id}
type={getRecipientType(recipient)}
fallbackText={recipientAbbreviation(recipient)}
/>
<div>
<p className="text-muted-foreground text-sm">{recipient.email}</p>
<p className="text-muted-foreground/70 text-xs">
{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
</p>
</div>
</div>
))}
</div>
)}
{waitingRecipients.length > 0 && (
<div>
<h1 className="text-base font-medium">
@@ -6,14 +6,22 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { AnimatePresence, motion } from 'framer-motion';
import { useSession } from 'next-auth/react';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import type { z } from 'zod';
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { DocumentVisibility } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react';
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 { Checkbox } from '@documenso/ui/primitives/checkbox';
import {
Form,
FormControl,
@@ -29,17 +37,29 @@ export type UpdateTeamDialogProps = {
teamId: number;
teamName: string;
teamUrl: string;
documentVisibility?: DocumentVisibility;
includeSenderDetails?: boolean;
};
const ZUpdateTeamFormSchema = ZUpdateTeamMutationSchema.shape.data.pick({
name: true,
url: true,
documentVisibility: true,
includeSenderDetails: true,
});
type TUpdateTeamFormSchema = z.infer<typeof ZUpdateTeamFormSchema>;
export const UpdateTeamForm = ({ teamId, teamName, teamUrl }: UpdateTeamDialogProps) => {
export const UpdateTeamForm = ({
teamId,
teamName,
teamUrl,
documentVisibility,
includeSenderDetails,
}: UpdateTeamDialogProps) => {
const router = useRouter();
const { data: session } = useSession();
const email = session?.user?.email;
const { _ } = useLingui();
const { toast } = useToast();
@@ -48,17 +68,36 @@ export const UpdateTeamForm = ({ teamId, teamName, teamUrl }: UpdateTeamDialogPr
defaultValues: {
name: teamName,
url: teamUrl,
documentVisibility,
includeSenderDetails,
},
});
const { mutateAsync: updateTeam } = trpc.team.updateTeam.useMutation();
const includeSenderDetailsCheck = form.watch('includeSenderDetails');
const onFormSubmit = async ({ name, url }: TUpdateTeamFormSchema) => {
const mapVisibilityToRole = (visibility: DocumentVisibility): DocumentVisibility =>
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 {
await updateTeam({
data: {
name,
url,
documentVisibility,
includeSenderDetails,
},
teamId,
});
@@ -72,6 +111,8 @@ export const UpdateTeamForm = ({ teamId, teamName, teamUrl }: UpdateTeamDialogPr
form.reset({
name,
url,
documentVisibility,
includeSenderDetails,
});
if (url !== teamUrl) {
@@ -145,6 +186,68 @@ export const UpdateTeamForm = ({ teamId, teamName, teamUrl }: UpdateTeamDialogPr
)}
/>
<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">
<AnimatePresence>
{form.formState.isDirty && (
@@ -169,7 +169,6 @@ export const DocumentHistorySheet = ({
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED },
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED },
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED },
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED },
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT },
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_MOVED_TO_TEAM },
() => null,
@@ -138,7 +138,6 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
containerClassName={cn('rounded-lg border bg-background')}
defaultValue={user.signature ?? undefined}
onChange={(v) => onChange(v ?? '')}
allowTypedSignature={true}
/>
</FormControl>
<FormMessage />
+560 -236
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,6 +1,6 @@
{
"private": true,
"version": "1.8.1-rc.1",
"version": "1.8.0-rc.0",
"scripts": {
"build": "turbo run build",
"build:web": "turbo run build --filter=@documenso/web",
@@ -52,7 +52,7 @@
"husky": "^9.0.11",
"lint-staged": "^15.2.2",
"playwright": "1.43.0",
"prettier": "^3.3.3",
"prettier": "^2.5.1",
"rimraf": "^5.0.1",
"turbo": "^1.9.3"
},
-3
View File
@@ -168,9 +168,6 @@ export const ApiContractV1 = c.router(
500: ZUnsuccessfulResponseSchema,
},
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: {
-1
View File
@@ -302,7 +302,6 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
redirectUrl: body.meta.redirectUrl,
signingOrder: body.meta.signingOrder,
language: body.meta.language,
typedSignatureEnabled: body.meta.typedSignatureEnabled,
requestMetadata: extractNextApiRequestMetadata(args.req),
});
+7 -13
View File
@@ -3,6 +3,7 @@ import { z } from 'zod';
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
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 { ZUrlSchema } from '@documenso/lib/schemas/common';
import {
@@ -13,7 +14,6 @@ import {
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
import {
DocumentDataType,
DocumentDistributionMethod,
DocumentSigningOrder,
FieldType,
ReadStatus,
@@ -67,10 +67,7 @@ export type TSuccessfulDocumentResponseSchema = z.infer<typeof ZSuccessfulDocume
export const ZSendDocumentForSigningMutationSchema = z
.object({
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.',
}),
sendEmail: z.boolean().optional().default(true),
})
.or(z.literal('').transform(() => ({ sendEmail: true })));
@@ -132,7 +129,6 @@ export const ZCreateDocumentMutationSchema = z.object({
redirectUrl: z.string(),
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
typedSignatureEnabled: z.boolean().optional().default(true),
})
.partial(),
authOptions: z
@@ -227,14 +223,14 @@ export type TCreateDocumentFromTemplateMutationResponseSchema = z.infer<
export const ZGenerateDocumentFromTemplateMutationSchema = z.object({
title: z.string().optional(),
externalId: z.string().optional(),
externalId: z.string().nullish(),
recipients: z
.array(
z.object({
id: z.number(),
email: z.string().email(),
name: z.string().optional(),
signingOrder: z.number().optional(),
email: z.string().email().min(1),
signingOrder: z.number().nullish(),
}),
)
.refine(
@@ -253,10 +249,8 @@ export const ZGenerateDocumentFromTemplateMutationSchema = z.object({
timezone: z.string(),
dateFormat: z.string(),
redirectUrl: ZUrlSchema,
signingOrder: z.nativeEnum(DocumentSigningOrder),
language: z.enum(SUPPORTED_LANGUAGE_CODES),
distributionMethod: z.nativeEnum(DocumentDistributionMethod),
typedSignatureEnabled: z.boolean(),
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
})
.partial()
.optional(),
@@ -1,271 +0,0 @@
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);
});
});
@@ -17,17 +17,19 @@ test('[TEAMS]: update the default document visibility in the team global setting
page,
email: team.owner.email,
password: 'password',
redirectPath: `/t/${team.url}/settings/preferences`,
redirectPath: `/t/${team.url}/settings`,
});
// !: Brittle selector
await page.getByRole('combobox').first().click();
await page.getByRole('combobox').click();
await page.getByRole('option', { name: 'Admin' }).click();
await page.getByRole('button', { name: 'Save' }).first().click();
await page.getByRole('button', { name: 'Update team' }).click();
const toast = page.locator('li[role="status"][data-state="open"]').first();
await expect(toast).toBeVisible();
await expect(toast.getByText('Document preferences updated', { exact: true })).toBeVisible();
await expect(toast.getByText('Success', { 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 }) => {
@@ -39,7 +41,7 @@ test('[TEAMS]: update the sender details in the team global settings', async ({
page,
email: team.owner.email,
password: 'password',
redirectPath: `/t/${team.url}/settings/preferences`,
redirectPath: `/t/${team.url}/settings`,
});
const checkbox = page.getByLabel('Send on Behalf of Team');
@@ -47,11 +49,14 @@ test('[TEAMS]: update the sender details in the team global settings', async ({
await expect(checkbox).toBeChecked();
await page.getByRole('button', { name: 'Save' }).first().click();
await page.getByRole('button', { name: 'Update team' }).click();
const toast = page.locator('li[role="status"][data-state="open"]').first();
await expect(toast).toBeVisible();
await expect(toast.getByText('Document preferences updated', { exact: true })).toBeVisible();
await expect(toast.getByText('Success', { exact: true })).toBeVisible();
await expect(
toast.getByText('Your team has been successfully updated.', { exact: true }),
).toBeVisible();
await expect(checkbox).toBeChecked();
});
+2 -4
View File
@@ -7,17 +7,15 @@
"scripts": {
"test:dev": "NODE_OPTIONS=--experimental-require-module playwright test",
"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 $E2E_TEST_PATH\""
"test:e2e": "NODE_OPTIONS=--experimental-require-module start-server-and-test \"npm run start -w @documenso/web\" http://localhost:3000 \"playwright test\""
},
"keywords": [],
"author": "",
"devDependencies": {
"@playwright/test": "^1.18.1",
"@types/node": "^20.8.2",
"@documenso/lib": "*",
"@documenso/prisma": "*",
"@documenso/web": "*",
"pdf-lib": "^1.17.1"
"@documenso/web": "*"
},
"dependencies": {
"start-server-and-test": "^2.0.1"
@@ -9,7 +9,6 @@ export const getDocumentRelatedPrices = async () => {
return await getPricesByPlan([
STRIPE_PLAN_TYPE.REGULAR,
STRIPE_PLAN_TYPE.COMMUNITY,
STRIPE_PLAN_TYPE.PLATFORM,
STRIPE_PLAN_TYPE.ENTERPRISE,
]);
};
@@ -1,13 +0,0 @@
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);
};
@@ -9,7 +9,6 @@ export const getPrimaryAccountPlanPrices = async () => {
return await getPricesByPlan([
STRIPE_PLAN_TYPE.REGULAR,
STRIPE_PLAN_TYPE.COMMUNITY,
STRIPE_PLAN_TYPE.PLATFORM,
STRIPE_PLAN_TYPE.ENTERPRISE,
]);
};
@@ -6,11 +6,7 @@ import { getPricesByPlan } from './get-prices-by-plan';
* Returns the Stripe prices of items that affect the amount of teams a user can create.
*/
export const getTeamRelatedPrices = async () => {
return await getPricesByPlan([
STRIPE_PLAN_TYPE.COMMUNITY,
STRIPE_PLAN_TYPE.PLATFORM,
STRIPE_PLAN_TYPE.ENTERPRISE,
]);
return await getPricesByPlan([STRIPE_PLAN_TYPE.COMMUNITY, STRIPE_PLAN_TYPE.ENTERPRISE]);
};
/**
@@ -1,61 +0,0 @@
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);
};
@@ -1,5 +1,3 @@
import { useMemo } from 'react';
import { Trans } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { match } from 'ts-pattern';
@@ -38,14 +36,6 @@ export const TemplateDocumentInvite = ({
const { actionVerb } = RECIPIENT_ROLES_DESCRIPTION[role];
const rejectDocumentLink = useMemo(() => {
const url = new URL(signDocumentLink);
url.searchParams.set('reject', 'true');
return url.toString();
}, []);
return (
<>
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
@@ -89,13 +79,6 @@ export const TemplateDocumentInvite = ({
</Text>
<Section className="mb-6 mt-8 text-center">
<Button
className="mr-4 inline-flex items-center justify-center rounded-lg bg-red-500 px-6 py-3 text-center text-sm font-medium text-black no-underline"
href={rejectDocumentLink}
>
<Trans>Reject Document</Trans>
</Button>
<Button
className="bg-documenso-500 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
href={signDocumentLink}
@@ -1,48 +0,0 @@
import { Trans } from '@lingui/macro';
import { Button, Heading, Text } from '../components';
export interface TemplateDocumentRejectedProps {
documentName: string;
recipientName: string;
rejectionReason?: string;
documentUrl: string;
}
export function TemplateDocumentRejected({
documentName,
recipientName: signerName,
rejectionReason,
documentUrl,
}: TemplateDocumentRejectedProps) {
return (
<div className="mt-4">
<Heading className="mb-4 text-center text-2xl font-semibold text-slate-800">
<Trans>Document Rejected</Trans>
</Heading>
<Text className="mb-4 text-base">
<Trans>
{signerName} has rejected the document "{documentName}".
</Trans>
</Text>
{rejectionReason && (
<Text className="mb-4 text-base text-slate-400">
<Trans>Reason for rejection: {rejectionReason}</Trans>
</Text>
)}
<Text className="mb-6 text-base">
<Trans>You can view the document and its status by clicking the button below.</Trans>
</Text>
<Button
href={documentUrl}
className="bg-documenso-500 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
>
<Trans>View Document</Trans>
</Button>
</div>
);
}
@@ -1,48 +0,0 @@
import { Trans } from '@lingui/macro';
import { Container, Heading, Section, Text } from '../components';
interface TemplateDocumentRejectionConfirmedProps {
recipientName: string;
documentName: string;
documentOwnerName: string;
reason?: string;
}
export function TemplateDocumentRejectionConfirmed({
recipientName,
documentName,
documentOwnerName,
reason,
}: TemplateDocumentRejectionConfirmedProps) {
return (
<Container>
<Section>
<Heading className="text-2xl font-semibold">
<Trans>Rejection Confirmed</Trans>
</Heading>
<Text className="text-primary text-base">
<Trans>
This email confirms that you have rejected the document{' '}
<strong className="font-bold">"{documentName}"</strong> sent by {documentOwnerName}.
</Trans>
</Text>
{reason && (
<Text className="text-base font-medium text-slate-400">
<Trans>Rejection reason: {reason}</Trans>
</Text>
)}
<Text className="text-base">
<Trans>
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.
</Trans>
</Text>
</Section>
</Container>
);
}
@@ -1,70 +0,0 @@
import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import { TemplateDocumentRejected } from '../template-components/template-document-rejected';
import { TemplateFooter } from '../template-components/template-footer';
type DocumentRejectedEmailProps = {
recipientName: string;
documentName: string;
documentUrl: string;
rejectionReason: string;
assetBaseUrl?: string;
};
export function DocumentRejectedEmail({
recipientName,
documentName,
documentUrl,
rejectionReason,
assetBaseUrl = 'http://localhost:3002',
}: DocumentRejectedEmailProps) {
const { _ } = useLingui();
const branding = useBranding();
const previewText = _(msg`${recipientName} has rejected the document '${documentName}'`);
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
<Preview>{previewText}</Preview>
<Body className="mx-auto my-auto bg-white font-sans">
<Section>
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
<Section>
{branding.brandingEnabled && branding.brandingLogo ? (
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
) : (
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
)}
<TemplateDocumentRejected
recipientName={recipientName}
documentName={documentName}
documentUrl={documentUrl}
rejectionReason={rejectionReason}
/>
</Section>
</Container>
<Container className="mx-auto max-w-xl">
<TemplateFooter />
</Container>
</Section>
</Body>
</Html>
);
}
export default DocumentRejectedEmail;
@@ -1,70 +0,0 @@
import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import { TemplateDocumentRejectionConfirmed } from '../template-components/template-document-rejection-confirmed';
import { TemplateFooter } from '../template-components/template-footer';
export type DocumentRejectionConfirmedEmailProps = {
recipientName: string;
documentName: string;
documentOwnerName: string;
reason: string;
assetBaseUrl?: string;
};
export function DocumentRejectionConfirmedEmail({
recipientName,
documentName,
documentOwnerName,
reason,
assetBaseUrl = 'http://localhost:3002',
}: DocumentRejectionConfirmedEmailProps) {
const { _ } = useLingui();
const branding = useBranding();
const previewText = _(msg`You have rejected the document '${documentName}'`);
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
<Preview>{previewText}</Preview>
<Body className="mx-auto my-auto bg-white font-sans">
<Section>
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
<Section>
{branding.brandingEnabled && branding.brandingLogo ? (
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
) : (
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
)}
<TemplateDocumentRejectionConfirmed
recipientName={recipientName}
documentName={documentName}
documentOwnerName={documentOwnerName}
reason={reason}
/>
</Section>
</Container>
<Container className="mx-auto max-w-xl">
<TemplateFooter />
</Container>
</Section>
</Body>
</Html>
);
}
export default DocumentRejectionConfirmedEmail;
@@ -6,7 +6,6 @@ export enum RecipientStatusType {
OPENED = 'opened',
WAITING = 'waiting',
UNSIGNED = 'unsigned',
REJECTED = 'rejected',
}
export const getRecipientType = (recipient: Recipient) => {
@@ -17,10 +16,6 @@ export const getRecipientType = (recipient: Recipient) => {
return RecipientStatusType.COMPLETED;
}
if (recipient.signingStatus === SigningStatus.REJECTED) {
return RecipientStatusType.REJECTED;
}
if (
recipient.sendStatus === SendStatus.SENT &&
recipient.readStatus === ReadStatus.OPENED &&
-1
View File
@@ -7,6 +7,5 @@ export enum STRIPE_PLAN_TYPE {
REGULAR = 'regular',
TEAM = 'team',
COMMUNITY = 'community',
PLATFORM = 'platform',
ENTERPRISE = 'enterprise',
}
+5 -1
View File
@@ -1,6 +1,6 @@
import { z } from 'zod';
export const SUPPORTED_LANGUAGE_CODES = ['de', 'en', 'fr', 'es'] as const;
export const SUPPORTED_LANGUAGE_CODES = ['de', 'en', 'fr', 'es', 'pl'] as const;
export const ZSupportedLanguageCodeSchema = z.enum(SUPPORTED_LANGUAGE_CODES).catch('en');
@@ -46,6 +46,10 @@ export const SUPPORTED_LANGUAGES: Record<string, SupportedLanguage> = {
full: 'Spanish',
short: 'es',
},
pl: {
full: 'Polish',
short: 'pl',
},
} satisfies Record<SupportedLanguageCodes, SupportedLanguage>;
export const isValidLanguageCode = (code: unknown): code is SupportedLanguageCodes =>
-2
View File
@@ -1,6 +1,5 @@
import { JobClient } from './client/client';
import { SEND_CONFIRMATION_EMAIL_JOB_DEFINITION } from './definitions/emails/send-confirmation-email';
import { SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION } from './definitions/emails/send-rejection-emails';
import { SEND_SIGNING_EMAIL_JOB_DEFINITION } from './definitions/emails/send-signing-email';
import { SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION } from './definitions/emails/send-team-deleted-email';
import { SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION } from './definitions/emails/send-team-member-joined-email';
@@ -18,7 +17,6 @@ export const jobsClient = new JobClient([
SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION,
SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION,
SEAL_DOCUMENT_JOB_DEFINITION,
SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION,
] as const);
export const jobs = jobsClient;
@@ -1,169 +0,0 @@
import { createElement } from 'react';
import { msg } from '@lingui/macro';
import { z } from 'zod';
import { mailer } from '@documenso/email/mailer';
import DocumentRejectedEmail from '@documenso/email/templates/document-rejected';
import DocumentRejectionConfirmedEmail from '@documenso/email/templates/document-rejection-confirmed';
import { prisma } from '@documenso/prisma';
import { SendStatus, SigningStatus } from '@documenso/prisma/client';
import { getI18nInstance } from '../../../client-only/providers/i18n.server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
import { extractDerivedDocumentEmailSettings } from '../../../types/document-email';
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
import { formatDocumentsPath } from '../../../utils/teams';
import { type JobDefinition } from '../../client/_internal/job';
const SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_ID = 'send.signing.rejected.emails';
const SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_SCHEMA = z.object({
documentId: z.number(),
recipientId: z.number(),
});
export const SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION = {
id: SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_ID,
name: 'Send Rejection Emails',
version: '1.0.0',
trigger: {
name: SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_ID,
schema: SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_SCHEMA,
},
handler: async ({ payload, io }) => {
const { documentId, recipientId } = payload;
const [document, recipient] = await Promise.all([
prisma.document.findFirstOrThrow({
where: {
id: documentId,
},
include: {
User: true,
documentMeta: true,
team: {
select: {
teamEmail: true,
name: true,
url: true,
teamGlobalSettings: true,
},
},
},
}),
prisma.recipient.findFirstOrThrow({
where: {
id: recipientId,
signingStatus: SigningStatus.REJECTED,
},
}),
]);
const { documentMeta, team, User: documentOwner } = document;
const isEmailEnabled = extractDerivedDocumentEmailSettings(
document.documentMeta,
).recipientSigningRequest;
if (!isEmailEnabled) {
return;
}
const i18n = await getI18nInstance(documentMeta?.language);
// Send confirmation email to the recipient who rejected
await io.runTask('send-rejection-confirmation-email', async () => {
const recipientTemplate = createElement(DocumentRejectionConfirmedEmail, {
recipientName: recipient.name,
documentName: document.title,
documentOwnerName: document.User.name || document.User.email,
reason: recipient.rejectionReason || '',
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(),
});
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(recipientTemplate, { lang: documentMeta?.language, branding }),
renderEmailWithI18N(recipientTemplate, {
lang: documentMeta?.language,
branding,
plainText: true,
}),
]);
await mailer.sendMail({
to: {
name: recipient.name,
address: recipient.email,
},
from: {
name: FROM_NAME,
address: FROM_ADDRESS,
},
subject: i18n._(msg`Document "${document.title}" - Rejection Confirmed`),
html,
text,
});
});
// Send notification email to document owner
await io.runTask('send-owner-notification-email', async () => {
const ownerTemplate = createElement(DocumentRejectedEmail, {
recipientName: recipient.name,
documentName: document.title,
documentUrl: `${NEXT_PUBLIC_WEBAPP_URL()}${formatDocumentsPath(document.team?.url)}/${
document.id
}`,
rejectionReason: recipient.rejectionReason || '',
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(),
});
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(ownerTemplate, { lang: documentMeta?.language, branding }),
renderEmailWithI18N(ownerTemplate, {
lang: documentMeta?.language,
branding,
plainText: true,
}),
]);
await mailer.sendMail({
to: {
name: documentOwner.name || '',
address: documentOwner.email,
},
from: {
name: FROM_NAME,
address: FROM_ADDRESS,
},
subject: i18n._(msg`Document "${document.title}" - Rejected by ${recipient.name}`),
html,
text,
});
});
await io.runTask('update-recipient', async () => {
await prisma.recipient.update({
where: {
id: recipient.id,
},
data: {
sendStatus: SendStatus.SENT,
},
});
});
},
} as const satisfies JobDefinition<
typeof SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_ID,
z.infer<typeof SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_SCHEMA>
>;
@@ -17,14 +17,12 @@ const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
documentVisibility: z.nativeEnum(DocumentVisibility),
documentLanguage: z.string(),
includeSenderDetails: z.boolean(),
includeSigningCertificate: z.boolean(),
brandingEnabled: z.boolean(),
brandingLogo: z.string(),
brandingUrl: z.string(),
brandingCompanyDetails: z.string(),
brandingHidePoweredBy: z.boolean(),
teamId: z.number(),
typedSignatureEnabled: z.boolean(),
})
.nullish(),
}),
@@ -57,17 +57,7 @@ export const SEAL_DOCUMENT_JOB_DEFINITION = {
},
},
include: {
documentMeta: true,
Recipient: true,
team: {
select: {
teamGlobalSettings: {
select: {
includeSigningCertificate: true,
},
},
},
},
},
});
@@ -127,13 +117,7 @@ export const SEAL_DOCUMENT_JOB_DEFINITION = {
}
const pdfData = await getFile(documentData);
const certificateData =
document.team?.teamGlobalSettings?.includeSigningCertificate ?? true
? await getCertificatePdf({
documentId,
language: document.documentMeta?.language,
}).catch(() => null)
: null;
const certificateData = await getCertificatePdf({ documentId }).catch(() => null);
const newDataId = await io.runTask('decorate-and-sign-pdf', async () => {
const pdfDoc = await PDFDocument.load(pdfData);
+1 -1
View File
@@ -51,7 +51,7 @@
"pg": "^8.11.3",
"playwright": "1.43.0",
"react": "^18",
"remeda": "^2.17.3",
"remeda": "^2.12.1",
"sharp": "0.32.6",
"stripe": "^12.7.0",
"ts-pattern": "^5.0.5",
@@ -1,25 +1,21 @@
import { base32 } from '@scure/base';
import { generateHOTP } from 'oslo/otp';
import { TOTPController } from 'oslo/otp';
import type { User } from '@documenso/prisma/client';
import { DOCUMENSO_ENCRYPTION_KEY } from '../../constants/crypto';
import { symmetricDecrypt } from '../../universal/crypto';
const totp = new TOTPController();
type VerifyTwoFactorAuthenticationTokenOptions = {
user: User;
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 ({
user,
totpCode,
window = 1,
period = 30_000,
}: VerifyTwoFactorAuthenticationTokenOptions) => {
const key = DOCUMENSO_ENCRYPTION_KEY;
@@ -31,21 +27,7 @@ export const verifyTwoFactorAuthenticationToken = async ({
'utf-8',
);
const decodedSecret = base32.decode(secret);
const isValidToken = await totp.verify(totpCode, base32.decode(secret));
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;
return isValidToken;
};
@@ -13,7 +13,6 @@ export const getRecipientsStats = async () => {
[ReadStatus.NOT_OPENED]: 0,
[SigningStatus.SIGNED]: 0,
[SigningStatus.NOT_SIGNED]: 0,
[SigningStatus.REJECTED]: 0,
[SendStatus.SENT]: 0,
[SendStatus.NOT_SENT]: 0,
};
@@ -112,7 +112,6 @@ export const createDocument = async ({
documentMeta: {
create: {
language: team?.teamGlobalSettings?.documentLanguage,
typedSignatureEnabled: team?.teamGlobalSettings?.typedSignatureEnabled,
},
},
},
@@ -66,7 +66,6 @@ export const findDocumentAuditLogs = async ({
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED,
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED,
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED,
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED,
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT,
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_MOVED_TO_TEAM,
],
@@ -112,7 +112,6 @@ export const isRecipientAuthorized = async ({
return await verifyTwoFactorAuthenticationToken({
user,
totpCode: token,
window: 10, // 5 minutes worth of tokens
});
})
.exhaustive();
@@ -1,92 +0,0 @@
import { SigningStatus } from '@prisma/client';
import { TRPCError } from '@trpc/server';
import { jobs } from '@documenso/lib/jobs/client';
import { prisma } from '@documenso/prisma';
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 RejectDocumentWithTokenOptions = {
token: string;
documentId: number;
reason: string;
requestMetadata?: RequestMetadata;
};
export async function rejectDocumentWithToken({
token,
documentId,
reason,
requestMetadata,
}: RejectDocumentWithTokenOptions) {
// Find the recipient and document in a single query
const recipient = await prisma.recipient.findFirst({
where: {
token,
documentId,
},
include: {
Document: {
include: {
User: true,
},
},
},
});
const document = recipient?.Document;
if (!recipient || !document) {
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Document or recipient not found',
});
}
// Add the audit log entry before updating the recipient
// Update the recipient status to rejected
const [updatedRecipient] = await prisma.$transaction([
prisma.recipient.update({
where: {
id: recipient.id,
},
data: {
signedAt: new Date(),
signingStatus: SigningStatus.REJECTED,
rejectionReason: reason,
},
}),
prisma.documentAuditLog.create({
data: createDocumentAuditLogData({
documentId,
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED,
user: {
name: recipient.name,
email: recipient.email,
},
data: {
recipientEmail: recipient.email,
recipientName: recipient.name,
recipientId: recipient.id,
recipientRole: recipient.role,
reason,
},
requestMetadata,
}),
}),
]);
// Send email notifications
await jobs.triggerJob({
name: 'send.signing.rejected.emails',
payload: {
recipientId: recipient.id,
documentId,
},
});
return updatedRecipient;
}
@@ -10,6 +10,7 @@ import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/
import { WebhookTriggerEvents } from '@documenso/prisma/client';
import { signPdf } from '@documenso/signing';
import { ZSupportedLanguageCodeSchema } from '../../constants/i18n';
import type { RequestMetadata } from '../../universal/extract-request-metadata';
import { getFile } from '../../universal/upload/get-file';
import { putPdfFile } from '../../universal/upload/put-file';
@@ -47,15 +48,6 @@ export const sealDocument = async ({
documentData: true,
documentMeta: true,
Recipient: true,
team: {
select: {
teamGlobalSettings: {
select: {
includeSigningCertificate: true,
},
},
},
},
},
});
@@ -100,13 +92,11 @@ export const sealDocument = async ({
// !: Need to write the fields onto the document as a hard copy
const pdfData = await getFile(documentData);
const certificateData =
document.team?.teamGlobalSettings?.includeSigningCertificate ?? true
? await getCertificatePdf({
documentId,
language: document.documentMeta?.language,
}).catch(() => null)
: null;
const documentLanguage = ZSupportedLanguageCodeSchema.parse(document.documentMeta?.language);
const certificate = await getCertificatePdf({ documentId, language: documentLanguage })
.then(async (doc) => PDFDocument.load(doc))
.catch(() => null);
const doc = await PDFDocument.load(pdfData);
@@ -115,9 +105,7 @@ export const sealDocument = async ({
flattenForm(doc);
flattenAnnotations(doc);
if (certificateData) {
const certificate = await PDFDocument.load(certificateData);
if (certificate) {
const certificatePages = await doc.copyPages(certificate, certificate.getPageIndices());
certificatePages.forEach((page) => {
@@ -17,7 +17,6 @@ import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
import { renderCustomEmailTemplate } from '../../utils/render-custom-email-template';
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
import { formatDocumentsPath } from '../../utils/teams';
export interface SendDocumentOptions {
documentId: number;
@@ -60,9 +59,7 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000';
let documentOwnerDownloadLink = `${NEXT_PUBLIC_WEBAPP_URL()}${formatDocumentsPath(
document.team?.url,
)}/${document.id}`;
let documentOwnerDownloadLink = `${NEXT_PUBLIC_WEBAPP_URL()}/documents/${document.id}`;
if (document.team?.url) {
documentOwnerDownloadLink = `${NEXT_PUBLIC_WEBAPP_URL()}/t/${document.team.url}/documents/${
@@ -177,10 +177,6 @@ export const signFieldWithToken = async ({
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) => {
const updatedField = await tx.field.update({
where: {
@@ -2,13 +2,12 @@ import { DateTime } from 'luxon';
import type { Browser } from 'playwright';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
import { type SupportedLanguageCodes, isValidLanguageCode } from '../../constants/i18n';
import type { SupportedLanguageCodes } from '../../constants/i18n';
import { encryptSecondaryData } from '../crypto/encrypt';
export type GetCertificatePdfOptions = {
documentId: number;
// eslint-disable-next-line @typescript-eslint/ban-types
language?: SupportedLanguageCodes | (string & {});
language?: SupportedLanguageCodes;
};
export const getCertificatePdf = async ({ documentId, language }: GetCertificatePdfOptions) => {
@@ -39,15 +38,15 @@ export const getCertificatePdf = async ({ documentId, language }: GetCertificate
const page = await browserContext.newPage();
const lang = isValidLanguageCode(language) ? language : 'en';
await page.context().addCookies([
{
name: 'language',
value: lang,
url: NEXT_PUBLIC_WEBAPP_URL(),
},
]);
if (language) {
await page.context().addCookies([
{
name: 'language',
value: language,
url: NEXT_PUBLIC_WEBAPP_URL(),
},
]);
}
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encryptedId}`, {
waitUntil: 'networkidle',
@@ -82,10 +82,7 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
const fieldX = pageWidth * (Number(field.positionX) / 100);
const fieldY = pageHeight * (Number(field.positionY) / 100);
const font = await pdf.embedFont(
isSignatureField ? fontCaveat : fontNoto,
isSignatureField ? { features: { calt: false } } : undefined,
);
const font = await pdf.embedFont(isSignatureField ? fontCaveat : fontNoto);
if (field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE) {
await pdf.embedFont(fontCaveat);
@@ -95,89 +92,45 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
.with(
{
type: P.union(FieldType.SIGNATURE, FieldType.FREE_SIGNATURE),
Signature: { signatureImageAsBase64: P.string },
},
async (field) => {
if (field.Signature?.signatureImageAsBase64) {
const image = await pdf.embedPng(field.Signature?.signatureImageAsBase64 ?? '');
const image = await pdf.embedPng(field.Signature?.signatureImageAsBase64 ?? '');
let imageWidth = image.width;
let imageHeight = image.height;
let imageWidth = image.width;
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;
imageHeight = imageHeight * scalingFactor;
imageWidth = imageWidth * scalingFactor;
imageHeight = imageHeight * scalingFactor;
let imageX = fieldX + (fieldWidth - imageWidth) / 2;
let imageY = fieldY + (fieldHeight - imageHeight) / 2;
let imageX = fieldX + (fieldWidth - imageWidth) / 2;
let imageY = fieldY + (fieldHeight - imageHeight) / 2;
// Invert the Y axis since PDFs use a bottom-left coordinate system
imageY = pageHeight - imageY - imageHeight;
// Invert the Y axis since PDFs use a bottom-left coordinate system
imageY = pageHeight - imageY - imageHeight;
if (pageRotationInDegrees !== 0) {
const adjustedPosition = adjustPositionForRotation(
pageWidth,
pageHeight,
imageX,
imageY,
pageRotationInDegrees,
);
if (pageRotationInDegrees !== 0) {
const adjustedPosition = adjustPositionForRotation(
pageWidth,
pageHeight,
imageX,
imageY,
pageRotationInDegrees,
);
imageX = adjustedPosition.xPos;
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),
});
imageX = adjustedPosition.xPos;
imageY = adjustedPosition.yPos;
}
page.drawImage(image, {
x: imageX,
y: imageY,
width: imageWidth,
height: imageHeight,
rotate: degrees(pageRotationInDegrees),
});
},
)
.with({ type: FieldType.CHECKBOX }, (field) => {
@@ -42,16 +42,7 @@ export const acceptTeamInvitation = async ({ userId, teamId }: AcceptTeamInvitat
});
if (teamMemberInvite.status === TeamMemberInviteStatus.ACCEPTED) {
const memberExists = await tx.teamMember.findFirst({
where: {
teamId: teamMemberInvite.teamId,
userId: user.id,
},
});
if (memberExists) {
return;
}
return;
}
const { team } = teamMemberInvite;
@@ -12,8 +12,6 @@ export type UpdateTeamDocumentSettingsOptions = {
documentVisibility: DocumentVisibility;
documentLanguage: SupportedLanguageCodes;
includeSenderDetails: boolean;
typedSignatureEnabled: boolean;
includeSigningCertificate: boolean;
};
};
@@ -22,13 +20,7 @@ export const updateTeamDocumentSettings = async ({
teamId,
settings,
}: UpdateTeamDocumentSettingsOptions) => {
const {
documentVisibility,
documentLanguage,
includeSenderDetails,
includeSigningCertificate,
typedSignatureEnabled,
} = settings;
const { documentVisibility, documentLanguage, includeSenderDetails } = settings;
const member = await prisma.teamMember.findFirst({
where: {
@@ -50,15 +42,11 @@ export const updateTeamDocumentSettings = async ({
documentVisibility,
documentLanguage,
includeSenderDetails,
typedSignatureEnabled,
includeSigningCertificate,
},
update: {
documentVisibility,
documentLanguage,
includeSenderDetails,
typedSignatureEnabled,
includeSigningCertificate,
},
});
};
@@ -4,6 +4,7 @@ import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { prisma } from '@documenso/prisma';
import { Prisma } from '@documenso/prisma/client';
import type { DocumentVisibility } from '@documenso/prisma/client';
export type UpdateTeamOptions = {
userId: number;
@@ -11,6 +12,8 @@ export type UpdateTeamOptions = {
data: {
name?: string;
url?: string;
documentVisibility?: DocumentVisibility;
includeSenderDetails?: boolean;
};
};
@@ -42,6 +45,18 @@ export const updateTeam = async ({ userId, teamId, data }: UpdateTeamOptions) =>
data: {
url: data.url,
name: data.name,
teamGlobalSettings: {
upsert: {
create: {
documentVisibility: data.documentVisibility,
includeSenderDetails: data.includeSenderDetails,
},
update: {
documentVisibility: data.documentVisibility,
includeSenderDetails: data.includeSenderDetails,
},
},
},
},
});
@@ -64,7 +64,6 @@ export type CreateDocumentFromTemplateOptions = {
signingOrder?: DocumentSigningOrder;
language?: SupportedLanguageCodes;
distributionMethod?: DocumentDistributionMethod;
typedSignatureEnabled?: boolean;
};
requestMetadata?: RequestMetadata;
};
@@ -147,7 +146,7 @@ export const createDocumentFromTemplate = async ({
return {
templateRecipientId: templateRecipient.id,
fields: templateRecipient.Field,
name: foundRecipient ? (foundRecipient.name ?? '') : templateRecipient.name,
name: foundRecipient ? foundRecipient.name ?? '' : templateRecipient.name,
email: foundRecipient ? foundRecipient.email : templateRecipient.email,
role: templateRecipient.role,
signingOrder: foundRecipient?.signingOrder ?? templateRecipient.signingOrder,
@@ -197,8 +196,6 @@ export const createDocumentFromTemplate = async ({
override?.language ||
template.templateMeta?.language ||
template.team?.teamGlobalSettings?.documentLanguage,
typedSignatureEnabled:
override?.typedSignatureEnabled ?? template.templateMeta?.typedSignatureEnabled,
},
},
Recipient: {
+3 -3
View File
@@ -3,7 +3,7 @@ import { hash } from '@node-rs/bcrypt';
import { getStripeCustomerByUser } from '@documenso/ee/server-only/stripe/get-customer';
import { updateSubscriptionItemQuantity } from '@documenso/ee/server-only/stripe/update-subscription-item-quantity';
import { prisma } from '@documenso/prisma';
import { IdentityProvider, TeamMemberInviteStatus } from '@documenso/prisma/client';
import { IdentityProvider, Prisma, TeamMemberInviteStatus } from '@documenso/prisma/client';
import { IS_BILLING_ENABLED } from '../../constants/app';
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({
where: {
status: TeamMemberInviteStatus.ACCEPTED,
email: {
equals: email,
mode: 'insensitive',
mode: Prisma.QueryMode.insensitive,
},
status: TeamMemberInviteStatus.ACCEPTED,
},
});
+73 -151
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: de\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-11-20 11:56\n"
"PO-Revision-Date: 2024-11-12 05:45\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -79,7 +79,7 @@ msgstr "{inviterName} hat das Dokument {documentName} storniert, du musst es nic
msgid "{inviterName} has cancelled the document<0/>\"{documentName}\""
msgstr "{inviterName} hat das Dokument<0/>\"{documentName}\" storniert"
#: packages/email/template-components/template-document-invite.tsx:75
#: packages/email/template-components/template-document-invite.tsx:65
msgid "{inviterName} has invited you to {0}<0/>\"{documentName}\""
msgstr "{inviterName} hat dich eingeladen, {0}<0/>\"{documentName}\""
@@ -99,7 +99,7 @@ msgstr "{inviterName} hat dich aus dem Dokument {documentName} entfernt."
msgid "{inviterName} has removed you from the document<0/>\"{documentName}\""
msgstr "{inviterName} hat dich aus dem Dokument<0/>\"{documentName}\" entfernt"
#: packages/email/template-components/template-document-invite.tsx:63
#: packages/email/template-components/template-document-invite.tsx:53
msgid "{inviterName} on behalf of {teamName} has invited you to {0}"
msgstr "{inviterName} im Namen von {teamName} hat Sie eingeladen, {0}"
@@ -151,11 +151,11 @@ msgstr "{prefix} hat ein Feld entfernt"
msgid "{prefix} removed a recipient"
msgstr "{prefix} hat einen Empfänger entfernt"
#: packages/lib/utils/document-audit-logs.ts:365
#: packages/lib/utils/document-audit-logs.ts:355
msgid "{prefix} resent an email to {0}"
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:356
msgid "{prefix} sent an email to {0}"
msgstr "{prefix} hat eine E-Mail an {0} gesendet"
@@ -207,15 +207,7 @@ msgstr "{prefix} hat die Sichtbarkeit des Dokuments aktualisiert"
msgid "{recipientName} {action} a document by using one of your direct links"
msgstr "{recipientName} {action} ein Dokument, indem Sie einen Ihrer direkten Links verwenden"
#: packages/email/templates/document-rejected.tsx:27
msgid "{recipientName} has rejected the document '{documentName}'"
msgstr "{recipientName} hat das Dokument '{documentName}' abgelehnt"
#: packages/email/template-components/template-document-rejected.tsx:25
msgid "{signerName} has rejected the document \"{documentName}\"."
msgstr "{signerName} hat das Dokument \"{documentName}\" abgelehnt."
#: packages/email/template-components/template-document-invite.tsx:68
#: packages/email/template-components/template-document-invite.tsx:58
msgid "{teamName} has invited you to {0}"
msgstr "{teamName} hat Sie eingeladen, {0}"
@@ -239,10 +231,6 @@ msgstr "{userName} hat das Dokument in CC gesetzt"
msgid "{userName} completed their task"
msgstr "{userName} hat ihre Aufgabe abgeschlossen"
#: packages/lib/utils/document-audit-logs.ts:355
msgid "{userName} rejected the document"
msgstr "{userName} hat das Dokument abgelehnt"
#: packages/lib/utils/document-audit-logs.ts:341
msgid "{userName} signed the document"
msgstr "{userName} hat das Dokument unterschrieben"
@@ -410,19 +398,19 @@ msgstr "Weitere Option hinzufügen"
msgid "Add another value"
msgstr "Weiteren Wert hinzufügen"
#: packages/ui/primitives/document-flow/add-signers.tsx:691
#: packages/ui/primitives/document-flow/add-signers.tsx:662
msgid "Add myself"
msgstr "Mich selbst hinzufügen"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:645
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:637
msgid "Add Myself"
msgstr "Mich hinzufügen"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:631
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:623
msgid "Add Placeholder Recipient"
msgstr "Platzhalterempfänger hinzufügen"
#: packages/ui/primitives/document-flow/add-signers.tsx:680
#: packages/ui/primitives/document-flow/add-signers.tsx:651
msgid "Add Signer"
msgstr "Unterzeichner hinzufügen"
@@ -443,8 +431,8 @@ msgstr "Admin"
msgid "Advanced Options"
msgstr "Erweiterte Optionen"
#: packages/ui/primitives/document-flow/add-fields.tsx:576
#: packages/ui/primitives/template-flow/add-template-fields.tsx:414
#: packages/ui/primitives/document-flow/add-fields.tsx:573
#: packages/ui/primitives/template-flow/add-template-fields.tsx:406
msgid "Advanced settings"
msgstr "Erweiterte Einstellungen"
@@ -472,7 +460,7 @@ msgstr "Ein Fehler ist beim Laden des Dokuments aufgetreten."
msgid "Approve"
msgstr "Genehmigen"
#: packages/email/template-components/template-document-invite.tsx:106
#: packages/email/template-components/template-document-invite.tsx:89
msgid "Approve Document"
msgstr "Dokument genehmigen"
@@ -500,11 +488,11 @@ msgstr "Genehmigung"
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:"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:383
#: packages/ui/primitives/signature-pad/signature-pad.tsx:377
msgid "Black"
msgstr "Schwarz"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:397
#: packages/ui/primitives/signature-pad/signature-pad.tsx:391
msgid "Blue"
msgstr "Blau"
@@ -550,10 +538,6 @@ msgstr "Ccers"
msgid "Character Limit"
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
msgid "Checkbox values"
msgstr "Checkbox-Werte"
@@ -562,7 +546,7 @@ msgstr "Checkbox-Werte"
msgid "Clear filters"
msgstr "Filter löschen"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:417
#: packages/ui/primitives/signature-pad/signature-pad.tsx:411
msgid "Clear Signature"
msgstr "Unterschrift löschen"
@@ -589,8 +573,8 @@ msgstr "Abgeschlossenes Dokument"
msgid "Configure Direct Recipient"
msgstr "Direkten Empfänger konfigurieren"
#: packages/ui/primitives/document-flow/add-fields.tsx:577
#: packages/ui/primitives/template-flow/add-template-fields.tsx:415
#: packages/ui/primitives/document-flow/add-fields.tsx:574
#: packages/ui/primitives/template-flow/add-template-fields.tsx:407
msgid "Configure the {0} field"
msgstr "Konfigurieren Sie das Feld {0}"
@@ -606,7 +590,7 @@ msgstr "Fortsetzen"
#~ msgid "Continue by {0} the document."
#~ msgstr "Continue by {0} the document."
#: packages/email/template-components/template-document-invite.tsx:86
#: packages/email/template-components/template-document-invite.tsx:76
msgid "Continue by approving the document."
msgstr "Fahre fort, indem du das Dokument genehmigst."
@@ -614,11 +598,11 @@ msgstr "Fahre fort, indem du das Dokument genehmigst."
msgid "Continue by downloading the document."
msgstr "Fahre fort, indem du das Dokument herunterlädst."
#: packages/email/template-components/template-document-invite.tsx:84
#: packages/email/template-components/template-document-invite.tsx:74
msgid "Continue by signing the document."
msgstr "Fahre fort, indem du das Dokument signierst."
#: packages/email/template-components/template-document-invite.tsx:85
#: packages/email/template-components/template-document-invite.tsx:75
msgid "Continue by viewing the document."
msgstr "Fahre fort, indem du das Dokument ansiehst."
@@ -651,9 +635,9 @@ msgstr "Konto erstellen"
msgid "Custom Text"
msgstr "Benutzerdefinierter Text"
#: packages/ui/primitives/document-flow/add-fields.tsx:934
#: packages/ui/primitives/document-flow/add-fields.tsx:927
#: packages/ui/primitives/document-flow/types.ts:53
#: packages/ui/primitives/template-flow/add-template-fields.tsx:729
#: packages/ui/primitives/template-flow/add-template-fields.tsx:690
msgid "Date"
msgstr "Datum"
@@ -670,18 +654,10 @@ msgstr "Ablehnen"
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>"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:578
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:570
msgid "Direct link receiver"
msgstr "Empfänger des direkten Links"
#: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:149
msgid "Document \"{0}\" - Rejected by {1}"
msgstr "Dokument \"{0}\" - Abgelehnt von {1}"
#: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:109
msgid "Document \"{0}\" - Rejection Confirmed"
msgstr "Dokument \"{0}\" - Ablehnung Bestätigt"
#: packages/ui/components/document/document-global-auth-access-select.tsx:62
#: packages/ui/primitives/document-flow/add-settings.tsx:216
#: packages/ui/primitives/template-flow/add-template-settings.tsx:202
@@ -697,8 +673,8 @@ msgstr "Die Authentifizierung für den Dokumentenzugriff wurde aktualisiert"
msgid "Document Cancelled"
msgstr "Dokument storniert"
#: packages/lib/utils/document-audit-logs.ts:369
#: packages/lib/utils/document-audit-logs.ts:370
#: packages/lib/utils/document-audit-logs.ts:359
#: packages/lib/utils/document-audit-logs.ts:360
msgid "Document completed"
msgstr "Dokument abgeschlossen"
@@ -752,13 +728,6 @@ msgstr "Dokument geöffnet"
msgid "Document pending email"
msgstr "E-Mail über ausstehende Dokumente"
#: packages/email/template-components/template-document-rejected.tsx:21
msgid "Document Rejected"
msgstr "Dokument Abgelehnt"
#~ msgid "Document Rejection Confirmed"
#~ msgstr "Document Rejection Confirmed"
#: packages/lib/utils/document-audit-logs.ts:330
msgid "Document sent"
msgstr "Dokument gesendet"
@@ -792,8 +761,8 @@ msgstr "Entwurf"
msgid "Drag & drop your PDF here."
msgstr "Ziehen Sie Ihr PDF hierher."
#: packages/ui/primitives/document-flow/add-fields.tsx:1065
#: packages/ui/primitives/template-flow/add-template-fields.tsx:860
#: packages/ui/primitives/document-flow/add-fields.tsx:1058
#: packages/ui/primitives/template-flow/add-template-fields.tsx:820
msgid "Dropdown"
msgstr "Dropdown"
@@ -802,14 +771,14 @@ msgid "Dropdown options"
msgstr "Dropdown-Optionen"
#: packages/lib/constants/document.ts:28
#: packages/ui/primitives/document-flow/add-fields.tsx:882
#: packages/ui/primitives/document-flow/add-fields.tsx:875
#: 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:519
#: packages/ui/primitives/document-flow/add-signers.tsx:500
#: packages/ui/primitives/document-flow/add-signers.tsx:507
#: packages/ui/primitives/document-flow/types.ts:54
#: packages/ui/primitives/template-flow/add-template-fields.tsx:677
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:471
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:478
#: packages/ui/primitives/template-flow/add-template-fields.tsx:638
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:463
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:470
msgid "Email"
msgstr "E-Mail"
@@ -821,15 +790,15 @@ msgstr "E-Mail ist erforderlich"
msgid "Email Options"
msgstr "E-Mail-Optionen"
#: packages/lib/utils/document-audit-logs.ts:363
#: packages/lib/utils/document-audit-logs.ts:353
msgid "Email resent"
msgstr "E-Mail erneut gesendet"
#: packages/lib/utils/document-audit-logs.ts:363
#: packages/lib/utils/document-audit-logs.ts:353
msgid "Email sent"
msgstr "E-Mail gesendet"
#: packages/ui/primitives/document-flow/add-fields.tsx:1130
#: packages/ui/primitives/document-flow/add-fields.tsx:1123
msgid "Empty field"
msgstr "Leeres Feld"
@@ -838,12 +807,11 @@ msgid "Enable Direct Link Signing"
msgstr "Direktlink-Signierung aktivieren"
#: packages/ui/primitives/document-flow/add-signers.tsx:401
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:370
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:362
msgid "Enable signing order"
msgstr "Aktiviere die Signaturreihenfolge"
#: packages/ui/primitives/document-flow/add-fields.tsx:802
#: packages/ui/primitives/template-flow/add-template-fields.tsx:597
#: packages/ui/primitives/document-flow/add-fields.tsx:795
msgid "Enable Typed Signatures"
msgstr "Aktivieren Sie getippte Unterschriften"
@@ -931,13 +899,10 @@ msgstr "Globale Empfängerauthentifizierung"
msgid "Go Back"
msgstr "Zurück"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:404
#: packages/ui/primitives/signature-pad/signature-pad.tsx:398
msgid "Green"
msgstr "Grün"
#~ msgid "Hello {recipientName},"
#~ msgstr "Hello {recipientName},"
#: packages/email/templates/reset-password.tsx:56
msgid "Hi, {userName} <0>({userEmail})</0>"
msgstr "Hallo, {userName} <0>({userEmail})</0>"
@@ -1021,14 +986,14 @@ msgstr "Nachricht <0>(Optional)</0>"
msgid "Min"
msgstr "Min"
#: packages/ui/primitives/document-flow/add-fields.tsx:908
#: packages/ui/primitives/document-flow/add-fields.tsx:901
#: 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:556
#: packages/ui/primitives/document-flow/add-signers.tsx:535
#: packages/ui/primitives/document-flow/add-signers.tsx:541
#: packages/ui/primitives/document-flow/types.ts:55
#: packages/ui/primitives/template-flow/add-template-fields.tsx:703
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:506
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:512
#: packages/ui/primitives/template-flow/add-template-fields.tsx:664
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:498
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:504
msgid "Name"
msgstr "Name"
@@ -1044,8 +1009,8 @@ msgstr "Muss unterzeichnen"
msgid "Needs to view"
msgstr "Muss sehen"
#: packages/ui/primitives/document-flow/add-fields.tsx:693
#: packages/ui/primitives/template-flow/add-template-fields.tsx:516
#: packages/ui/primitives/document-flow/add-fields.tsx:686
#: packages/ui/primitives/template-flow/add-template-fields.tsx:504
msgid "No recipient matching this description was found."
msgstr "Kein passender Empfänger mit dieser Beschreibung gefunden."
@@ -1053,8 +1018,8 @@ msgstr "Kein passender Empfänger mit dieser Beschreibung gefunden."
msgid "No recipients"
msgstr "Keine Empfänger"
#: packages/ui/primitives/document-flow/add-fields.tsx:708
#: packages/ui/primitives/template-flow/add-template-fields.tsx:531
#: packages/ui/primitives/document-flow/add-fields.tsx:701
#: packages/ui/primitives/template-flow/add-template-fields.tsx:519
msgid "No recipients with this role"
msgstr "Keine Empfänger mit dieser Rolle"
@@ -1082,9 +1047,9 @@ msgstr "Kein Wert gefunden."
msgid "None"
msgstr "Keine"
#: packages/ui/primitives/document-flow/add-fields.tsx:986
#: packages/ui/primitives/document-flow/add-fields.tsx:979
#: packages/ui/primitives/document-flow/types.ts:56
#: packages/ui/primitives/template-flow/add-template-fields.tsx:781
#: packages/ui/primitives/template-flow/add-template-fields.tsx:742
msgid "Number"
msgstr "Nummer"
@@ -1142,7 +1107,7 @@ msgstr "Wählen Sie eine Zahl"
msgid "Placeholder"
msgstr "Platzhalter"
#: packages/email/template-components/template-document-invite.tsx:56
#: packages/email/template-components/template-document-invite.tsx:46
msgid "Please {0} your document<0/>\"{documentName}\""
msgstr "Bitte {0} dein Dokument<0/>\"{documentName}\""
@@ -1175,7 +1140,7 @@ msgstr "Bitte bestätige deine E-Mail-Adresse"
msgid "Please try again or contact our support."
msgstr "Bitte versuchen Sie es erneut oder kontaktieren Sie unseren Support."
#: packages/ui/primitives/document-flow/types.ts:57
#: packages/ui/primitives/template-flow/add-template-fields.tsx:768
msgid "Radio"
msgstr "Radio"
@@ -1191,16 +1156,11 @@ msgstr "Radio-Werte"
msgid "Read only"
msgstr "Nur lesen"
#: packages/email/template-components/template-document-rejected.tsx:32
msgid "Reason for rejection: {rejectionReason}"
msgstr "Grund für die Ablehnung: {rejectionReason}"
#: packages/ui/components/recipient/recipient-role-select.tsx:95
msgid "Receives copy"
msgstr "Erhält Kopie"
#: packages/lib/utils/document-audit-logs.ts:338
#: packages/lib/utils/document-audit-logs.ts:353
msgid "Recipient"
msgstr "Empfänger"
@@ -1218,7 +1178,7 @@ msgstr "E-Mail des entfernten Empfängers"
msgid "Recipient signing request email"
msgstr "E-Mail zur Unterzeichnungsanfrage des Empfängers"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:390
#: packages/ui/primitives/signature-pad/signature-pad.tsx:384
msgid "Red"
msgstr "Rot"
@@ -1227,18 +1187,6 @@ msgstr "Rot"
msgid "Redirect URL"
msgstr "Weiterleitungs-URL"
#: packages/email/template-components/template-document-invite.tsx:96
msgid "Reject Document"
msgstr "Dokument Ablehnen"
#: packages/email/template-components/template-document-rejection-confirmed.tsx:22
msgid "Rejection Confirmed"
msgstr "Ablehnung Bestätigt"
#: packages/email/template-components/template-document-rejection-confirmed.tsx:34
msgid "Rejection reason: {reason}"
msgstr "Ablehnungsgrund: {reason}"
#: packages/lib/server-only/document/resend-document.tsx:192
msgid "Reminder: {0}"
msgstr "Erinnerung: {0}"
@@ -1255,7 +1203,7 @@ msgstr "Erinnerung: Bitte {recipientActionVerb} dieses Dokument"
msgid "Reminder: Please {recipientActionVerb} your document"
msgstr "Erinnerung: Bitte {recipientActionVerb} dein Dokument"
#: packages/ui/primitives/document-flow/add-fields.tsx:1117
#: packages/ui/primitives/document-flow/add-fields.tsx:1110
msgid "Remove"
msgstr "Entfernen"
@@ -1287,7 +1235,7 @@ msgstr "Zeilen pro Seite"
msgid "Save"
msgstr "Speichern"
#: packages/ui/primitives/template-flow/add-template-fields.tsx:893
#: packages/ui/primitives/template-flow/add-template-fields.tsx:854
msgid "Save Template"
msgstr "Vorlage speichern"
@@ -1296,7 +1244,6 @@ msgid "Search languages..."
msgstr "Sprachen suchen..."
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:115
#: packages/ui/primitives/document-flow/types.ts:59
msgid "Select"
msgstr "Auswählen"
@@ -1359,8 +1306,8 @@ msgstr "Link teilen"
msgid "Share your signing experience!"
msgstr "Teilen Sie Ihre Unterzeichnungserfahrung!"
#: packages/ui/primitives/document-flow/add-signers.tsx:709
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:663
#: packages/ui/primitives/document-flow/add-signers.tsx:680
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:655
msgid "Show advanced settings"
msgstr "Erweiterte Einstellungen anzeigen"
@@ -1368,7 +1315,7 @@ msgstr "Erweiterte Einstellungen anzeigen"
msgid "Sign"
msgstr "Unterschreiben"
#: packages/email/template-components/template-document-invite.tsx:104
#: packages/email/template-components/template-document-invite.tsx:87
msgid "Sign Document"
msgstr "Dokument signieren"
@@ -1376,11 +1323,11 @@ msgstr "Dokument signieren"
msgid "Sign In"
msgstr "Anmelden"
#: packages/ui/primitives/document-flow/add-fields.tsx:830
#: packages/ui/primitives/document-flow/add-fields.tsx:823
#: packages/ui/primitives/document-flow/add-signature.tsx:323
#: packages/ui/primitives/document-flow/field-icon.tsx:52
#: packages/ui/primitives/document-flow/types.ts:49
#: packages/ui/primitives/template-flow/add-template-fields.tsx:625
#: packages/ui/primitives/template-flow/add-template-fields.tsx:586
msgid "Signature"
msgstr "Unterschrift"
@@ -1404,8 +1351,8 @@ msgstr "Unterzeichner müssen eindeutige E-Mails haben"
msgid "Signing"
msgstr "Unterzeichnung"
#: packages/lib/server-only/document/send-completed-email.ts:114
#: packages/lib/server-only/document/send-completed-email.ts:194
#: packages/lib/server-only/document/send-completed-email.ts:111
#: packages/lib/server-only/document/send-completed-email.ts:191
msgid "Signing Complete!"
msgstr "Unterzeichnung abgeschlossen!"
@@ -1463,9 +1410,9 @@ msgstr "Team-E-Mail für {teamName} auf Documenso entfernt"
msgid "Template title"
msgstr "Vorlagentitel"
#: packages/ui/primitives/document-flow/add-fields.tsx:960
#: packages/ui/primitives/document-flow/add-fields.tsx:953
#: packages/ui/primitives/document-flow/types.ts:52
#: packages/ui/primitives/template-flow/add-template-fields.tsx:755
#: packages/ui/primitives/template-flow/add-template-fields.tsx:716
msgid "Text"
msgstr "Text"
@@ -1485,13 +1432,6 @@ msgstr "Die Authentifizierung, die erforderlich ist, damit Empfänger das Signat
msgid "The authentication required for recipients to view the document."
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
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 "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
msgid "The document's name"
msgstr "Der Name des Dokuments"
@@ -1560,7 +1500,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."
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:764
#: packages/ui/primitives/document-flow/add-fields.tsx:757
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."
@@ -1572,13 +1512,6 @@ msgstr "Dieses Dokument ist durch ein Passwort geschützt. Bitte geben Sie das P
msgid "This document was sent using <0>Documenso.</0>"
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
msgid "This email confirms that you have rejected the document <0>\"{documentName}\"</0> sent by {documentOwnerName}."
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
msgid "This email is sent to the recipient if they are removed from a pending document."
msgstr "Diese E-Mail wird an den Empfänger gesendet, wenn er von einem ausstehenden Dokument entfernt wird."
@@ -1591,7 +1524,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."
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:581
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:573
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."
@@ -1599,7 +1532,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."
msgstr "So wird das Dokument die Empfänger erreichen, sobald es zum Unterschreiben bereit ist."
#: packages/ui/primitives/document-flow/add-fields.tsx:1097
#: packages/ui/primitives/document-flow/add-fields.tsx:1090
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."
@@ -1628,8 +1561,8 @@ msgstr "Zeitzone"
msgid "Title"
msgstr "Titel"
#: packages/ui/primitives/document-flow/add-fields.tsx:1080
#: packages/ui/primitives/template-flow/add-template-fields.tsx:873
#: packages/ui/primitives/document-flow/add-fields.tsx:1073
#: packages/ui/primitives/template-flow/add-template-fields.tsx:834
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."
@@ -1675,8 +1608,7 @@ msgstr "Sehen Sie sich alle Dokumente an, die an diese E-Mail-Adresse gesendet w
msgid "View document"
msgstr "Dokument anzeigen"
#: packages/email/template-components/template-document-invite.tsx:105
#: packages/email/template-components/template-document-rejected.tsx:44
#: packages/email/template-components/template-document-invite.tsx:88
#: packages/ui/primitives/document-flow/add-subject.tsx:90
#: packages/ui/primitives/document-flow/add-subject.tsx:91
msgid "View Document"
@@ -1754,10 +1686,6 @@ msgstr "Sie können den Zugriff jederzeit in Ihren Teameinstellungen auf Documen
msgid "You can use the following variables in your message:"
msgstr "Sie können die folgenden Variablen in Ihrer Nachricht verwenden:"
#: packages/email/template-components/template-document-rejected.tsx:37
msgid "You can view the document and its status by clicking the button below."
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
msgid "You cannot upload documents at this time."
msgstr "Sie können derzeit keine Dokumente hochladen."
@@ -1791,13 +1719,6 @@ msgstr "Du hast das Dokument {0} initiiert, das erfordert, dass du {recipientAct
msgid "You have reached your document limit."
msgstr "Sie haben Ihr Dokumentenlimit erreicht."
#: packages/email/templates/document-rejection-confirmed.tsx:27
msgid "You have rejected the document '{documentName}'"
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
msgid "You have signed “{documentName}”"
msgstr "Du hast „{documentName}“ unterzeichnet"
@@ -1814,3 +1735,4 @@ msgstr "Dein Passwort wurde aktualisiert."
#: packages/email/templates/team-delete.tsx:32
msgid "Your team has been deleted"
msgstr "Dein Team wurde gelöscht"
+2 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: de\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-11-20 11:56\n"
"PO-Revision-Date: 2024-11-12 05:45\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -602,3 +602,4 @@ msgstr "Sie können Documenso kostenlos selbst hosten oder unsere sofort einsatz
#: apps/marketing/src/components/(marketing)/carousel.tsx:272
msgid "Your browser does not support the video tag."
msgstr "Ihr Browser unterstützt das Video-Tag nicht."
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+71 -150
View File
@@ -74,7 +74,7 @@ msgstr "{inviterName} has cancelled the document {documentName}, you don't need
msgid "{inviterName} has cancelled the document<0/>\"{documentName}\""
msgstr "{inviterName} has cancelled the document<0/>\"{documentName}\""
#: packages/email/template-components/template-document-invite.tsx:75
#: packages/email/template-components/template-document-invite.tsx:65
msgid "{inviterName} has invited you to {0}<0/>\"{documentName}\""
msgstr "{inviterName} has invited you to {0}<0/>\"{documentName}\""
@@ -94,7 +94,7 @@ msgstr "{inviterName} has removed you from the document {documentName}."
msgid "{inviterName} has removed you from the document<0/>\"{documentName}\""
msgstr "{inviterName} has removed you from the document<0/>\"{documentName}\""
#: packages/email/template-components/template-document-invite.tsx:63
#: packages/email/template-components/template-document-invite.tsx:53
msgid "{inviterName} on behalf of {teamName} has invited you to {0}"
msgstr "{inviterName} on behalf of {teamName} has invited you to {0}"
@@ -146,11 +146,11 @@ msgstr "{prefix} removed a field"
msgid "{prefix} removed a recipient"
msgstr "{prefix} removed a recipient"
#: packages/lib/utils/document-audit-logs.ts:365
#: packages/lib/utils/document-audit-logs.ts:355
msgid "{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:356
msgid "{prefix} sent an email to {0}"
msgstr "{prefix} sent an email to {0}"
@@ -202,15 +202,7 @@ msgstr "{prefix} updated the document visibility"
msgid "{recipientName} {action} a document by using one of your direct links"
msgstr "{recipientName} {action} a document by using one of your direct links"
#: packages/email/templates/document-rejected.tsx:27
msgid "{recipientName} has rejected the document '{documentName}'"
msgstr "{recipientName} has rejected the document '{documentName}'"
#: packages/email/template-components/template-document-rejected.tsx:25
msgid "{signerName} has rejected the document \"{documentName}\"."
msgstr "{signerName} has rejected the document \"{documentName}\"."
#: packages/email/template-components/template-document-invite.tsx:68
#: packages/email/template-components/template-document-invite.tsx:58
msgid "{teamName} has invited you to {0}"
msgstr "{teamName} has invited you to {0}"
@@ -234,10 +226,6 @@ msgstr "{userName} CC'd the document"
msgid "{userName} completed their task"
msgstr "{userName} completed their task"
#: packages/lib/utils/document-audit-logs.ts:355
msgid "{userName} rejected the document"
msgstr "{userName} rejected the document"
#: packages/lib/utils/document-audit-logs.ts:341
msgid "{userName} signed the document"
msgstr "{userName} signed the document"
@@ -405,19 +393,19 @@ msgstr "Add another option"
msgid "Add another value"
msgstr "Add another value"
#: packages/ui/primitives/document-flow/add-signers.tsx:691
#: packages/ui/primitives/document-flow/add-signers.tsx:662
msgid "Add myself"
msgstr "Add myself"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:645
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:637
msgid "Add Myself"
msgstr "Add Myself"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:631
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:623
msgid "Add Placeholder Recipient"
msgstr "Add Placeholder Recipient"
#: packages/ui/primitives/document-flow/add-signers.tsx:680
#: packages/ui/primitives/document-flow/add-signers.tsx:651
msgid "Add Signer"
msgstr "Add Signer"
@@ -438,8 +426,8 @@ msgstr "Admin"
msgid "Advanced Options"
msgstr "Advanced Options"
#: packages/ui/primitives/document-flow/add-fields.tsx:576
#: packages/ui/primitives/template-flow/add-template-fields.tsx:414
#: packages/ui/primitives/document-flow/add-fields.tsx:573
#: packages/ui/primitives/template-flow/add-template-fields.tsx:406
msgid "Advanced settings"
msgstr "Advanced settings"
@@ -467,7 +455,7 @@ msgstr "An error occurred while loading the document."
msgid "Approve"
msgstr "Approve"
#: packages/email/template-components/template-document-invite.tsx:106
#: packages/email/template-components/template-document-invite.tsx:89
msgid "Approve Document"
msgstr "Approve Document"
@@ -495,11 +483,11 @@ msgstr "Approving"
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:"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:383
#: packages/ui/primitives/signature-pad/signature-pad.tsx:377
msgid "Black"
msgstr "Black"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:397
#: packages/ui/primitives/signature-pad/signature-pad.tsx:391
msgid "Blue"
msgstr "Blue"
@@ -545,10 +533,6 @@ msgstr "Ccers"
msgid "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
msgid "Checkbox values"
msgstr "Checkbox values"
@@ -557,7 +541,7 @@ msgstr "Checkbox values"
msgid "Clear filters"
msgstr "Clear filters"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:417
#: packages/ui/primitives/signature-pad/signature-pad.tsx:411
msgid "Clear Signature"
msgstr "Clear Signature"
@@ -584,8 +568,8 @@ msgstr "Completed Document"
msgid "Configure Direct Recipient"
msgstr "Configure Direct Recipient"
#: packages/ui/primitives/document-flow/add-fields.tsx:577
#: packages/ui/primitives/template-flow/add-template-fields.tsx:415
#: packages/ui/primitives/document-flow/add-fields.tsx:574
#: packages/ui/primitives/template-flow/add-template-fields.tsx:407
msgid "Configure the {0} field"
msgstr "Configure the {0} field"
@@ -601,7 +585,7 @@ msgstr "Continue"
#~ msgid "Continue by {0} the document."
#~ msgstr "Continue by {0} the document."
#: packages/email/template-components/template-document-invite.tsx:86
#: packages/email/template-components/template-document-invite.tsx:76
msgid "Continue by approving the document."
msgstr "Continue by approving the document."
@@ -609,11 +593,11 @@ msgstr "Continue by approving the document."
msgid "Continue by downloading the document."
msgstr "Continue by downloading the document."
#: packages/email/template-components/template-document-invite.tsx:84
#: packages/email/template-components/template-document-invite.tsx:74
msgid "Continue by signing the document."
msgstr "Continue by signing the document."
#: packages/email/template-components/template-document-invite.tsx:85
#: packages/email/template-components/template-document-invite.tsx:75
msgid "Continue by viewing the document."
msgstr "Continue by viewing the document."
@@ -646,9 +630,9 @@ msgstr "Create account"
msgid "Custom Text"
msgstr "Custom Text"
#: packages/ui/primitives/document-flow/add-fields.tsx:934
#: packages/ui/primitives/document-flow/add-fields.tsx:927
#: packages/ui/primitives/document-flow/types.ts:53
#: packages/ui/primitives/template-flow/add-template-fields.tsx:729
#: packages/ui/primitives/template-flow/add-template-fields.tsx:690
msgid "Date"
msgstr "Date"
@@ -665,18 +649,10 @@ msgstr "Decline"
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>"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:578
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:570
msgid "Direct link receiver"
msgstr "Direct link receiver"
#: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:149
msgid "Document \"{0}\" - Rejected by {1}"
msgstr "Document \"{0}\" - Rejected by {1}"
#: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:109
msgid "Document \"{0}\" - Rejection Confirmed"
msgstr "Document \"{0}\" - Rejection Confirmed"
#: packages/ui/components/document/document-global-auth-access-select.tsx:62
#: packages/ui/primitives/document-flow/add-settings.tsx:216
#: packages/ui/primitives/template-flow/add-template-settings.tsx:202
@@ -692,8 +668,8 @@ msgstr "Document access auth updated"
msgid "Document Cancelled"
msgstr "Document Cancelled"
#: packages/lib/utils/document-audit-logs.ts:369
#: packages/lib/utils/document-audit-logs.ts:370
#: packages/lib/utils/document-audit-logs.ts:359
#: packages/lib/utils/document-audit-logs.ts:360
msgid "Document completed"
msgstr "Document completed"
@@ -747,13 +723,6 @@ msgstr "Document opened"
msgid "Document pending email"
msgstr "Document pending email"
#: packages/email/template-components/template-document-rejected.tsx:21
msgid "Document Rejected"
msgstr "Document Rejected"
#~ msgid "Document Rejection Confirmed"
#~ msgstr "Document Rejection Confirmed"
#: packages/lib/utils/document-audit-logs.ts:330
msgid "Document sent"
msgstr "Document sent"
@@ -787,8 +756,8 @@ msgstr "Draft"
msgid "Drag & drop your PDF here."
msgstr "Drag & drop your PDF here."
#: packages/ui/primitives/document-flow/add-fields.tsx:1065
#: packages/ui/primitives/template-flow/add-template-fields.tsx:860
#: packages/ui/primitives/document-flow/add-fields.tsx:1058
#: packages/ui/primitives/template-flow/add-template-fields.tsx:820
msgid "Dropdown"
msgstr "Dropdown"
@@ -797,14 +766,14 @@ msgid "Dropdown options"
msgstr "Dropdown options"
#: packages/lib/constants/document.ts:28
#: packages/ui/primitives/document-flow/add-fields.tsx:882
#: packages/ui/primitives/document-flow/add-fields.tsx:875
#: 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:519
#: packages/ui/primitives/document-flow/add-signers.tsx:500
#: packages/ui/primitives/document-flow/add-signers.tsx:507
#: packages/ui/primitives/document-flow/types.ts:54
#: packages/ui/primitives/template-flow/add-template-fields.tsx:677
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:471
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:478
#: packages/ui/primitives/template-flow/add-template-fields.tsx:638
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:463
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:470
msgid "Email"
msgstr "Email"
@@ -816,15 +785,15 @@ msgstr "Email is required"
msgid "Email Options"
msgstr "Email Options"
#: packages/lib/utils/document-audit-logs.ts:363
#: packages/lib/utils/document-audit-logs.ts:353
msgid "Email resent"
msgstr "Email resent"
#: packages/lib/utils/document-audit-logs.ts:363
#: packages/lib/utils/document-audit-logs.ts:353
msgid "Email sent"
msgstr "Email sent"
#: packages/ui/primitives/document-flow/add-fields.tsx:1130
#: packages/ui/primitives/document-flow/add-fields.tsx:1123
msgid "Empty field"
msgstr "Empty field"
@@ -833,12 +802,11 @@ msgid "Enable Direct Link Signing"
msgstr "Enable Direct Link Signing"
#: packages/ui/primitives/document-flow/add-signers.tsx:401
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:370
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:362
msgid "Enable signing order"
msgstr "Enable signing order"
#: packages/ui/primitives/document-flow/add-fields.tsx:802
#: packages/ui/primitives/template-flow/add-template-fields.tsx:597
#: packages/ui/primitives/document-flow/add-fields.tsx:795
msgid "Enable Typed Signatures"
msgstr "Enable Typed Signatures"
@@ -926,13 +894,10 @@ msgstr "Global recipient action authentication"
msgid "Go Back"
msgstr "Go Back"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:404
#: packages/ui/primitives/signature-pad/signature-pad.tsx:398
msgid "Green"
msgstr "Green"
#~ msgid "Hello {recipientName},"
#~ msgstr "Hello {recipientName},"
#: packages/email/templates/reset-password.tsx:56
msgid "Hi, {userName} <0>({userEmail})</0>"
msgstr "Hi, {userName} <0>({userEmail})</0>"
@@ -1016,14 +981,14 @@ msgstr "Message <0>(Optional)</0>"
msgid "Min"
msgstr "Min"
#: packages/ui/primitives/document-flow/add-fields.tsx:908
#: packages/ui/primitives/document-flow/add-fields.tsx:901
#: 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:556
#: packages/ui/primitives/document-flow/add-signers.tsx:535
#: packages/ui/primitives/document-flow/add-signers.tsx:541
#: packages/ui/primitives/document-flow/types.ts:55
#: packages/ui/primitives/template-flow/add-template-fields.tsx:703
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:506
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:512
#: packages/ui/primitives/template-flow/add-template-fields.tsx:664
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:498
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:504
msgid "Name"
msgstr "Name"
@@ -1039,8 +1004,8 @@ msgstr "Needs to sign"
msgid "Needs to view"
msgstr "Needs to view"
#: packages/ui/primitives/document-flow/add-fields.tsx:693
#: packages/ui/primitives/template-flow/add-template-fields.tsx:516
#: packages/ui/primitives/document-flow/add-fields.tsx:686
#: packages/ui/primitives/template-flow/add-template-fields.tsx:504
msgid "No recipient matching this description was found."
msgstr "No recipient matching this description was found."
@@ -1048,8 +1013,8 @@ msgstr "No recipient matching this description was found."
msgid "No recipients"
msgstr "No recipients"
#: packages/ui/primitives/document-flow/add-fields.tsx:708
#: packages/ui/primitives/template-flow/add-template-fields.tsx:531
#: packages/ui/primitives/document-flow/add-fields.tsx:701
#: packages/ui/primitives/template-flow/add-template-fields.tsx:519
msgid "No recipients with this role"
msgstr "No recipients with this role"
@@ -1077,9 +1042,9 @@ msgstr "No value found."
msgid "None"
msgstr "None"
#: packages/ui/primitives/document-flow/add-fields.tsx:986
#: packages/ui/primitives/document-flow/add-fields.tsx:979
#: packages/ui/primitives/document-flow/types.ts:56
#: packages/ui/primitives/template-flow/add-template-fields.tsx:781
#: packages/ui/primitives/template-flow/add-template-fields.tsx:742
msgid "Number"
msgstr "Number"
@@ -1137,7 +1102,7 @@ msgstr "Pick a number"
msgid "Placeholder"
msgstr "Placeholder"
#: packages/email/template-components/template-document-invite.tsx:56
#: packages/email/template-components/template-document-invite.tsx:46
msgid "Please {0} your document<0/>\"{documentName}\""
msgstr "Please {0} your document<0/>\"{documentName}\""
@@ -1170,7 +1135,7 @@ msgstr "Please confirm your email address"
msgid "Please try again or contact our support."
msgstr "Please try again or contact our support."
#: packages/ui/primitives/document-flow/types.ts:57
#: packages/ui/primitives/template-flow/add-template-fields.tsx:768
msgid "Radio"
msgstr "Radio"
@@ -1186,16 +1151,11 @@ msgstr "Radio values"
msgid "Read only"
msgstr "Read only"
#: packages/email/template-components/template-document-rejected.tsx:32
msgid "Reason for rejection: {rejectionReason}"
msgstr "Reason for rejection: {rejectionReason}"
#: packages/ui/components/recipient/recipient-role-select.tsx:95
msgid "Receives copy"
msgstr "Receives copy"
#: packages/lib/utils/document-audit-logs.ts:338
#: packages/lib/utils/document-audit-logs.ts:353
msgid "Recipient"
msgstr "Recipient"
@@ -1213,7 +1173,7 @@ msgstr "Recipient removed email"
msgid "Recipient signing request email"
msgstr "Recipient signing request email"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:390
#: packages/ui/primitives/signature-pad/signature-pad.tsx:384
msgid "Red"
msgstr "Red"
@@ -1222,18 +1182,6 @@ msgstr "Red"
msgid "Redirect URL"
msgstr "Redirect URL"
#: packages/email/template-components/template-document-invite.tsx:96
msgid "Reject Document"
msgstr "Reject Document"
#: packages/email/template-components/template-document-rejection-confirmed.tsx:22
msgid "Rejection Confirmed"
msgstr "Rejection Confirmed"
#: packages/email/template-components/template-document-rejection-confirmed.tsx:34
msgid "Rejection reason: {reason}"
msgstr "Rejection reason: {reason}"
#: packages/lib/server-only/document/resend-document.tsx:192
msgid "Reminder: {0}"
msgstr "Reminder: {0}"
@@ -1250,7 +1198,7 @@ msgstr "Reminder: Please {recipientActionVerb} this document"
msgid "Reminder: Please {recipientActionVerb} your document"
msgstr "Reminder: Please {recipientActionVerb} your document"
#: packages/ui/primitives/document-flow/add-fields.tsx:1117
#: packages/ui/primitives/document-flow/add-fields.tsx:1110
msgid "Remove"
msgstr "Remove"
@@ -1282,7 +1230,7 @@ msgstr "Rows per page"
msgid "Save"
msgstr "Save"
#: packages/ui/primitives/template-flow/add-template-fields.tsx:893
#: packages/ui/primitives/template-flow/add-template-fields.tsx:854
msgid "Save Template"
msgstr "Save Template"
@@ -1291,7 +1239,6 @@ msgid "Search languages..."
msgstr "Search languages..."
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:115
#: packages/ui/primitives/document-flow/types.ts:59
msgid "Select"
msgstr "Select"
@@ -1354,8 +1301,8 @@ msgstr "Share the Link"
msgid "Share your signing experience!"
msgstr "Share your signing experience!"
#: packages/ui/primitives/document-flow/add-signers.tsx:709
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:663
#: packages/ui/primitives/document-flow/add-signers.tsx:680
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:655
msgid "Show advanced settings"
msgstr "Show advanced settings"
@@ -1363,7 +1310,7 @@ msgstr "Show advanced settings"
msgid "Sign"
msgstr "Sign"
#: packages/email/template-components/template-document-invite.tsx:104
#: packages/email/template-components/template-document-invite.tsx:87
msgid "Sign Document"
msgstr "Sign Document"
@@ -1371,11 +1318,11 @@ msgstr "Sign Document"
msgid "Sign In"
msgstr "Sign In"
#: packages/ui/primitives/document-flow/add-fields.tsx:830
#: packages/ui/primitives/document-flow/add-fields.tsx:823
#: packages/ui/primitives/document-flow/add-signature.tsx:323
#: packages/ui/primitives/document-flow/field-icon.tsx:52
#: packages/ui/primitives/document-flow/types.ts:49
#: packages/ui/primitives/template-flow/add-template-fields.tsx:625
#: packages/ui/primitives/template-flow/add-template-fields.tsx:586
msgid "Signature"
msgstr "Signature"
@@ -1399,8 +1346,8 @@ msgstr "Signers must have unique emails"
msgid "Signing"
msgstr "Signing"
#: packages/lib/server-only/document/send-completed-email.ts:114
#: packages/lib/server-only/document/send-completed-email.ts:194
#: packages/lib/server-only/document/send-completed-email.ts:111
#: packages/lib/server-only/document/send-completed-email.ts:191
msgid "Signing Complete!"
msgstr "Signing Complete!"
@@ -1458,9 +1405,9 @@ msgstr "Team email removed for {teamName} on Documenso"
msgid "Template title"
msgstr "Template title"
#: packages/ui/primitives/document-flow/add-fields.tsx:960
#: packages/ui/primitives/document-flow/add-fields.tsx:953
#: packages/ui/primitives/document-flow/types.ts:52
#: packages/ui/primitives/template-flow/add-template-fields.tsx:755
#: packages/ui/primitives/template-flow/add-template-fields.tsx:716
msgid "Text"
msgstr "Text"
@@ -1480,13 +1427,6 @@ msgstr "The authentication required for recipients to sign the signature field."
msgid "The authentication required for recipients to view the document."
msgstr "The authentication required for recipients to view the document."
#~ 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
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 "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."
#: packages/ui/components/document/document-send-email-message-helper.tsx:31
msgid "The document's name"
msgstr "The document's name"
@@ -1555,7 +1495,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."
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:764
#: packages/ui/primitives/document-flow/add-fields.tsx:757
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."
@@ -1567,13 +1507,6 @@ msgstr "This document is password protected. Please enter the password to view t
msgid "This document was sent using <0>Documenso.</0>"
msgstr "This document was sent using <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
msgid "This email confirms that you have rejected the document <0>\"{documentName}\"</0> sent by {documentOwnerName}."
msgstr "This email confirms that you have rejected the document <0>\"{documentName}\"</0> sent by {documentOwnerName}."
#: 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."
msgstr "This email is sent to the recipient if they are removed from a pending document."
@@ -1586,7 +1519,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."
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:581
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:573
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."
@@ -1594,7 +1527,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."
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:1097
#: packages/ui/primitives/document-flow/add-fields.tsx:1090
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."
@@ -1623,8 +1556,8 @@ msgstr "Time Zone"
msgid "Title"
msgstr "Title"
#: packages/ui/primitives/document-flow/add-fields.tsx:1080
#: packages/ui/primitives/template-flow/add-template-fields.tsx:873
#: packages/ui/primitives/document-flow/add-fields.tsx:1073
#: packages/ui/primitives/template-flow/add-template-fields.tsx:834
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."
@@ -1670,8 +1603,7 @@ msgstr "View all documents sent to and from this email address"
msgid "View document"
msgstr "View document"
#: packages/email/template-components/template-document-invite.tsx:105
#: packages/email/template-components/template-document-rejected.tsx:44
#: packages/email/template-components/template-document-invite.tsx:88
#: packages/ui/primitives/document-flow/add-subject.tsx:90
#: packages/ui/primitives/document-flow/add-subject.tsx:91
msgid "View Document"
@@ -1749,10 +1681,6 @@ msgstr "You can revoke access at any time in your team settings on Documenso <0>
msgid "You can use the following variables in your message:"
msgstr "You can use the following variables in your message:"
#: packages/email/template-components/template-document-rejected.tsx:37
msgid "You can view the document and its status by clicking the button below."
msgstr "You can view the document and its status by clicking the button below."
#: packages/ui/primitives/document-dropzone.tsx:43
msgid "You cannot upload documents at this time."
msgstr "You cannot upload documents at this time."
@@ -1786,13 +1714,6 @@ msgstr "You have initiated the document {0} that requires you to {recipientActio
msgid "You have reached your document limit."
msgstr "You have reached your document limit."
#: packages/email/templates/document-rejection-confirmed.tsx:27
msgid "You have rejected the document '{documentName}'"
msgstr "You have rejected the document '{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
msgid "You have signed “{documentName}”"
msgstr "You have signed “{documentName}”"
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+73 -151
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: es\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-11-20 11:56\n"
"PO-Revision-Date: 2024-11-12 05:45\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -79,7 +79,7 @@ msgstr "{inviterName} ha cancelado el documento {documentName}, ya no necesitas
msgid "{inviterName} has cancelled the document<0/>\"{documentName}\""
msgstr "{inviterName} ha cancelado el documento<0/>\"{documentName}\""
#: packages/email/template-components/template-document-invite.tsx:75
#: packages/email/template-components/template-document-invite.tsx:65
msgid "{inviterName} has invited you to {0}<0/>\"{documentName}\""
msgstr "{inviterName} te ha invitado a {0}<0/>\"{documentName}\""
@@ -99,7 +99,7 @@ msgstr "{inviterName} te ha eliminado del documento {documentName}."
msgid "{inviterName} has removed you from the document<0/>\"{documentName}\""
msgstr "{inviterName} te ha eliminado del documento<0/>\"{documentName}\""
#: packages/email/template-components/template-document-invite.tsx:63
#: packages/email/template-components/template-document-invite.tsx:53
msgid "{inviterName} on behalf of {teamName} has invited you to {0}"
msgstr "{inviterName} en nombre de {teamName} te ha invitado a {0}"
@@ -151,11 +151,11 @@ msgstr "{prefix} eliminó un campo"
msgid "{prefix} removed a recipient"
msgstr "{prefix} eliminó un destinatario"
#: packages/lib/utils/document-audit-logs.ts:365
#: packages/lib/utils/document-audit-logs.ts:355
msgid "{prefix} resent an email to {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:356
msgid "{prefix} sent an email to {0}"
msgstr "{prefix} envió un correo electrónico a {0}"
@@ -207,15 +207,7 @@ msgstr "{prefix} actualizó la visibilidad del documento"
msgid "{recipientName} {action} a document by using one of your direct links"
msgstr "{recipientName} {action} un documento utilizando uno de tus enlaces directos"
#: packages/email/templates/document-rejected.tsx:27
msgid "{recipientName} has rejected the document '{documentName}'"
msgstr "{recipientName} ha rechazado el documento '{documentName}'"
#: packages/email/template-components/template-document-rejected.tsx:25
msgid "{signerName} has rejected the document \"{documentName}\"."
msgstr "{signerName} ha rechazado el documento \"{documentName}\"."
#: packages/email/template-components/template-document-invite.tsx:68
#: packages/email/template-components/template-document-invite.tsx:58
msgid "{teamName} has invited you to {0}"
msgstr "{teamName} te ha invitado a {0}"
@@ -239,10 +231,6 @@ msgstr "{userName} envió una copia del documento"
msgid "{userName} completed their task"
msgstr "{userName} completó su tarea"
#: packages/lib/utils/document-audit-logs.ts:355
msgid "{userName} rejected the document"
msgstr "{userName} rechazó el documento"
#: packages/lib/utils/document-audit-logs.ts:341
msgid "{userName} signed the document"
msgstr "{userName} firmó el documento"
@@ -410,19 +398,19 @@ msgstr "Agregar otra opción"
msgid "Add another value"
msgstr "Agregar otro valor"
#: packages/ui/primitives/document-flow/add-signers.tsx:691
#: packages/ui/primitives/document-flow/add-signers.tsx:662
msgid "Add myself"
msgstr "Agregame"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:645
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:637
msgid "Add Myself"
msgstr "Agregame"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:631
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:623
msgid "Add Placeholder Recipient"
msgstr "Agregar destinatario de marcador de posición"
#: packages/ui/primitives/document-flow/add-signers.tsx:680
#: packages/ui/primitives/document-flow/add-signers.tsx:651
msgid "Add Signer"
msgstr "Agregar firmante"
@@ -443,8 +431,8 @@ msgstr "Admin"
msgid "Advanced Options"
msgstr "Opciones avanzadas"
#: packages/ui/primitives/document-flow/add-fields.tsx:576
#: packages/ui/primitives/template-flow/add-template-fields.tsx:414
#: packages/ui/primitives/document-flow/add-fields.tsx:573
#: packages/ui/primitives/template-flow/add-template-fields.tsx:406
msgid "Advanced settings"
msgstr "Configuraciones avanzadas"
@@ -472,7 +460,7 @@ msgstr "Se produjo un error al cargar el documento."
msgid "Approve"
msgstr "Aprobar"
#: packages/email/template-components/template-document-invite.tsx:106
#: packages/email/template-components/template-document-invite.tsx:89
msgid "Approve Document"
msgstr "Aprobar Documento"
@@ -500,11 +488,11 @@ msgstr "Aprobando"
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:"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:383
#: packages/ui/primitives/signature-pad/signature-pad.tsx:377
msgid "Black"
msgstr "Negro"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:397
#: packages/ui/primitives/signature-pad/signature-pad.tsx:391
msgid "Blue"
msgstr "Azul"
@@ -550,10 +538,6 @@ msgstr "Ccers"
msgid "Character Limit"
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
msgid "Checkbox values"
msgstr "Valores de Checkbox"
@@ -562,7 +546,7 @@ msgstr "Valores de Checkbox"
msgid "Clear filters"
msgstr "Limpiar filtros"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:417
#: packages/ui/primitives/signature-pad/signature-pad.tsx:411
msgid "Clear Signature"
msgstr "Limpiar firma"
@@ -589,8 +573,8 @@ msgstr "Documento completado"
msgid "Configure Direct Recipient"
msgstr "Configurar destinatario directo"
#: packages/ui/primitives/document-flow/add-fields.tsx:577
#: packages/ui/primitives/template-flow/add-template-fields.tsx:415
#: packages/ui/primitives/document-flow/add-fields.tsx:574
#: packages/ui/primitives/template-flow/add-template-fields.tsx:407
msgid "Configure the {0} field"
msgstr "Configurar el campo {0}"
@@ -606,7 +590,7 @@ msgstr "Continuar"
#~ msgid "Continue by {0} the document."
#~ msgstr "Continue by {0} the document."
#: packages/email/template-components/template-document-invite.tsx:86
#: packages/email/template-components/template-document-invite.tsx:76
msgid "Continue by approving the document."
msgstr "Continúa aprobando el documento."
@@ -614,11 +598,11 @@ msgstr "Continúa aprobando el documento."
msgid "Continue by downloading the document."
msgstr "Continúa descargando el documento."
#: packages/email/template-components/template-document-invite.tsx:84
#: packages/email/template-components/template-document-invite.tsx:74
msgid "Continue by signing the document."
msgstr "Continúa firmando el documento."
#: packages/email/template-components/template-document-invite.tsx:85
#: packages/email/template-components/template-document-invite.tsx:75
msgid "Continue by viewing the document."
msgstr "Continúa viendo el documento."
@@ -651,9 +635,9 @@ msgstr "Crear cuenta"
msgid "Custom Text"
msgstr "Texto personalizado"
#: packages/ui/primitives/document-flow/add-fields.tsx:934
#: packages/ui/primitives/document-flow/add-fields.tsx:927
#: packages/ui/primitives/document-flow/types.ts:53
#: packages/ui/primitives/template-flow/add-template-fields.tsx:729
#: packages/ui/primitives/template-flow/add-template-fields.tsx:690
msgid "Date"
msgstr "Fecha"
@@ -670,18 +654,10 @@ msgstr "Rechazar"
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>"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:578
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:570
msgid "Direct link receiver"
msgstr "Receptor de enlace directo"
#: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:149
msgid "Document \"{0}\" - Rejected by {1}"
msgstr "Documento \"{0}\" - Rechazado por {1}"
#: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:109
msgid "Document \"{0}\" - Rejection Confirmed"
msgstr "Documento \"{0}\" - Rechazo confirmado"
#: packages/ui/components/document/document-global-auth-access-select.tsx:62
#: packages/ui/primitives/document-flow/add-settings.tsx:216
#: packages/ui/primitives/template-flow/add-template-settings.tsx:202
@@ -697,8 +673,8 @@ msgstr "Se actualizó la autenticación de acceso al documento"
msgid "Document Cancelled"
msgstr "Documento cancelado"
#: packages/lib/utils/document-audit-logs.ts:369
#: packages/lib/utils/document-audit-logs.ts:370
#: packages/lib/utils/document-audit-logs.ts:359
#: packages/lib/utils/document-audit-logs.ts:360
msgid "Document completed"
msgstr "Documento completado"
@@ -752,13 +728,6 @@ msgstr "Documento abierto"
msgid "Document pending email"
msgstr "Correo electrónico de documento pendiente"
#: packages/email/template-components/template-document-rejected.tsx:21
msgid "Document Rejected"
msgstr "Documento Rechazado"
#~ msgid "Document Rejection Confirmed"
#~ msgstr "Document Rejection Confirmed"
#: packages/lib/utils/document-audit-logs.ts:330
msgid "Document sent"
msgstr "Documento enviado"
@@ -792,8 +761,8 @@ msgstr "Borrador"
msgid "Drag & drop your PDF here."
msgstr "Arrastre y suelte su PDF aquí."
#: packages/ui/primitives/document-flow/add-fields.tsx:1065
#: packages/ui/primitives/template-flow/add-template-fields.tsx:860
#: packages/ui/primitives/document-flow/add-fields.tsx:1058
#: packages/ui/primitives/template-flow/add-template-fields.tsx:820
msgid "Dropdown"
msgstr "Menú desplegable"
@@ -802,14 +771,14 @@ msgid "Dropdown options"
msgstr "Opciones de menú desplegable"
#: packages/lib/constants/document.ts:28
#: packages/ui/primitives/document-flow/add-fields.tsx:882
#: packages/ui/primitives/document-flow/add-fields.tsx:875
#: 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:519
#: packages/ui/primitives/document-flow/add-signers.tsx:500
#: packages/ui/primitives/document-flow/add-signers.tsx:507
#: packages/ui/primitives/document-flow/types.ts:54
#: packages/ui/primitives/template-flow/add-template-fields.tsx:677
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:471
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:478
#: packages/ui/primitives/template-flow/add-template-fields.tsx:638
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:463
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:470
msgid "Email"
msgstr "Correo electrónico"
@@ -821,15 +790,15 @@ msgstr "Se requiere email"
msgid "Email Options"
msgstr "Opciones de correo electrónico"
#: packages/lib/utils/document-audit-logs.ts:363
#: packages/lib/utils/document-audit-logs.ts:353
msgid "Email resent"
msgstr "Correo electrónico reeenviado"
#: packages/lib/utils/document-audit-logs.ts:363
#: packages/lib/utils/document-audit-logs.ts:353
msgid "Email sent"
msgstr "Correo electrónico enviado"
#: packages/ui/primitives/document-flow/add-fields.tsx:1130
#: packages/ui/primitives/document-flow/add-fields.tsx:1123
msgid "Empty field"
msgstr "Campo vacío"
@@ -838,12 +807,11 @@ msgid "Enable Direct Link Signing"
msgstr "Habilitar firma de enlace directo"
#: packages/ui/primitives/document-flow/add-signers.tsx:401
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:370
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:362
msgid "Enable signing order"
msgstr "Habilitar orden de firma"
#: packages/ui/primitives/document-flow/add-fields.tsx:802
#: packages/ui/primitives/template-flow/add-template-fields.tsx:597
#: packages/ui/primitives/document-flow/add-fields.tsx:795
msgid "Enable Typed Signatures"
msgstr "Habilitar firmas escritas"
@@ -931,13 +899,10 @@ msgstr "Autenticación de acción de destinatario global"
msgid "Go Back"
msgstr "Regresar"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:404
#: packages/ui/primitives/signature-pad/signature-pad.tsx:398
msgid "Green"
msgstr "Verde"
#~ msgid "Hello {recipientName},"
#~ msgstr "Hello {recipientName},"
#: packages/email/templates/reset-password.tsx:56
msgid "Hi, {userName} <0>({userEmail})</0>"
msgstr "Hola, {userName} <0>({userEmail})</0>"
@@ -1021,14 +986,14 @@ msgstr "Mensaje <0>(Opcional)</0>"
msgid "Min"
msgstr "Mín"
#: packages/ui/primitives/document-flow/add-fields.tsx:908
#: packages/ui/primitives/document-flow/add-fields.tsx:901
#: 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:556
#: packages/ui/primitives/document-flow/add-signers.tsx:535
#: packages/ui/primitives/document-flow/add-signers.tsx:541
#: packages/ui/primitives/document-flow/types.ts:55
#: packages/ui/primitives/template-flow/add-template-fields.tsx:703
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:506
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:512
#: packages/ui/primitives/template-flow/add-template-fields.tsx:664
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:498
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:504
msgid "Name"
msgstr "Nombre"
@@ -1044,8 +1009,8 @@ msgstr "Necesita firmar"
msgid "Needs to view"
msgstr "Necesita ver"
#: packages/ui/primitives/document-flow/add-fields.tsx:693
#: packages/ui/primitives/template-flow/add-template-fields.tsx:516
#: packages/ui/primitives/document-flow/add-fields.tsx:686
#: packages/ui/primitives/template-flow/add-template-fields.tsx:504
msgid "No recipient matching this description was found."
msgstr "No se encontró ningún destinatario que coincidiera con esta descripción."
@@ -1053,8 +1018,8 @@ msgstr "No se encontró ningún destinatario que coincidiera con esta descripci
msgid "No recipients"
msgstr "Sin destinatarios"
#: packages/ui/primitives/document-flow/add-fields.tsx:708
#: packages/ui/primitives/template-flow/add-template-fields.tsx:531
#: packages/ui/primitives/document-flow/add-fields.tsx:701
#: packages/ui/primitives/template-flow/add-template-fields.tsx:519
msgid "No recipients with this role"
msgstr "No hay destinatarios con este rol"
@@ -1082,9 +1047,9 @@ msgstr "No se encontró valor."
msgid "None"
msgstr "Ninguno"
#: packages/ui/primitives/document-flow/add-fields.tsx:986
#: packages/ui/primitives/document-flow/add-fields.tsx:979
#: packages/ui/primitives/document-flow/types.ts:56
#: packages/ui/primitives/template-flow/add-template-fields.tsx:781
#: packages/ui/primitives/template-flow/add-template-fields.tsx:742
msgid "Number"
msgstr "Número"
@@ -1142,7 +1107,7 @@ msgstr "Seleccione un número"
msgid "Placeholder"
msgstr "Marcador de posición"
#: packages/email/template-components/template-document-invite.tsx:56
#: packages/email/template-components/template-document-invite.tsx:46
msgid "Please {0} your document<0/>\"{documentName}\""
msgstr "Por favor {0} tu documento<0/>\"{documentName}\""
@@ -1175,7 +1140,7 @@ msgstr "Por favor confirma tu dirección de correo electrónico"
msgid "Please try again or contact our support."
msgstr "Por favor, inténtalo de nuevo o contacta a nuestro soporte."
#: packages/ui/primitives/document-flow/types.ts:57
#: packages/ui/primitives/template-flow/add-template-fields.tsx:768
msgid "Radio"
msgstr "Radio"
@@ -1191,16 +1156,11 @@ msgstr "Valores de radio"
msgid "Read only"
msgstr "Solo lectura"
#: packages/email/template-components/template-document-rejected.tsx:32
msgid "Reason for rejection: {rejectionReason}"
msgstr "Razón del rechazo: {rejectionReason}"
#: packages/ui/components/recipient/recipient-role-select.tsx:95
msgid "Receives copy"
msgstr "Recibe copia"
#: packages/lib/utils/document-audit-logs.ts:338
#: packages/lib/utils/document-audit-logs.ts:353
msgid "Recipient"
msgstr "Destinatario"
@@ -1218,7 +1178,7 @@ msgstr "Correo electrónico de destinatario eliminado"
msgid "Recipient signing request email"
msgstr "Correo electrónico de solicitud de firma de destinatario"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:390
#: packages/ui/primitives/signature-pad/signature-pad.tsx:384
msgid "Red"
msgstr "Rojo"
@@ -1227,18 +1187,6 @@ msgstr "Rojo"
msgid "Redirect URL"
msgstr "URL de redirección"
#: packages/email/template-components/template-document-invite.tsx:96
msgid "Reject Document"
msgstr "Rechazar Documento"
#: packages/email/template-components/template-document-rejection-confirmed.tsx:22
msgid "Rejection Confirmed"
msgstr "Rechazo Confirmado"
#: packages/email/template-components/template-document-rejection-confirmed.tsx:34
msgid "Rejection reason: {reason}"
msgstr "Razón del rechazo: {reason}"
#: packages/lib/server-only/document/resend-document.tsx:192
msgid "Reminder: {0}"
msgstr "Recordatorio: {0}"
@@ -1255,7 +1203,7 @@ msgstr "Recordatorio: Por favor {recipientActionVerb} este documento"
msgid "Reminder: Please {recipientActionVerb} your document"
msgstr "Recordatorio: Por favor {recipientActionVerb} tu documento"
#: packages/ui/primitives/document-flow/add-fields.tsx:1117
#: packages/ui/primitives/document-flow/add-fields.tsx:1110
msgid "Remove"
msgstr "Eliminar"
@@ -1287,7 +1235,7 @@ msgstr "Filas por página"
msgid "Save"
msgstr "Guardar"
#: packages/ui/primitives/template-flow/add-template-fields.tsx:893
#: packages/ui/primitives/template-flow/add-template-fields.tsx:854
msgid "Save Template"
msgstr "Guardar plantilla"
@@ -1296,7 +1244,6 @@ msgid "Search languages..."
msgstr "Buscar idiomas..."
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:115
#: packages/ui/primitives/document-flow/types.ts:59
msgid "Select"
msgstr "Seleccionar"
@@ -1359,8 +1306,8 @@ msgstr "Compartir el enlace"
msgid "Share your signing experience!"
msgstr "¡Comparte tu experiencia de firma!"
#: packages/ui/primitives/document-flow/add-signers.tsx:709
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:663
#: packages/ui/primitives/document-flow/add-signers.tsx:680
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:655
msgid "Show advanced settings"
msgstr "Mostrar configuraciones avanzadas"
@@ -1368,7 +1315,7 @@ msgstr "Mostrar configuraciones avanzadas"
msgid "Sign"
msgstr "Firmar"
#: packages/email/template-components/template-document-invite.tsx:104
#: packages/email/template-components/template-document-invite.tsx:87
msgid "Sign Document"
msgstr "Firmar Documento"
@@ -1376,11 +1323,11 @@ msgstr "Firmar Documento"
msgid "Sign In"
msgstr "Iniciar sesión"
#: packages/ui/primitives/document-flow/add-fields.tsx:830
#: packages/ui/primitives/document-flow/add-fields.tsx:823
#: packages/ui/primitives/document-flow/add-signature.tsx:323
#: packages/ui/primitives/document-flow/field-icon.tsx:52
#: packages/ui/primitives/document-flow/types.ts:49
#: packages/ui/primitives/template-flow/add-template-fields.tsx:625
#: packages/ui/primitives/template-flow/add-template-fields.tsx:586
msgid "Signature"
msgstr "Firma"
@@ -1404,8 +1351,8 @@ msgstr "Los firmantes deben tener correos electrónicos únicos"
msgid "Signing"
msgstr "Firmando"
#: packages/lib/server-only/document/send-completed-email.ts:114
#: packages/lib/server-only/document/send-completed-email.ts:194
#: packages/lib/server-only/document/send-completed-email.ts:111
#: packages/lib/server-only/document/send-completed-email.ts:191
msgid "Signing Complete!"
msgstr "¡Firma completa!"
@@ -1463,9 +1410,9 @@ msgstr "Correo electrónico del equipo eliminado para {teamName} en Documenso"
msgid "Template title"
msgstr "Título de plantilla"
#: packages/ui/primitives/document-flow/add-fields.tsx:960
#: packages/ui/primitives/document-flow/add-fields.tsx:953
#: packages/ui/primitives/document-flow/types.ts:52
#: packages/ui/primitives/template-flow/add-template-fields.tsx:755
#: packages/ui/primitives/template-flow/add-template-fields.tsx:716
msgid "Text"
msgstr "Texto"
@@ -1485,13 +1432,6 @@ msgstr "La autenticación requerida para que los destinatarios firmen el campo d
msgid "The authentication required for recipients to view the document."
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
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 "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
msgid "The document's name"
msgstr "El nombre del documento"
@@ -1560,7 +1500,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."
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:764
#: packages/ui/primitives/document-flow/add-fields.tsx:757
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."
@@ -1572,13 +1512,6 @@ msgstr "Este documento está protegido por contraseña. Por favor ingrese la con
msgid "This document was sent using <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
msgid "This email confirms that you have rejected the document <0>\"{documentName}\"</0> sent by {documentOwnerName}."
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
msgid "This email is sent to the recipient if they are removed from a pending document."
msgstr "Este correo electrónico se envía al destinatario si es eliminado de un documento pendiente."
@@ -1591,7 +1524,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."
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:581
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:573
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."
@@ -1599,7 +1532,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."
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:1097
#: packages/ui/primitives/document-flow/add-fields.tsx:1090
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."
@@ -1628,8 +1561,8 @@ msgstr "Zona horaria"
msgid "Title"
msgstr "Título"
#: packages/ui/primitives/document-flow/add-fields.tsx:1080
#: packages/ui/primitives/template-flow/add-template-fields.tsx:873
#: packages/ui/primitives/document-flow/add-fields.tsx:1073
#: packages/ui/primitives/template-flow/add-template-fields.tsx:834
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}."
@@ -1675,8 +1608,7 @@ msgstr "Ver todos los documentos enviados hacia y desde esta dirección de corre
msgid "View document"
msgstr "Ver documento"
#: packages/email/template-components/template-document-invite.tsx:105
#: packages/email/template-components/template-document-rejected.tsx:44
#: packages/email/template-components/template-document-invite.tsx:88
#: packages/ui/primitives/document-flow/add-subject.tsx:90
#: packages/ui/primitives/document-flow/add-subject.tsx:91
msgid "View Document"
@@ -1754,10 +1686,6 @@ msgstr "Puedes revocar el acceso en cualquier momento en la configuración de tu
msgid "You can use the following variables in your message:"
msgstr "Puede usar las siguientes variables en su mensaje:"
#: packages/email/template-components/template-document-rejected.tsx:37
msgid "You can view the document and its status by clicking the button below."
msgstr "Puede ver el documento y su estado haciendo clic en el botón de abajo."
#: packages/ui/primitives/document-dropzone.tsx:43
msgid "You cannot upload documents at this time."
msgstr "No puede cargar documentos en este momento."
@@ -1791,13 +1719,6 @@ msgstr "Has iniciado el documento {0} que requiere que {recipientActionVerb}."
msgid "You have reached your document limit."
msgstr "Ha alcanzado su límite de documentos."
#: packages/email/templates/document-rejection-confirmed.tsx:27
msgid "You have rejected the document '{documentName}'"
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
msgid "You have signed “{documentName}”"
msgstr "Has firmado “{documentName}”"
@@ -1814,3 +1735,4 @@ msgstr "Tu contraseña ha sido actualizada."
#: packages/email/templates/team-delete.tsx:32
msgid "Your team has been deleted"
msgstr "Tu equipo ha sido eliminado"
+2 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: es\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-11-20 11:56\n"
"PO-Revision-Date: 2024-11-12 05:45\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -602,3 +602,4 @@ msgstr "Puedes autoalojar Documenso de forma gratuita o usar nuestra versión al
#: apps/marketing/src/components/(marketing)/carousel.tsx:272
msgid "Your browser does not support the video tag."
msgstr "Tu navegador no soporta la etiqueta de video."

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