Compare commits

...

9 Commits

Author SHA1 Message Date
David Nguyen
063fd32f18 feat: add signature configurations (#1710)
Add ability to enable or disable allowed signature types:
- Drawn
- Typed
- Uploaded

**Tabbed style signature dialog**

![image](https://github.com/user-attachments/assets/a816fab6-b071-42a5-bb5c-6d4a2572431e)

**Document settings**

![image](https://github.com/user-attachments/assets/f0c1bff1-6be1-4c87-b384-1666fa25d7a6)

**Team preferences**

![image](https://github.com/user-attachments/assets/8767b05e-1463-4087-8672-f3f43d8b0f2c)

- Add multiselect to select allowed signatures in document and templates
settings tab
- Add multiselect to select allowed signatures in teams preferences
- Removed "Enable typed signatures" from document/template edit page
- Refactored signature pad to use tabs instead of an all in one
signature pad

Added E2E tests to check settings are applied correctly for documents
and templates
2025-03-24 17:13:11 +11:00
Mythie
231f51bd1f v1.10.0-rc.1 2025-03-22 17:34:33 +11:00
Mythie
a8de8368a2 fix: hide powered by on certificate for platform documents 2025-03-22 12:04:08 +11:00
Mythie
7dd331addf fix: allow blank rejection reasons 2025-03-22 12:01:18 +11:00
Mythie
c6743a7cec v1.10.0-rc.0 2025-03-22 03:23:23 +11:00
Mythie
efbc097191 fix: unblock last signer when using dictation 2025-03-22 02:34:12 +11:00
Lucas Smith
f1525991dc feat: dictate next signer (#1719)
Adds next recipient dictation functionality to document signing flow,
allowing assistants and signers to update the next recipient's
information during the signing process.

## Related Issue

N/A

## Changes Made

- Added form handling for next recipient dictation in signing dialogs
- Implemented UI for updating next recipient information
- Added e2e tests covering dictation scenarios:
  - Regular signing with dictation enabled
  - Assistant role with dictation
  - Parallel signing flow
  - Disabled dictation state

## Testing Performed

- Added comprehensive e2e tests covering:
  - Sequential signing with dictation
  - Assistant role dictation
  - Parallel signing without dictation
  - Form validation and state management
- Tested on Chrome and Firefox
- Verified recipient state updates in database
2025-03-21 13:27:04 +11:00
Mythie
fb173e4d0e chore: update docker build scripts 2025-03-20 10:52:33 +11:00
Catalin Pit
d422ffa873 chore: add terms and privacy policy link (#1707) 2025-03-19 19:29:09 +11:00
105 changed files with 4456 additions and 1534 deletions

View File

@@ -1,4 +1,9 @@
import { useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans } from '@lingui/react/macro'; import { Trans } from '@lingui/react/macro';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { import {
@@ -9,64 +14,208 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from '@documenso/ui/primitives/dialog'; } from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { DocumentSigningDisclosure } from '../general/document-signing/document-signing-disclosure'; import { DocumentSigningDisclosure } from '../general/document-signing/document-signing-disclosure';
export type NextSigner = {
name: string;
email: string;
};
type ConfirmationDialogProps = { type ConfirmationDialogProps = {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
onConfirm: () => void; onConfirm: (nextSigner?: NextSigner) => void;
hasUninsertedFields: boolean; hasUninsertedFields: boolean;
isSubmitting: boolean; isSubmitting: boolean;
allowDictateNextSigner?: boolean;
defaultNextSigner?: NextSigner;
}; };
const ZNextSignerFormSchema = z.object({
name: z.string().min(1, 'Name is required'),
email: z.string().email('Invalid email address'),
});
type TNextSignerFormSchema = z.infer<typeof ZNextSignerFormSchema>;
export function AssistantConfirmationDialog({ export function AssistantConfirmationDialog({
isOpen, isOpen,
onClose, onClose,
onConfirm, onConfirm,
hasUninsertedFields, hasUninsertedFields,
isSubmitting, isSubmitting,
allowDictateNextSigner = false,
defaultNextSigner,
}: ConfirmationDialogProps) { }: ConfirmationDialogProps) {
const [isEditingNextSigner, setIsEditingNextSigner] = useState(false);
const form = useForm<TNextSignerFormSchema>({
resolver: zodResolver(ZNextSignerFormSchema),
defaultValues: {
name: defaultNextSigner?.name ?? '',
email: defaultNextSigner?.email ?? '',
},
});
const onOpenChange = () => { const onOpenChange = () => {
if (isSubmitting) { if (form.formState.isSubmitting) {
return; return;
} }
form.reset({
name: defaultNextSigner?.name ?? '',
email: defaultNextSigner?.email ?? '',
});
setIsEditingNextSigner(false);
onClose(); onClose();
}; };
const onFormSubmit = async (data: TNextSignerFormSchema) => {
if (allowDictateNextSigner && data.name && data.email) {
await onConfirm({
name: data.name,
email: data.email,
});
} else {
await onConfirm();
}
};
const isNextSignerValid = !allowDictateNextSigner || (form.watch('name') && form.watch('email'));
return ( return (
<Dialog open={isOpen} onOpenChange={onOpenChange}> <Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogContent> <DialogContent>
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset
disabled={form.formState.isSubmitting || isSubmitting}
className="border-none p-0"
>
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
<Trans>Complete Document</Trans> <Trans>Complete Document</Trans>
</DialogTitle> </DialogTitle>
<DialogDescription> <DialogDescription>
<Trans> <Trans>
Are you sure you want to complete the document? This action cannot be undone. Please Are you sure you want to complete the document? This action cannot be undone.
ensure that you have completed prefilling all relevant fields before proceeding. Please ensure that you have completed prefilling all relevant fields before
proceeding.
</Trans> </Trans>
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="flex flex-col gap-4"> <div className="mt-4 flex flex-col gap-4">
<DocumentSigningDisclosure /> {allowDictateNextSigner && (
<div className="space-y-4">
{!isEditingNextSigner && (
<div>
<p className="text-muted-foreground text-sm">
The next recipient to sign this document will be{' '}
<span className="font-semibold">{form.watch('name')}</span> (
<span className="font-semibold">{form.watch('email')}</span>).
</p>
<Button
type="button"
className="mt-2"
variant="outline"
size="sm"
onClick={() => setIsEditingNextSigner((prev) => !prev)}
>
<Trans>Update Recipient</Trans>
</Button>
</div>
)}
{isEditingNextSigner && (
<div className="flex flex-col gap-4 md:flex-row">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>
<Trans>Name</Trans>
</FormLabel>
<FormControl>
<Input
{...field}
className="mt-2"
placeholder="Enter the next signer's name"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>
<Trans>Email</Trans>
</FormLabel>
<FormControl>
<Input
{...field}
type="email"
className="mt-2"
placeholder="Enter the next signer's email"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
)}
</div>
)}
<DocumentSigningDisclosure className="mt-4" />
</div> </div>
<DialogFooter className="mt-4"> <DialogFooter className="mt-4">
<Button variant="secondary" onClick={onClose} disabled={isSubmitting}> <Button
Cancel type="button"
variant="secondary"
onClick={onClose}
disabled={form.formState.isSubmitting}
>
<Trans>Cancel</Trans>
</Button> </Button>
<Button <Button
type="submit"
variant={hasUninsertedFields ? 'destructive' : 'default'} variant={hasUninsertedFields ? 'destructive' : 'default'}
onClick={onConfirm} disabled={form.formState.isSubmitting || !isNextSignerValid}
disabled={isSubmitting} loading={form.formState.isSubmitting}
loading={isSubmitting}
> >
{isSubmitting ? 'Submitting...' : hasUninsertedFields ? 'Proceed' : 'Continue'} {form.formState.isSubmitting ? (
<Trans>Submitting...</Trans>
) : hasUninsertedFields ? (
<Trans>Proceed</Trans>
) : (
<Trans>Continue</Trans>
)}
</Button> </Button>
</DialogFooter> </DialogFooter>
</fieldset>
</form>
</Form>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );

View File

@@ -3,8 +3,8 @@ import { useEffect, useLayoutEffect, useState } from 'react';
import { msg } from '@lingui/core/macro'; import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react'; import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro'; import { Trans } from '@lingui/react/macro';
import { type DocumentData, type Field, FieldType } from '@prisma/client';
import type { DocumentMeta, Recipient, Signature, TemplateMeta } from '@prisma/client'; import type { DocumentMeta, Recipient, Signature, TemplateMeta } from '@prisma/client';
import { type DocumentData, type Field, FieldType } from '@prisma/client';
import { LucideChevronDown, LucideChevronUp } from 'lucide-react'; import { LucideChevronDown, LucideChevronUp } from 'lucide-react';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { useSearchParams } from 'react-router'; import { useSearchParams } from 'react-router';
@@ -25,12 +25,11 @@ import type {
} from '@documenso/trpc/server/field-router/schema'; } from '@documenso/trpc/server/field-router/schema';
import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip'; import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import { ElementVisible } from '@documenso/ui/primitives/element-visible'; import { ElementVisible } from '@documenso/ui/primitives/element-visible';
import { Input } from '@documenso/ui/primitives/input'; import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label'; import { Label } from '@documenso/ui/primitives/label';
import { PDFViewer } from '@documenso/ui/primitives/pdf-viewer'; import { PDFViewer } from '@documenso/ui/primitives/pdf-viewer';
import { SignaturePad } from '@documenso/ui/primitives/signature-pad'; import { SignaturePadDialog } from '@documenso/ui/primitives/signature-pad/signature-pad-dialog';
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
import { BrandingLogo } from '~/components/general/branding-logo'; import { BrandingLogo } from '~/components/general/branding-logo';
@@ -69,16 +68,8 @@ export const EmbedDirectTemplateClientPage = ({
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const { const { fullName, email, signature, setFullName, setEmail, setSignature } =
fullName, useRequiredDocumentSigningContext();
email,
signature,
signatureValid,
setFullName,
setEmail,
setSignature,
setSignatureValid,
} = useRequiredDocumentSigningContext();
const [hasFinishedInit, setHasFinishedInit] = useState(false); const [hasFinishedInit, setHasFinishedInit] = useState(false);
const [hasDocumentLoaded, setHasDocumentLoaded] = useState(false); const [hasDocumentLoaded, setHasDocumentLoaded] = useState(false);
@@ -194,10 +185,6 @@ export const EmbedDirectTemplateClientPage = ({
const onCompleteClick = async () => { const onCompleteClick = async () => {
try { try {
if (hasSignatureField && !signatureValid) {
return;
}
const valid = validateFieldsInserted(pendingFields); const valid = validateFieldsInserted(pendingFields);
if (!valid) { if (!valid) {
@@ -419,34 +406,16 @@ export const EmbedDirectTemplateClientPage = ({
<Trans>Signature</Trans> <Trans>Signature</Trans>
</Label> </Label>
<Card className="mt-2" gradient degrees={-120}> <SignaturePadDialog
<CardContent className="p-0"> className="mt-2"
<SignaturePad
className="h-44 w-full"
disabled={isThrottled || isSubmitting} disabled={isThrottled || isSubmitting}
defaultValue={signature ?? undefined} disableAnimation
onChange={(value) => { value={signature ?? ''}
setSignature(value); onChange={(v) => setSignature(v ?? '')}
}} typedSignatureEnabled={metadata?.typedSignatureEnabled}
onValidityChange={(isValid) => { uploadSignatureEnabled={metadata?.uploadSignatureEnabled}
setSignatureValid(isValid); drawSignatureEnabled={metadata?.drawSignatureEnabled}
}}
allowTypedSignature={Boolean(
metadata &&
'typedSignatureEnabled' in metadata &&
metadata.typedSignatureEnabled,
)}
/> />
</CardContent>
</Card>
{hasSignatureField && !signatureValid && (
<div className="text-destructive mt-2 text-sm">
<Trans>
Signature is too small. Please provide a more complete signature.
</Trans>
</div>
)}
</div> </div>
)} )}
</div> </div>

View File

@@ -54,6 +54,8 @@ export const EmbedDocumentFields = ({
onSignField={onSignField} onSignField={onSignField}
onUnsignField={onUnsignField} onUnsignField={onUnsignField}
typedSignatureEnabled={metadata?.typedSignatureEnabled} typedSignatureEnabled={metadata?.typedSignatureEnabled}
uploadSignatureEnabled={metadata?.uploadSignatureEnabled}
drawSignatureEnabled={metadata?.drawSignatureEnabled}
/> />
)) ))
.with(FieldType.INITIALS, () => ( .with(FieldType.INITIALS, () => (

View File

@@ -21,13 +21,12 @@ import type { RecipientWithFields } from '@documenso/prisma/types/recipient-with
import { trpc } from '@documenso/trpc/react'; import { trpc } from '@documenso/trpc/react';
import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip'; import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import { ElementVisible } from '@documenso/ui/primitives/element-visible'; import { ElementVisible } from '@documenso/ui/primitives/element-visible';
import { Input } from '@documenso/ui/primitives/input'; import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label'; import { Label } from '@documenso/ui/primitives/label';
import { PDFViewer } from '@documenso/ui/primitives/pdf-viewer'; import { PDFViewer } from '@documenso/ui/primitives/pdf-viewer';
import { RadioGroup, RadioGroupItem } from '@documenso/ui/primitives/radio-group'; import { RadioGroup, RadioGroupItem } from '@documenso/ui/primitives/radio-group';
import { SignaturePad } from '@documenso/ui/primitives/signature-pad'; import { SignaturePadDialog } from '@documenso/ui/primitives/signature-pad/signature-pad-dialog';
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
import { BrandingLogo } from '~/components/general/branding-logo'; import { BrandingLogo } from '~/components/general/branding-logo';
@@ -70,15 +69,8 @@ export const EmbedSignDocumentClientPage = ({
const { _ } = useLingui(); const { _ } = useLingui();
const { toast } = useToast(); const { toast } = useToast();
const { const { fullName, email, signature, setFullName, setSignature } =
fullName, useRequiredDocumentSigningContext();
email,
signature,
signatureValid,
setFullName,
setSignature,
setSignatureValid,
} = useRequiredDocumentSigningContext();
const [hasFinishedInit, setHasFinishedInit] = useState(false); const [hasFinishedInit, setHasFinishedInit] = useState(false);
const [hasDocumentLoaded, setHasDocumentLoaded] = useState(false); const [hasDocumentLoaded, setHasDocumentLoaded] = useState(false);
@@ -129,10 +121,6 @@ export const EmbedSignDocumentClientPage = ({
const onCompleteClick = async () => { const onCompleteClick = async () => {
try { try {
if (hasSignatureField && !signatureValid) {
return;
}
const valid = validateFieldsInserted(fieldsRequiringValidation); const valid = validateFieldsInserted(fieldsRequiringValidation);
if (!valid) { if (!valid) {
@@ -432,34 +420,16 @@ export const EmbedSignDocumentClientPage = ({
<Trans>Signature</Trans> <Trans>Signature</Trans>
</Label> </Label>
<Card className="mt-2" gradient degrees={-120}> <SignaturePadDialog
<CardContent className="p-0"> className="mt-2"
<SignaturePad
className="h-44 w-full"
disabled={isThrottled || isSubmitting} disabled={isThrottled || isSubmitting}
defaultValue={signature ?? undefined} disableAnimation
onChange={(value) => { value={signature ?? ''}
setSignature(value); onChange={(v) => setSignature(v ?? '')}
}} typedSignatureEnabled={metadata?.typedSignatureEnabled}
onValidityChange={(isValid) => { uploadSignatureEnabled={metadata?.uploadSignatureEnabled}
setSignatureValid(isValid); drawSignatureEnabled={metadata?.drawSignatureEnabled}
}}
allowTypedSignature={Boolean(
metadata &&
'typedSignatureEnabled' in metadata &&
metadata.typedSignatureEnabled,
)}
/> />
</CardContent>
</Card>
{hasSignatureField && !signatureValid && (
<div className="text-destructive mt-2 text-sm">
<Trans>
Signature is too small. Please provide a more complete signature.
</Trans>
</div>
)}
</div> </div>
)} )}
</> </>
@@ -477,9 +447,7 @@ export const EmbedSignDocumentClientPage = ({
) : ( ) : (
<Button <Button
className={allowDocumentRejection ? 'col-start-2' : 'col-span-2'} className={allowDocumentRejection ? 'col-start-2' : 'col-span-2'}
disabled={ disabled={isThrottled}
isThrottled || (!isAssistantMode && hasSignatureField && !signatureValid)
}
loading={isSubmitting} loading={isSubmitting}
onClick={() => throttledOnCompleteClick()} onClick={() => throttledOnCompleteClick()}
> >

View File

@@ -19,12 +19,15 @@ import {
} from '@documenso/ui/primitives/form/form'; } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input'; import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label'; import { Label } from '@documenso/ui/primitives/label';
import { SignaturePad } from '@documenso/ui/primitives/signature-pad'; import { SignaturePadDialog } from '@documenso/ui/primitives/signature-pad/signature-pad-dialog';
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
export const ZProfileFormSchema = z.object({ export const ZProfileFormSchema = z.object({
name: z.string().trim().min(1, { message: 'Please enter a valid name.' }), name: z
signature: z.string().min(1, 'Signature Pad cannot be empty'), .string()
.trim()
.min(1, { message: msg`Please enter a valid name.`.id }),
signature: z.string().min(1, { message: msg`Signature Pad cannot be empty.`.id }),
}); });
export const ZTwoFactorAuthTokenSchema = z.object({ export const ZTwoFactorAuthTokenSchema = z.object({
@@ -109,22 +112,20 @@ export const ProfileForm = ({ className }: ProfileFormProps) => {
</Label> </Label>
<Input id="email" type="email" className="bg-muted mt-2" value={user.email} disabled /> <Input id="email" type="email" className="bg-muted mt-2" value={user.email} disabled />
</div> </div>
<FormField <FormField
control={form.control} control={form.control}
name="signature" name="signature"
render={({ field: { onChange } }) => ( render={({ field: { onChange, value } }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
<Trans>Signature</Trans> <Trans>Signature</Trans>
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<SignaturePad <SignaturePadDialog
className="h-44 w-full"
disabled={isSubmitting} disabled={isSubmitting}
containerClassName={cn('rounded-lg border bg-background')} value={value}
defaultValue={user.signature ?? undefined}
onChange={(v) => onChange(v ?? '')} onChange={(v) => onChange(v ?? '')}
allowTypedSignature={true}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@@ -134,7 +135,7 @@ export const ProfileForm = ({ className }: ProfileFormProps) => {
</fieldset> </fieldset>
<Button type="submit" loading={isSubmitting} className="self-end"> <Button type="submit" loading={isSubmitting} className="self-end">
{isSubmitting ? <Trans>Updating profile...</Trans> : <Trans>Update profile</Trans>} <Trans>Update profile</Trans>
</Button> </Button>
</form> </form>
</Form> </Form>

View File

@@ -30,7 +30,7 @@ import {
} from '@documenso/ui/primitives/form/form'; } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input'; import { Input } from '@documenso/ui/primitives/input';
import { PasswordInput } from '@documenso/ui/primitives/password-input'; import { PasswordInput } from '@documenso/ui/primitives/password-input';
import { SignaturePad } from '@documenso/ui/primitives/signature-pad'; import { SignaturePadDialog } from '@documenso/ui/primitives/signature-pad/signature-pad-dialog';
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
import { UserProfileSkeleton } from '~/components/general/user-profile-skeleton'; import { UserProfileSkeleton } from '~/components/general/user-profile-skeleton';
@@ -353,16 +353,15 @@ export const SignUpForm = ({
<FormField <FormField
control={form.control} control={form.control}
name="signature" name="signature"
render={({ field: { onChange } }) => ( render={({ field: { onChange, value } }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
<Trans>Sign Here</Trans> <Trans>Sign Here</Trans>
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<SignaturePad <SignaturePadDialog
className="h-36 w-full"
disabled={isSubmitting} disabled={isSubmitting}
containerClassName="mt-2 rounded-lg border bg-background" value={value}
onChange={(v) => onChange(v ?? '')} onChange={(v) => onChange(v ?? '')}
/> />
</FormControl> </FormControl>
@@ -531,6 +530,27 @@ export const SignUpForm = ({
</div> </div>
</form> </form>
</Form> </Form>
<p className="text-muted-foreground mt-6 text-xs">
<Trans>
By proceeding, you agree to our{' '}
<Link
to="https://documen.so/terms"
target="_blank"
className="text-documenso-700 duration-200 hover:opacity-70"
>
Terms of Service
</Link>{' '}
and{' '}
<Link
to="https://documen.so/privacy"
target="_blank"
className="text-documenso-700 duration-200 hover:opacity-70"
>
Privacy Policy
</Link>
.
</Trans>
</p>
</div> </div>
</div> </div>
); );

View File

@@ -308,7 +308,7 @@ export function TeamBrandingPreferencesForm({ team, settings }: TeamBrandingPref
<div className="flex flex-row justify-end space-x-4"> <div className="flex flex-row justify-end space-x-4">
<Button type="submit" loading={form.formState.isSubmitting}> <Button type="submit" loading={form.formState.isSubmitting}>
<Trans>Save</Trans> <Trans>Update</Trans>
</Button> </Button>
</div> </div>
</fieldset> </fieldset>

View File

@@ -8,12 +8,15 @@ import { useForm } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import { useSession } from '@documenso/lib/client-only/providers/session'; import { useSession } from '@documenso/lib/client-only/providers/session';
import { DOCUMENT_SIGNATURE_TYPES, DocumentSignatureType } from '@documenso/lib/constants/document';
import { import {
SUPPORTED_LANGUAGES, SUPPORTED_LANGUAGES,
SUPPORTED_LANGUAGE_CODES, SUPPORTED_LANGUAGE_CODES,
isValidLanguageCode, isValidLanguageCode,
} from '@documenso/lib/constants/i18n'; } from '@documenso/lib/constants/i18n';
import { extractTeamSignatureSettings } from '@documenso/lib/utils/teams';
import { trpc } from '@documenso/trpc/react'; import { trpc } from '@documenso/trpc/react';
import { DocumentSignatureSettingsTooltip } from '@documenso/ui/components/document/document-signature-settings-tooltip';
import { Alert } from '@documenso/ui/primitives/alert'; import { Alert } from '@documenso/ui/primitives/alert';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { import {
@@ -23,7 +26,9 @@ import {
FormField, FormField,
FormItem, FormItem,
FormLabel, FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form'; } from '@documenso/ui/primitives/form/form';
import { MultiSelectCombobox } from '@documenso/ui/primitives/multi-select-combobox';
import { import {
Select, Select,
SelectContent, SelectContent,
@@ -38,8 +43,10 @@ const ZTeamDocumentPreferencesFormSchema = z.object({
documentVisibility: z.nativeEnum(DocumentVisibility), documentVisibility: z.nativeEnum(DocumentVisibility),
documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES), documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES),
includeSenderDetails: z.boolean(), includeSenderDetails: z.boolean(),
typedSignatureEnabled: z.boolean(),
includeSigningCertificate: z.boolean(), includeSigningCertificate: z.boolean(),
signatureTypes: z.array(z.nativeEnum(DocumentSignatureType)).min(1, {
message: msg`At least one signature type must be enabled`.id,
}),
}); });
type TTeamDocumentPreferencesFormSchema = z.infer<typeof ZTeamDocumentPreferencesFormSchema>; type TTeamDocumentPreferencesFormSchema = z.infer<typeof ZTeamDocumentPreferencesFormSchema>;
@@ -69,8 +76,8 @@ export const TeamDocumentPreferencesForm = ({
? settings?.documentLanguage ? settings?.documentLanguage
: 'en', : 'en',
includeSenderDetails: settings?.includeSenderDetails ?? false, includeSenderDetails: settings?.includeSenderDetails ?? false,
typedSignatureEnabled: settings?.typedSignatureEnabled ?? true,
includeSigningCertificate: settings?.includeSigningCertificate ?? true, includeSigningCertificate: settings?.includeSigningCertificate ?? true,
signatureTypes: extractTeamSignatureSettings(settings),
}, },
resolver: zodResolver(ZTeamDocumentPreferencesFormSchema), resolver: zodResolver(ZTeamDocumentPreferencesFormSchema),
}); });
@@ -84,7 +91,7 @@ export const TeamDocumentPreferencesForm = ({
documentLanguage, documentLanguage,
includeSenderDetails, includeSenderDetails,
includeSigningCertificate, includeSigningCertificate,
typedSignatureEnabled, signatureTypes,
} = data; } = data;
await updateTeamDocumentPreferences({ await updateTeamDocumentPreferences({
@@ -93,8 +100,10 @@ export const TeamDocumentPreferencesForm = ({
documentVisibility, documentVisibility,
documentLanguage, documentLanguage,
includeSenderDetails, includeSenderDetails,
typedSignatureEnabled,
includeSigningCertificate, includeSigningCertificate,
typedSignatureEnabled: signatureTypes.includes(DocumentSignatureType.TYPE),
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
}, },
}); });
@@ -190,6 +199,44 @@ export const TeamDocumentPreferencesForm = ({
)} )}
/> />
<FormField
control={form.control}
name="signatureTypes"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel className="flex flex-row items-center">
<Trans>Default Signature Settings</Trans>
<DocumentSignatureSettingsTooltip />
</FormLabel>
<FormControl>
<MultiSelectCombobox
options={Object.values(DOCUMENT_SIGNATURE_TYPES).map((option) => ({
label: _(option.label),
value: option.value,
}))}
selectedValues={field.value}
onChange={field.onChange}
className="bg-background w-full"
enableSearch={false}
emptySelectionPlaceholder="Select signature types"
testId="signature-types-combobox"
/>
</FormControl>
{form.formState.errors.signatureTypes ? (
<FormMessage />
) : (
<FormDescription>
<Trans>
Controls which signatures are allowed to be used when signing a document.
</Trans>
</FormDescription>
)}
</FormItem>
)}
/>
<FormField <FormField
control={form.control} control={form.control}
name="includeSenderDetails" name="includeSenderDetails"
@@ -238,36 +285,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 <FormField
control={form.control} control={form.control}
name="includeSigningCertificate" name="includeSigningCertificate"
@@ -301,7 +318,7 @@ export const TeamDocumentPreferencesForm = ({
<div className="flex flex-row justify-end space-x-4"> <div className="flex flex-row justify-end space-x-4">
<Button type="submit" loading={form.formState.isSubmitting}> <Button type="submit" loading={form.formState.isSubmitting}>
<Trans>Save</Trans> <Trans>Update</Trans>
</Button> </Button>
</div> </div>
</fieldset> </fieldset>

View File

@@ -24,7 +24,6 @@ import type {
} from '@documenso/trpc/server/field-router/schema'; } from '@documenso/trpc/server/field-router/schema';
import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip'; import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import { import {
DocumentFlowFormContainerContent, DocumentFlowFormContainerContent,
DocumentFlowFormContainerFooter, DocumentFlowFormContainerFooter,
@@ -35,7 +34,7 @@ import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/ty
import { ElementVisible } from '@documenso/ui/primitives/element-visible'; import { ElementVisible } from '@documenso/ui/primitives/element-visible';
import { Input } from '@documenso/ui/primitives/input'; import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label'; import { Label } from '@documenso/ui/primitives/label';
import { SignaturePad } from '@documenso/ui/primitives/signature-pad'; import { SignaturePadDialog } from '@documenso/ui/primitives/signature-pad/signature-pad-dialog';
import { useStep } from '@documenso/ui/primitives/stepper'; import { useStep } from '@documenso/ui/primitives/stepper';
import { DocumentSigningCheckboxField } from '~/components/general/document-signing/document-signing-checkbox-field'; import { DocumentSigningCheckboxField } from '~/components/general/document-signing/document-signing-checkbox-field';
@@ -73,8 +72,7 @@ export const DirectTemplateSigningForm = ({
template, template,
onSubmit, onSubmit,
}: DirectTemplateSigningFormProps) => { }: DirectTemplateSigningFormProps) => {
const { fullName, signature, signatureValid, setFullName, setSignature } = const { fullName, signature, setFullName, setSignature } = useRequiredDocumentSigningContext();
useRequiredDocumentSigningContext();
const [localFields, setLocalFields] = useState<DirectTemplateLocalField[]>(directRecipientFields); const [localFields, setLocalFields] = useState<DirectTemplateLocalField[]>(directRecipientFields);
const [validateUninsertedFields, setValidateUninsertedFields] = useState(false); const [validateUninsertedFields, setValidateUninsertedFields] = useState(false);
@@ -135,8 +133,6 @@ export const DirectTemplateSigningForm = ({
); );
}; };
const hasSignatureField = localFields.some((field) => field.type === FieldType.SIGNATURE);
const uninsertedFields = useMemo(() => { const uninsertedFields = useMemo(() => {
return sortFieldsByPosition(localFields.filter((field) => !field.inserted)); return sortFieldsByPosition(localFields.filter((field) => !field.inserted));
}, [localFields]); }, [localFields]);
@@ -149,10 +145,6 @@ export const DirectTemplateSigningForm = ({
const handleSubmit = async () => { const handleSubmit = async () => {
setValidateUninsertedFields(true); setValidateUninsertedFields(true);
if (hasSignatureField && !signatureValid) {
return;
}
const isFieldsValid = validateFieldsInserted(localFields); const isFieldsValid = validateFieldsInserted(localFields);
if (!isFieldsValid) { if (!isFieldsValid) {
@@ -240,6 +232,8 @@ export const DirectTemplateSigningForm = ({
onSignField={onSignField} onSignField={onSignField}
onUnsignField={onUnsignField} onUnsignField={onUnsignField}
typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled} typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled}
uploadSignatureEnabled={template.templateMeta?.uploadSignatureEnabled}
drawSignatureEnabled={template.templateMeta?.drawSignatureEnabled}
/> />
)) ))
.with(FieldType.INITIALS, () => ( .with(FieldType.INITIALS, () => (
@@ -384,19 +378,15 @@ export const DirectTemplateSigningForm = ({
<Trans>Signature</Trans> <Trans>Signature</Trans>
</Label> </Label>
<Card className="mt-2" gradient degrees={-120}> <SignaturePadDialog
<CardContent className="p-0"> className="mt-2"
<SignaturePad
className="h-44 w-full"
disabled={isSubmitting} disabled={isSubmitting}
defaultValue={signature ?? undefined} value={signature ?? ''}
onChange={(value) => { onChange={(value) => setSignature(value)}
setSignature(value); typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled}
}} uploadSignatureEnabled={template.templateMeta?.uploadSignatureEnabled}
allowTypedSignature={template.templateMeta?.typedSignatureEnabled} drawSignatureEnabled={template.templateMeta?.drawSignatureEnabled}
/> />
</CardContent>
</Card>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,9 +1,12 @@
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans } from '@lingui/react/macro'; import { Trans } from '@lingui/react/macro';
import type { Field } from '@prisma/client'; import type { Field } from '@prisma/client';
import { RecipientRole } from '@prisma/client'; import { RecipientRole } from '@prisma/client';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import { z } from 'zod';
import { fieldsContainUnsignedRequiredField } from '@documenso/lib/utils/advanced-fields-helpers'; import { fieldsContainUnsignedRequiredField } from '@documenso/lib/utils/advanced-fields-helpers';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
@@ -14,6 +17,15 @@ import {
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from '@documenso/ui/primitives/dialog'; } from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { DocumentSigningDisclosure } from '~/components/general/document-signing/document-signing-disclosure'; import { DocumentSigningDisclosure } from '~/components/general/document-signing/document-signing-disclosure';
@@ -22,10 +34,22 @@ export type DocumentSigningCompleteDialogProps = {
documentTitle: string; documentTitle: string;
fields: Field[]; fields: Field[];
fieldsValidated: () => void | Promise<void>; fieldsValidated: () => void | Promise<void>;
onSignatureComplete: () => void | Promise<void>; onSignatureComplete: (nextSigner?: { name: string; email: string }) => void | Promise<void>;
role: RecipientRole; role: RecipientRole;
disabled?: boolean; disabled?: boolean;
allowDictateNextSigner?: boolean;
defaultNextSigner?: {
name: string;
email: string;
}; };
};
const ZNextSignerFormSchema = z.object({
name: z.string().min(1, 'Name is required'),
email: z.string().email('Invalid email address'),
});
type TNextSignerFormSchema = z.infer<typeof ZNextSignerFormSchema>;
export const DocumentSigningCompleteDialog = ({ export const DocumentSigningCompleteDialog = ({
isSubmitting, isSubmitting,
@@ -35,19 +59,54 @@ export const DocumentSigningCompleteDialog = ({
onSignatureComplete, onSignatureComplete,
role, role,
disabled = false, disabled = false,
allowDictateNextSigner = false,
defaultNextSigner,
}: DocumentSigningCompleteDialogProps) => { }: DocumentSigningCompleteDialogProps) => {
const [showDialog, setShowDialog] = useState(false); const [showDialog, setShowDialog] = useState(false);
const [isEditingNextSigner, setIsEditingNextSigner] = useState(false);
const form = useForm<TNextSignerFormSchema>({
resolver: allowDictateNextSigner ? zodResolver(ZNextSignerFormSchema) : undefined,
defaultValues: {
name: defaultNextSigner?.name ?? '',
email: defaultNextSigner?.email ?? '',
},
});
const isComplete = useMemo(() => !fieldsContainUnsignedRequiredField(fields), [fields]); const isComplete = useMemo(() => !fieldsContainUnsignedRequiredField(fields), [fields]);
const handleOpenChange = (open: boolean) => { const handleOpenChange = (open: boolean) => {
if (isSubmitting || !isComplete) { if (form.formState.isSubmitting || !isComplete) {
return; return;
} }
if (open) {
form.reset({
name: defaultNextSigner?.name ?? '',
email: defaultNextSigner?.email ?? '',
});
}
setIsEditingNextSigner(false);
setShowDialog(open); setShowDialog(open);
}; };
const onFormSubmit = async (data: TNextSignerFormSchema) => {
console.log('data', data);
console.log('form.formState.errors', form.formState.errors);
try {
if (allowDictateNextSigner && data.name && data.email) {
await onSignatureComplete({ name: data.name, email: data.email });
} else {
await onSignatureComplete();
}
} catch (error) {
console.error('Error completing signature:', error);
}
};
const isNextSignerValid = !allowDictateNextSigner || (form.watch('name') && form.watch('email'));
return ( return (
<Dialog open={showDialog} onOpenChange={handleOpenChange}> <Dialog open={showDialog} onOpenChange={handleOpenChange}>
<DialogTrigger asChild> <DialogTrigger asChild>
@@ -71,6 +130,9 @@ export const DocumentSigningCompleteDialog = ({
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent>
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset disabled={form.formState.isSubmitting} className="border-none p-0">
<DialogTitle> <DialogTitle>
<div className="text-foreground text-xl font-semibold"> <div className="text-foreground text-xl font-semibold">
{match(role) {match(role)
@@ -143,27 +205,95 @@ export const DocumentSigningCompleteDialog = ({
))} ))}
</div> </div>
{allowDictateNextSigner && (
<div className="mt-4 flex flex-col gap-4">
{!isEditingNextSigner && (
<div>
<p className="text-muted-foreground text-sm">
The next recipient to sign this document will be{' '}
<span className="font-semibold">{form.watch('name')}</span> (
<span className="font-semibold">{form.watch('email')}</span>).
</p>
<Button
type="button"
className="mt-2"
variant="outline"
size="sm"
onClick={() => setIsEditingNextSigner((prev) => !prev)}
>
<Trans>Update Recipient</Trans>
</Button>
</div>
)}
{isEditingNextSigner && (
<div className="flex flex-col gap-4 md:flex-row">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>
<Trans>Name</Trans>
</FormLabel>
<FormControl>
<Input
{...field}
className="mt-2"
placeholder="Enter the next signer's name"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>
<Trans>Email</Trans>
</FormLabel>
<FormControl>
<Input
{...field}
type="email"
className="mt-2"
placeholder="Enter the next signer's email"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
)}
</div>
)}
<DocumentSigningDisclosure className="mt-4" /> <DocumentSigningDisclosure className="mt-4" />
<DialogFooter> <DialogFooter className="mt-4">
<div className="flex w-full flex-1 flex-nowrap gap-4"> <div className="flex w-full flex-1 flex-nowrap gap-4">
<Button <Button
type="button" type="button"
className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10" className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10"
variant="secondary" variant="secondary"
onClick={() => { onClick={() => setShowDialog(false)}
setShowDialog(false); disabled={form.formState.isSubmitting}
}}
> >
<Trans>Cancel</Trans> <Trans>Cancel</Trans>
</Button> </Button>
<Button <Button
type="button" type="submit"
className="flex-1" className="flex-1"
disabled={!isComplete} disabled={!isComplete || !isNextSignerValid}
loading={isSubmitting} loading={form.formState.isSubmitting}
onClick={onSignatureComplete}
> >
{match(role) {match(role)
.with(RecipientRole.VIEWER, () => <Trans>Mark as Viewed</Trans>) .with(RecipientRole.VIEWER, () => <Trans>Mark as Viewed</Trans>)
@@ -175,6 +305,9 @@ export const DocumentSigningCompleteDialog = ({
</Button> </Button>
</div> </div>
</DialogFooter> </DialogFooter>
</fieldset>
</form>
</Form>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );

View File

@@ -1,11 +1,13 @@
import { useId, useMemo, useState } from 'react'; import { useId, useMemo, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro'; import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react'; import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro'; import { Trans } from '@lingui/react/macro';
import { type Field, FieldType, type Recipient, RecipientRole } from '@prisma/client'; import { type Field, FieldType, type Recipient, RecipientRole } from '@prisma/client';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router';
import { z } from 'zod';
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics'; import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
import { useOptionalSession } from '@documenso/lib/client-only/providers/session'; import { useOptionalSession } from '@documenso/lib/client-only/providers/session';
@@ -18,17 +20,26 @@ import { trpc } from '@documenso/trpc/react';
import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip'; import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip';
import { cn } from '@documenso/ui/lib/utils'; import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import { Input } from '@documenso/ui/primitives/input'; import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label'; import { Label } from '@documenso/ui/primitives/label';
import { RadioGroup, RadioGroupItem } from '@documenso/ui/primitives/radio-group'; import { RadioGroup, RadioGroupItem } from '@documenso/ui/primitives/radio-group';
import { SignaturePad } from '@documenso/ui/primitives/signature-pad'; import { SignaturePadDialog } from '@documenso/ui/primitives/signature-pad/signature-pad-dialog';
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
import { AssistantConfirmationDialog } from '../../dialogs/assistant-confirmation-dialog'; import {
AssistantConfirmationDialog,
type NextSigner,
} from '../../dialogs/assistant-confirmation-dialog';
import { DocumentSigningCompleteDialog } from './document-signing-complete-dialog'; import { DocumentSigningCompleteDialog } from './document-signing-complete-dialog';
import { useRequiredDocumentSigningContext } from './document-signing-provider'; import { useRequiredDocumentSigningContext } from './document-signing-provider';
export const ZSigningFormSchema = z.object({
name: z.string().min(1, 'Name is required').optional(),
email: z.string().email('Invalid email address').optional(),
});
export type TSigningFormSchema = z.infer<typeof ZSigningFormSchema>;
export type DocumentSigningFormProps = { export type DocumentSigningFormProps = {
document: DocumentAndSender; document: DocumentAndSender;
recipient: Recipient; recipient: Recipient;
@@ -59,8 +70,7 @@ export const DocumentSigningForm = ({
const assistantSignersId = useId(); const assistantSignersId = useId();
const { fullName, signature, setFullName, setSignature, signatureValid, setSignatureValid } = const { fullName, signature, setFullName, setSignature } = useRequiredDocumentSigningContext();
useRequiredDocumentSigningContext();
const [validateUninsertedFields, setValidateUninsertedFields] = useState(false); const [validateUninsertedFields, setValidateUninsertedFields] = useState(false);
const [isConfirmationDialogOpen, setIsConfirmationDialogOpen] = useState(false); const [isConfirmationDialogOpen, setIsConfirmationDialogOpen] = useState(false);
@@ -75,7 +85,9 @@ export const DocumentSigningForm = ({
}, },
}); });
const { handleSubmit, formState } = useForm(); const { handleSubmit, formState } = useForm<TSigningFormSchema>({
resolver: zodResolver(ZSigningFormSchema),
});
// Keep the loading state going if successful since the redirect may take some time. // Keep the loading state going if successful since the redirect may take some time.
const isSubmitting = formState.isSubmitting || formState.isSubmitSuccessful; const isSubmitting = formState.isSubmitting || formState.isSubmitSuccessful;
@@ -100,20 +112,32 @@ export const DocumentSigningForm = ({
validateFieldsInserted(fieldsRequiringValidation); validateFieldsInserted(fieldsRequiringValidation);
}; };
const onFormSubmit = async () => { const onFormSubmit = async (data: TSigningFormSchema) => {
try {
setValidateUninsertedFields(true); setValidateUninsertedFields(true);
const isFieldsValid = validateFieldsInserted(fieldsRequiringValidation); const isFieldsValid = validateFieldsInserted(fieldsRequiringValidation);
if (hasSignatureField && !signatureValid) {
return;
}
if (!isFieldsValid) { if (!isFieldsValid) {
return; return;
} }
await completeDocument(); const nextSigner =
data.email && data.name
? {
email: data.email,
name: data.name,
}
: undefined;
await completeDocument(undefined, nextSigner);
} catch (error) {
toast({
title: 'Error',
description: error instanceof Error ? error.message : 'An error occurred while signing',
variant: 'destructive',
});
}
}; };
const onAssistantFormSubmit = () => { const onAssistantFormSubmit = () => {
@@ -124,11 +148,11 @@ export const DocumentSigningForm = ({
setIsConfirmationDialogOpen(true); setIsConfirmationDialogOpen(true);
}; };
const handleAssistantConfirmDialogSubmit = async () => { const handleAssistantConfirmDialogSubmit = async (nextSigner?: NextSigner) => {
setIsAssistantSubmitting(true); setIsAssistantSubmitting(true);
try { try {
await completeDocument(); await completeDocument(undefined, nextSigner);
} catch (err) { } catch (err) {
toast({ toast({
title: 'Error', title: 'Error',
@@ -141,12 +165,18 @@ export const DocumentSigningForm = ({
} }
}; };
const completeDocument = async (authOptions?: TRecipientActionAuth) => { const completeDocument = async (
await completeDocumentWithToken({ authOptions?: TRecipientActionAuth,
nextSigner?: { email: string; name: string },
) => {
const payload = {
token: recipient.token, token: recipient.token,
documentId: document.id, documentId: document.id,
authOptions, authOptions,
}); ...(nextSigner?.email && nextSigner?.name ? { nextSigner } : {}),
};
await completeDocumentWithToken(payload);
analytics.capture('App: Recipient has completed signing', { analytics.capture('App: Recipient has completed signing', {
signerId: recipient.id, signerId: recipient.id,
@@ -161,6 +191,31 @@ export const DocumentSigningForm = ({
} }
}; };
const nextRecipient = useMemo(() => {
if (
!document.documentMeta?.signingOrder ||
document.documentMeta.signingOrder !== 'SEQUENTIAL'
) {
return undefined;
}
const sortedRecipients = allRecipients.sort((a, b) => {
// Sort by signingOrder first (nulls last), then by id
if (a.signingOrder === null && b.signingOrder === null) return a.id - b.id;
if (a.signingOrder === null) return 1;
if (b.signingOrder === null) return -1;
if (a.signingOrder === b.signingOrder) return a.id - b.id;
return a.signingOrder - b.signingOrder;
});
const currentIndex = sortedRecipients.findIndex((r) => r.id === recipient.id);
return currentIndex !== -1 && currentIndex < sortedRecipients.length - 1
? sortedRecipients[currentIndex + 1]
: undefined;
}, [document.documentMeta?.signingOrder, allRecipients, recipient.id]);
console.log('nextRecipient', nextRecipient);
return ( return (
<div <div
className={cn( className={cn(
@@ -210,12 +265,19 @@ export const DocumentSigningForm = ({
<DocumentSigningCompleteDialog <DocumentSigningCompleteDialog
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
onSignatureComplete={handleSubmit(onFormSubmit)}
documentTitle={document.title} documentTitle={document.title}
fields={fields} fields={fields}
fieldsValidated={fieldsValidated} fieldsValidated={fieldsValidated}
onSignatureComplete={async (nextSigner) => {
await completeDocument(undefined, nextSigner);
}}
role={recipient.role} role={recipient.role}
disabled={!isRecipientsTurn} allowDictateNextSigner={document.documentMeta?.allowDictateNextSigner}
defaultNextSigner={
nextRecipient
? { name: nextRecipient.name, email: nextRecipient.email }
: undefined
}
/> />
</div> </div>
</div> </div>
@@ -306,6 +368,14 @@ export const DocumentSigningForm = ({
onClose={() => !isAssistantSubmitting && setIsConfirmationDialogOpen(false)} onClose={() => !isAssistantSubmitting && setIsConfirmationDialogOpen(false)}
onConfirm={handleAssistantConfirmDialogSubmit} onConfirm={handleAssistantConfirmDialogSubmit}
isSubmitting={isAssistantSubmitting} isSubmitting={isAssistantSubmitting}
allowDictateNextSigner={
nextRecipient && document.documentMeta?.allowDictateNextSigner
}
defaultNextSigner={
nextRecipient
? { name: nextRecipient.name, email: nextRecipient.email }
: undefined
}
/> />
</form> </form>
</> </>
@@ -347,37 +417,21 @@ export const DocumentSigningForm = ({
<Trans>Signature</Trans> <Trans>Signature</Trans>
</Label> </Label>
<Card className="mt-2" gradient degrees={-120}> <SignaturePadDialog
<CardContent className="p-0"> className="mt-2"
<SignaturePad
className="h-44 w-full"
disabled={isSubmitting} disabled={isSubmitting}
defaultValue={signature ?? undefined} value={signature ?? ''}
onValidityChange={(isValid) => { onChange={(v) => setSignature(v ?? '')}
setSignatureValid(isValid); typedSignatureEnabled={document.documentMeta?.typedSignatureEnabled}
}} uploadSignatureEnabled={document.documentMeta?.uploadSignatureEnabled}
onChange={(value) => { drawSignatureEnabled={document.documentMeta?.drawSignatureEnabled}
if (signatureValid) {
setSignature(value);
}
}}
allowTypedSignature={document.documentMeta?.typedSignatureEnabled}
/> />
</CardContent>
</Card>
{!signatureValid && (
<div className="text-destructive mt-2 text-sm">
<Trans>
Signature is too small. Please provide a more complete signature.
</Trans>
</div>
)}
</div> </div>
)} )}
</div> </div>
</fieldset>
<div className="flex flex-col gap-4 md:flex-row"> <div className="mt-6 flex flex-col gap-4 md:flex-row">
<Button <Button
type="button" type="button"
className="dark:bg-muted dark:hover:bg-muted/80 w-full bg-black/5 hover:bg-black/10" className="dark:bg-muted dark:hover:bg-muted/80 w-full bg-black/5 hover:bg-black/10"
@@ -390,16 +444,25 @@ export const DocumentSigningForm = ({
</Button> </Button>
<DocumentSigningCompleteDialog <DocumentSigningCompleteDialog
isSubmitting={isSubmitting} isSubmitting={isSubmitting || isAssistantSubmitting}
onSignatureComplete={handleSubmit(onFormSubmit)}
documentTitle={document.title} documentTitle={document.title}
fields={fields} fields={fields}
fieldsValidated={fieldsValidated} fieldsValidated={fieldsValidated}
role={recipient.role}
disabled={!isRecipientsTurn} disabled={!isRecipientsTurn}
onSignatureComplete={async (nextSigner) => {
await completeDocument(undefined, nextSigner);
}}
role={recipient.role}
allowDictateNextSigner={
nextRecipient && document.documentMeta?.allowDictateNextSigner
}
defaultNextSigner={
nextRecipient
? { name: nextRecipient.name, email: nextRecipient.email }
: undefined
}
/> />
</div> </div>
</fieldset>
</form> </form>
</> </>
)} )}

View File

@@ -40,9 +40,9 @@ import { DocumentReadOnlyFields } from '~/components/general/document/document-r
import { DocumentSigningRecipientProvider } from './document-signing-recipient-provider'; import { DocumentSigningRecipientProvider } from './document-signing-recipient-provider';
export type SigningPageViewProps = { export type DocumentSigningPageViewProps = {
document: DocumentAndSender;
recipient: RecipientWithFields; recipient: RecipientWithFields;
document: DocumentAndSender;
fields: Field[]; fields: Field[];
completedFields: CompletedField[]; completedFields: CompletedField[];
isRecipientsTurn: boolean; isRecipientsTurn: boolean;
@@ -50,13 +50,13 @@ export type SigningPageViewProps = {
}; };
export const DocumentSigningPageView = ({ export const DocumentSigningPageView = ({
document,
recipient, recipient,
document,
fields, fields,
completedFields, completedFields,
isRecipientsTurn, isRecipientsTurn,
allRecipients = [], allRecipients = [],
}: SigningPageViewProps) => { }: DocumentSigningPageViewProps) => {
const { documentData, documentMeta } = document; const { documentData, documentMeta } = document;
const [selectedSignerId, setSelectedSignerId] = useState<number | null>(allRecipients?.[0]?.id); const [selectedSignerId, setSelectedSignerId] = useState<number | null>(allRecipients?.[0]?.id);
@@ -177,6 +177,8 @@ export const DocumentSigningPageView = ({
key={field.id} key={field.id}
field={field} field={field}
typedSignatureEnabled={documentMeta?.typedSignatureEnabled} typedSignatureEnabled={documentMeta?.typedSignatureEnabled}
uploadSignatureEnabled={documentMeta?.uploadSignatureEnabled}
drawSignatureEnabled={documentMeta?.drawSignatureEnabled}
/> />
)) ))
.with(FieldType.INITIALS, () => ( .with(FieldType.INITIALS, () => (

View File

@@ -1,4 +1,6 @@
import { createContext, useContext, useEffect, useState } from 'react'; import { createContext, useContext, useState } from 'react';
import { isBase64Image } from '@documenso/lib/constants/signatures';
export type DocumentSigningContextValue = { export type DocumentSigningContextValue = {
fullName: string; fullName: string;
@@ -7,8 +9,6 @@ export type DocumentSigningContextValue = {
setEmail: (_value: string) => void; setEmail: (_value: string) => void;
signature: string | null; signature: string | null;
setSignature: (_value: string | null) => void; setSignature: (_value: string | null) => void;
signatureValid: boolean;
setSignatureValid: (_valid: boolean) => void;
}; };
const DocumentSigningContext = createContext<DocumentSigningContextValue | null>(null); const DocumentSigningContext = createContext<DocumentSigningContextValue | null>(null);
@@ -31,6 +31,9 @@ export interface DocumentSigningProviderProps {
fullName?: string | null; fullName?: string | null;
email?: string | null; email?: string | null;
signature?: string | null; signature?: string | null;
typedSignatureEnabled?: boolean;
uploadSignatureEnabled?: boolean;
drawSignatureEnabled?: boolean;
children: React.ReactNode; children: React.ReactNode;
} }
@@ -38,18 +41,31 @@ export const DocumentSigningProvider = ({
fullName: initialFullName, fullName: initialFullName,
email: initialEmail, email: initialEmail,
signature: initialSignature, signature: initialSignature,
typedSignatureEnabled = true,
uploadSignatureEnabled = true,
drawSignatureEnabled = true,
children, children,
}: DocumentSigningProviderProps) => { }: DocumentSigningProviderProps) => {
const [fullName, setFullName] = useState(initialFullName || ''); const [fullName, setFullName] = useState(initialFullName || '');
const [email, setEmail] = useState(initialEmail || ''); const [email, setEmail] = useState(initialEmail || '');
const [signature, setSignature] = useState(initialSignature || null);
const [signatureValid, setSignatureValid] = useState(true);
useEffect(() => { // Ensure the user signature doesn't show up if it's not allowed.
if (initialSignature) { const [signature, setSignature] = useState(
setSignature(initialSignature); (() => {
const sig = initialSignature || '';
const isBase64 = isBase64Image(sig);
if (isBase64 && (uploadSignatureEnabled || drawSignatureEnabled)) {
return sig;
} }
}, [initialSignature]);
if (!isBase64 && typedSignatureEnabled) {
return sig;
}
return null;
})(),
);
return ( return (
<DocumentSigningContext.Provider <DocumentSigningContext.Provider
@@ -60,8 +76,6 @@ export const DocumentSigningProvider = ({
setEmail, setEmail,
signature, signature,
setSignature, setSignature,
signatureValid,
setSignatureValid,
}} }}
> >
{children} {children}

View File

@@ -31,10 +31,7 @@ import { Textarea } from '@documenso/ui/primitives/textarea';
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
const ZRejectDocumentFormSchema = z.object({ const ZRejectDocumentFormSchema = z.object({
reason: z reason: z.string().max(500, msg`Reason must be less than 500 characters`),
.string()
.min(5, msg`Please provide a reason`)
.max(500, msg`Reason must be less than 500 characters`),
}); });
type TRejectDocumentFormSchema = z.infer<typeof ZRejectDocumentFormSchema>; type TRejectDocumentFormSchema = z.infer<typeof ZRejectDocumentFormSchema>;

View File

@@ -17,7 +17,6 @@ import type {
} from '@documenso/trpc/server/field-router/schema'; } from '@documenso/trpc/server/field-router/schema';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { Dialog, DialogContent, DialogFooter, DialogTitle } from '@documenso/ui/primitives/dialog'; import { Dialog, DialogContent, DialogFooter, DialogTitle } from '@documenso/ui/primitives/dialog';
import { Label } from '@documenso/ui/primitives/label';
import { SignaturePad } from '@documenso/ui/primitives/signature-pad'; import { SignaturePad } from '@documenso/ui/primitives/signature-pad';
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
@@ -29,11 +28,14 @@ import { useRequiredDocumentSigningContext } from './document-signing-provider';
import { useDocumentSigningRecipientContext } from './document-signing-recipient-provider'; import { useDocumentSigningRecipientContext } from './document-signing-recipient-provider';
type SignatureFieldState = 'empty' | 'signed-image' | 'signed-text'; type SignatureFieldState = 'empty' | 'signed-image' | 'signed-text';
export type DocumentSigningSignatureFieldProps = { export type DocumentSigningSignatureFieldProps = {
field: FieldWithSignature; field: FieldWithSignature;
onSignField?: (value: TSignFieldWithTokenMutationSchema) => Promise<void> | void; onSignField?: (value: TSignFieldWithTokenMutationSchema) => Promise<void> | void;
onUnsignField?: (value: TRemovedSignedFieldWithTokenMutationSchema) => Promise<void> | void; onUnsignField?: (value: TRemovedSignedFieldWithTokenMutationSchema) => Promise<void> | void;
typedSignatureEnabled?: boolean; typedSignatureEnabled?: boolean;
uploadSignatureEnabled?: boolean;
drawSignatureEnabled?: boolean;
}; };
export const DocumentSigningSignatureField = ({ export const DocumentSigningSignatureField = ({
@@ -41,6 +43,8 @@ export const DocumentSigningSignatureField = ({
onSignField, onSignField,
onUnsignField, onUnsignField,
typedSignatureEnabled, typedSignatureEnabled,
uploadSignatureEnabled,
drawSignatureEnabled,
}: DocumentSigningSignatureFieldProps) => { }: DocumentSigningSignatureFieldProps) => {
const { _ } = useLingui(); const { _ } = useLingui();
const { toast } = useToast(); const { toast } = useToast();
@@ -52,12 +56,8 @@ export const DocumentSigningSignatureField = ({
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const [fontSize, setFontSize] = useState(2); const [fontSize, setFontSize] = useState(2);
const { const { signature: providedSignature, setSignature: setProvidedSignature } =
signature: providedSignature, useRequiredDocumentSigningContext();
setSignature: setProvidedSignature,
signatureValid,
setSignatureValid,
} = useRequiredDocumentSigningContext();
const { executeActionAuthProcedure } = useRequiredDocumentSigningAuthContext(); const { executeActionAuthProcedure } = useRequiredDocumentSigningAuthContext();
@@ -89,7 +89,7 @@ export const DocumentSigningSignatureField = ({
}, [field.inserted, signature?.signatureImageAsBase64]); }, [field.inserted, signature?.signatureImageAsBase64]);
const onPreSign = () => { const onPreSign = () => {
if (!providedSignature || !signatureValid) { if (!providedSignature) {
setShowSignatureModal(true); setShowSignatureModal(true);
return false; return false;
} }
@@ -102,6 +102,7 @@ export const DocumentSigningSignatureField = ({
const onDialogSignClick = () => { const onDialogSignClick = () => {
setShowSignatureModal(false); setShowSignatureModal(false);
setProvidedSignature(localSignature); setProvidedSignature(localSignature);
if (!localSignature) { if (!localSignature) {
return; return;
} }
@@ -116,14 +117,14 @@ export const DocumentSigningSignatureField = ({
try { try {
const value = signature || providedSignature; const value = signature || providedSignature;
if (!value || (signature && !signatureValid)) { if (!value) {
setShowSignatureModal(true); setShowSignatureModal(true);
return; return;
} }
const isTypedSignature = !value.startsWith('data:image'); const isTypedSignature = !value.startsWith('data:image');
if (isTypedSignature && !typedSignatureEnabled) { if (isTypedSignature && typedSignatureEnabled === false) {
toast({ toast({
title: _(msg`Error`), title: _(msg`Error`),
description: _(msg`Typed signatures are not allowed. Please draw your signature.`), description: _(msg`Typed signatures are not allowed. Please draw your signature.`),
@@ -275,29 +276,14 @@ export const DocumentSigningSignatureField = ({
</Trans> </Trans>
</DialogTitle> </DialogTitle>
<div className="">
<Label htmlFor="signature">
<Trans>Signature</Trans>
</Label>
<div className="border-border mt-2 rounded-md border">
<SignaturePad <SignaturePad
id="signature" className="mt-2"
className="h-44 w-full" value={localSignature ?? ''}
onChange={(value) => setLocalSignature(value)} onChange={({ value }) => setLocalSignature(value)}
allowTypedSignature={typedSignatureEnabled} typedSignatureEnabled={typedSignatureEnabled}
onValidityChange={(isValid) => { uploadSignatureEnabled={uploadSignatureEnabled}
setSignatureValid(isValid); drawSignatureEnabled={drawSignatureEnabled}
}}
/> />
</div>
{!signatureValid && (
<div className="text-destructive mt-2 text-sm">
<Trans>Signature is too small. Please provide a more complete signature.</Trans>
</div>
)}
</div>
<DocumentSigningDisclosure /> <DocumentSigningDisclosure />
@@ -317,7 +303,7 @@ export const DocumentSigningSignatureField = ({
<Button <Button
type="button" type="button"
className="flex-1" className="flex-1"
disabled={!localSignature || !signatureValid} disabled={!localSignature}
onClick={() => onDialogSignClick()} onClick={() => onDialogSignClick()}
> >
<Trans>Sign</Trans> <Trans>Sign</Trans>

View File

@@ -5,6 +5,7 @@ import { useLingui } from '@lingui/react';
import { DocumentDistributionMethod, DocumentStatus } from '@prisma/client'; import { DocumentDistributionMethod, DocumentStatus } from '@prisma/client';
import { useNavigate, useSearchParams } from 'react-router'; import { useNavigate, useSearchParams } from 'react-router';
import { DocumentSignatureType } from '@documenso/lib/constants/document';
import { isValidLanguageCode } from '@documenso/lib/constants/i18n'; import { isValidLanguageCode } from '@documenso/lib/constants/i18n';
import { import {
DO_NOT_INVALIDATE_QUERY_ON_MUTATION, DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
@@ -71,7 +72,7 @@ export const DocumentEditForm = ({
const { recipients, fields } = document; const { recipients, fields } = document;
const { mutateAsync: updateDocument } = trpc.document.setSettingsForDocument.useMutation({ const { mutateAsync: updateDocument } = trpc.document.updateDocument.useMutation({
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION, ...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
onSuccess: (newData) => { onSuccess: (newData) => {
utils.document.getDocumentWithDetailsById.setData( utils.document.getDocumentWithDetailsById.setData(
@@ -174,7 +175,7 @@ export const DocumentEditForm = ({
const onAddSettingsFormSubmit = async (data: TAddSettingsFormSchema) => { const onAddSettingsFormSubmit = async (data: TAddSettingsFormSchema) => {
try { try {
const { timezone, dateFormat, redirectUrl, language } = data.meta; const { timezone, dateFormat, redirectUrl, language, signatureTypes } = data.meta;
await updateDocument({ await updateDocument({
documentId: document.id, documentId: document.id,
@@ -190,6 +191,9 @@ export const DocumentEditForm = ({
dateFormat, dateFormat,
redirectUrl, redirectUrl,
language: isValidLanguageCode(language) ? language : undefined, language: isValidLanguageCode(language) ? language : undefined,
typedSignatureEnabled: signatureTypes.includes(DocumentSignatureType.TYPE),
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
}, },
}); });
@@ -213,6 +217,13 @@ export const DocumentEditForm = ({
signingOrder: data.signingOrder, signingOrder: data.signingOrder,
}), }),
updateDocument({
documentId: document.id,
meta: {
allowDictateNextSigner: data.allowDictateNextSigner,
},
}),
setRecipients({ setRecipients({
documentId: document.id, documentId: document.id,
recipients: data.signers.map((signer) => ({ recipients: data.signers.map((signer) => ({
@@ -242,14 +253,6 @@ export const DocumentEditForm = ({
fields: data.fields, fields: data.fields,
}); });
await updateDocument({
documentId: document.id,
meta: {
typedSignatureEnabled: data.typedSignatureEnabled,
},
});
// Clear all field data from localStorage // Clear all field data from localStorage
for (let i = 0; i < localStorage.length; i++) { for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i); const key = localStorage.key(i);
@@ -365,6 +368,7 @@ export const DocumentEditForm = ({
documentFlow={documentFlow.signers} documentFlow={documentFlow.signers}
recipients={recipients} recipients={recipients}
signingOrder={document.documentMeta?.signingOrder} signingOrder={document.documentMeta?.signingOrder}
allowDictateNextSigner={document.documentMeta?.allowDictateNextSigner}
fields={fields} fields={fields}
isDocumentEnterprise={isDocumentEnterprise} isDocumentEnterprise={isDocumentEnterprise}
onSubmit={onAddSignersFormSubmit} onSubmit={onAddSignersFormSubmit}
@@ -378,7 +382,6 @@ export const DocumentEditForm = ({
fields={fields} fields={fields}
onSubmit={onAddFieldsFormSubmit} onSubmit={onAddFieldsFormSubmit}
isDocumentPdfLoaded={isDocumentPdfLoaded} isDocumentPdfLoaded={isDocumentPdfLoaded}
typedSignatureEnabled={document.documentMeta?.typedSignatureEnabled}
teamId={team?.id} teamId={team?.id}
/> />

View File

@@ -4,6 +4,7 @@ import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react'; import { useLingui } from '@lingui/react';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router';
import { DocumentSignatureType } from '@documenso/lib/constants/document';
import { isValidLanguageCode } from '@documenso/lib/constants/i18n'; import { isValidLanguageCode } from '@documenso/lib/constants/i18n';
import { import {
DO_NOT_INVALIDATE_QUERY_ON_MUTATION, DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
@@ -124,6 +125,8 @@ export const TemplateEditForm = ({
}); });
const onAddSettingsFormSubmit = async (data: TAddTemplateSettingsFormSchema) => { const onAddSettingsFormSubmit = async (data: TAddTemplateSettingsFormSchema) => {
const { signatureTypes } = data.meta;
try { try {
await updateTemplateSettings({ await updateTemplateSettings({
templateId: template.id, templateId: template.id,
@@ -136,6 +139,9 @@ export const TemplateEditForm = ({
}, },
meta: { meta: {
...data.meta, ...data.meta,
typedSignatureEnabled: signatureTypes.includes(DocumentSignatureType.TYPE),
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
language: isValidLanguageCode(data.meta.language) ? data.meta.language : undefined, language: isValidLanguageCode(data.meta.language) ? data.meta.language : undefined,
}, },
}); });
@@ -161,6 +167,7 @@ export const TemplateEditForm = ({
templateId: template.id, templateId: template.id,
meta: { meta: {
signingOrder: data.signingOrder, signingOrder: data.signingOrder,
allowDictateNextSigner: data.allowDictateNextSigner,
}, },
}), }),
@@ -187,13 +194,6 @@ export const TemplateEditForm = ({
fields: data.fields, fields: data.fields,
}); });
await updateTemplateSettings({
templateId: template.id,
meta: {
typedSignatureEnabled: data.typedSignatureEnabled,
},
});
// Clear all field data from localStorage // Clear all field data from localStorage
for (let i = 0; i < localStorage.length; i++) { for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i); const key = localStorage.key(i);
@@ -271,6 +271,7 @@ export const TemplateEditForm = ({
recipients={recipients} recipients={recipients}
fields={fields} fields={fields}
signingOrder={template.templateMeta?.signingOrder} signingOrder={template.templateMeta?.signingOrder}
allowDictateNextSigner={template.templateMeta?.allowDictateNextSigner}
templateDirectLink={template.directLink} templateDirectLink={template.directLink}
onSubmit={onAddTemplatePlaceholderFormSubmit} onSubmit={onAddTemplatePlaceholderFormSubmit}
isEnterprise={isEnterprise} isEnterprise={isEnterprise}
@@ -284,7 +285,6 @@ export const TemplateEditForm = ({
fields={fields} fields={fields}
onSubmit={onAddFieldsFormSubmit} onSubmit={onAddFieldsFormSubmit}
teamId={team?.id} teamId={team?.id}
typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled}
/> />
</Stepper> </Stepper>
</DocumentFlowFormContainer> </DocumentFlowFormContainer>

View File

@@ -6,6 +6,7 @@ import { redirect } from 'react-router';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import { UAParser } from 'ua-parser-js'; import { UAParser } from 'ua-parser-js';
import { isDocumentPlatform } from '@documenso/ee/server-only/util/is-document-platform';
import { APP_I18N_OPTIONS, ZSupportedLanguageCodeSchema } from '@documenso/lib/constants/i18n'; import { APP_I18N_OPTIONS, ZSupportedLanguageCodeSchema } from '@documenso/lib/constants/i18n';
import { import {
RECIPIENT_ROLES_DESCRIPTION, RECIPIENT_ROLES_DESCRIPTION,
@@ -59,6 +60,8 @@ export async function loader({ request }: Route.LoaderArgs) {
throw redirect('/'); throw redirect('/');
} }
const isPlatformDocument = await isDocumentPlatform(document);
const documentLanguage = ZSupportedLanguageCodeSchema.parse(document.documentMeta?.language); const documentLanguage = ZSupportedLanguageCodeSchema.parse(document.documentMeta?.language);
const auditLogs = await getDocumentCertificateAuditLogs({ const auditLogs = await getDocumentCertificateAuditLogs({
@@ -70,6 +73,7 @@ export async function loader({ request }: Route.LoaderArgs) {
return { return {
document, document,
documentLanguage, documentLanguage,
isPlatformDocument,
auditLogs, auditLogs,
messages, messages,
}; };
@@ -85,7 +89,7 @@ export async function loader({ request }: Route.LoaderArgs) {
* Update: Maybe <Trans> tags work now after RR7 migration. * Update: Maybe <Trans> tags work now after RR7 migration.
*/ */
export default function SigningCertificate({ loaderData }: Route.ComponentProps) { export default function SigningCertificate({ loaderData }: Route.ComponentProps) {
const { document, documentLanguage, auditLogs, messages } = loaderData; const { document, documentLanguage, isPlatformDocument, auditLogs, messages } = loaderData;
const { i18n, _ } = useLingui(); const { i18n, _ } = useLingui();
@@ -337,6 +341,7 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
</CardContent> </CardContent>
</Card> </Card>
{isPlatformDocument && (
<div className="my-8 flex-row-reverse"> <div className="my-8 flex-row-reverse">
<div className="flex items-end justify-end gap-x-4"> <div className="flex items-end justify-end gap-x-4">
<p className="flex-shrink-0 text-sm font-medium print:text-xs"> <p className="flex-shrink-0 text-sm font-medium print:text-xs">
@@ -346,6 +351,7 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
<BrandingLogo className="max-h-6 print:max-h-4" /> <BrandingLogo className="max-h-6 print:max-h-4" />
</div> </div>
</div> </div>
)}
</div> </div>
); );
} }

View File

@@ -79,7 +79,14 @@ export default function DirectTemplatePage() {
const { template, directTemplateRecipient } = data; const { template, directTemplateRecipient } = data;
return ( return (
<DocumentSigningProvider email={user?.email} fullName={user?.name} signature={user?.signature}> <DocumentSigningProvider
email={user?.email}
fullName={user?.name}
signature={user?.signature}
typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled}
uploadSignatureEnabled={template.templateMeta?.uploadSignatureEnabled}
drawSignatureEnabled={template.templateMeta?.drawSignatureEnabled}
>
<DocumentSigningAuthProvider <DocumentSigningAuthProvider
documentAuthOptions={template.authOptions} documentAuthOptions={template.authOptions}
recipient={directTemplateRecipient} recipient={directTemplateRecipient}

View File

@@ -1,5 +1,5 @@
import { Trans } from '@lingui/react/macro'; import { Trans } from '@lingui/react/macro';
import { DocumentStatus, RecipientRole, SigningStatus } from '@prisma/client'; import { DocumentSigningOrder, DocumentStatus, RecipientRole, SigningStatus } from '@prisma/client';
import { Clock8 } from 'lucide-react'; import { Clock8 } from 'lucide-react';
import { Link, redirect } from 'react-router'; import { Link, redirect } from 'react-router';
import { getOptionalLoaderContext } from 'server/utils/get-loader-session'; import { getOptionalLoaderContext } from 'server/utils/get-loader-session';
@@ -13,6 +13,7 @@ import { viewedDocument } from '@documenso/lib/server-only/document/viewed-docum
import { getCompletedFieldsForToken } from '@documenso/lib/server-only/field/get-completed-fields-for-token'; import { getCompletedFieldsForToken } from '@documenso/lib/server-only/field/get-completed-fields-for-token';
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token'; import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
import { getIsRecipientsTurnToSign } from '@documenso/lib/server-only/recipient/get-is-recipient-turn'; import { getIsRecipientsTurnToSign } from '@documenso/lib/server-only/recipient/get-is-recipient-turn';
import { getNextPendingRecipient } from '@documenso/lib/server-only/recipient/get-next-pending-recipient';
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token'; import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
import { getRecipientSignatures } from '@documenso/lib/server-only/recipient/get-recipient-signatures'; import { getRecipientSignatures } from '@documenso/lib/server-only/recipient/get-recipient-signatures';
import { getRecipientsForAssistant } from '@documenso/lib/server-only/recipient/get-recipients-for-assistant'; import { getRecipientsForAssistant } from '@documenso/lib/server-only/recipient/get-recipients-for-assistant';
@@ -72,7 +73,24 @@ export async function loader({ params, request }: Route.LoaderArgs) {
? await getRecipientsForAssistant({ ? await getRecipientsForAssistant({
token, token,
}) })
: []; : [recipient];
if (
document.documentMeta?.signingOrder === DocumentSigningOrder.SEQUENTIAL &&
recipient.role !== RecipientRole.ASSISTANT
) {
const nextPendingRecipient = await getNextPendingRecipient({
documentId: document.id,
currentRecipientId: recipient.id,
});
if (nextPendingRecipient) {
allRecipients.push({
...nextPendingRecipient,
fields: [],
});
}
}
const { derivedRecipientAccessAuth } = extractDocumentAuthMethods({ const { derivedRecipientAccessAuth } = extractDocumentAuthMethods({
documentAuth: document.authOptions, documentAuth: document.authOptions,
@@ -215,6 +233,9 @@ export default function SigningPage() {
email={recipient.email} email={recipient.email}
fullName={user?.email === recipient.email ? user?.name : recipient.name} fullName={user?.email === recipient.email ? user?.name : recipient.name}
signature={user?.email === recipient.email ? user?.signature : undefined} signature={user?.email === recipient.email ? user?.signature : undefined}
typedSignatureEnabled={document.documentMeta?.typedSignatureEnabled}
uploadSignatureEnabled={document.documentMeta?.uploadSignatureEnabled}
drawSignatureEnabled={document.documentMeta?.drawSignatureEnabled}
> >
<DocumentSigningAuthProvider <DocumentSigningAuthProvider
documentAuthOptions={document.authOptions} documentAuthOptions={document.authOptions}

View File

@@ -131,7 +131,14 @@ export default function EmbedDirectTemplatePage() {
} = useSuperLoaderData<typeof loader>(); } = useSuperLoaderData<typeof loader>();
return ( return (
<DocumentSigningProvider email={user?.email} fullName={user?.name} signature={user?.signature}> <DocumentSigningProvider
email={user?.email}
fullName={user?.name}
signature={user?.signature}
typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled}
uploadSignatureEnabled={template.templateMeta?.uploadSignatureEnabled}
drawSignatureEnabled={template.templateMeta?.drawSignatureEnabled}
>
<DocumentSigningAuthProvider <DocumentSigningAuthProvider
documentAuthOptions={template.authOptions} documentAuthOptions={template.authOptions}
recipient={recipient} recipient={recipient}

View File

@@ -156,6 +156,9 @@ export default function EmbedSignDocumentPage() {
email={recipient.email} email={recipient.email}
fullName={user?.email === recipient.email ? user?.name : recipient.name} fullName={user?.email === recipient.email ? user?.name : recipient.name}
signature={user?.email === recipient.email ? user?.signature : undefined} signature={user?.email === recipient.email ? user?.signature : undefined}
typedSignatureEnabled={document.documentMeta?.typedSignatureEnabled}
uploadSignatureEnabled={document.documentMeta?.uploadSignatureEnabled}
drawSignatureEnabled={document.documentMeta?.drawSignatureEnabled}
> >
<DocumentSigningAuthProvider <DocumentSigningAuthProvider
documentAuthOptions={document.authOptions} documentAuthOptions={document.authOptions}

View File

@@ -99,5 +99,6 @@
"vite": "^6.1.0", "vite": "^6.1.0",
"vite-plugin-babel-macros": "^1.0.6", "vite-plugin-babel-macros": "^1.0.6",
"vite-tsconfig-paths": "^5.1.4" "vite-tsconfig-paths": "^5.1.4"
} },
"version": "1.10.0-rc.1"
} }

View File

@@ -8,6 +8,7 @@ command -v docker >/dev/null 2>&1 || {
SCRIPT_DIR="$(readlink -f "$(dirname "$0")")" SCRIPT_DIR="$(readlink -f "$(dirname "$0")")"
MONOREPO_ROOT="$(readlink -f "$SCRIPT_DIR/../")" MONOREPO_ROOT="$(readlink -f "$SCRIPT_DIR/../")"
# Get Git information
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')" APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
GIT_SHA="$(git rev-parse HEAD)" GIT_SHA="$(git rev-parse HEAD)"
@@ -15,12 +16,39 @@ echo "Building docker image for monorepo at $MONOREPO_ROOT"
echo "App version: $APP_VERSION" echo "App version: $APP_VERSION"
echo "Git SHA: $GIT_SHA" echo "Git SHA: $GIT_SHA"
# Build with temporary base tag
docker build -f "$SCRIPT_DIR/Dockerfile" \ docker build -f "$SCRIPT_DIR/Dockerfile" \
--progress=plain \ --progress=plain \
-t "documenso/documenso:latest" \ -t "documenso-base" \
-t "documenso/documenso:$GIT_SHA" \
-t "documenso/documenso:$APP_VERSION" \
-t "ghcr.io/documenso/documenso:latest" \
-t "ghcr.io/documenso/documenso:$GIT_SHA" \
-t "ghcr.io/documenso/documenso:$APP_VERSION" \
"$MONOREPO_ROOT" "$MONOREPO_ROOT"
# Handle repository tagging
if [ ! -z "$DOCKER_REPOSITORY" ]; then
echo "Using custom repository: $DOCKER_REPOSITORY"
# Add tags for custom repository
docker tag "documenso-base" "$DOCKER_REPOSITORY:latest"
docker tag "documenso-base" "$DOCKER_REPOSITORY:$GIT_SHA"
# Add version tag if available
if [ ! -z "$APP_VERSION" ] && [ "$APP_VERSION" != "undefined" ]; then
docker tag "documenso-base" "$DOCKER_REPOSITORY:$APP_VERSION"
fi
else
echo "Using default repositories: dockerhub and ghcr.io"
# Add tags for both default repositories
docker tag "documenso-base" "documenso/documenso:latest"
docker tag "documenso-base" "documenso/documenso:$GIT_SHA"
docker tag "documenso-base" "ghcr.io/documenso/documenso:latest"
docker tag "documenso-base" "ghcr.io/documenso/documenso:$GIT_SHA"
# Add version tags if available
if [ ! -z "$APP_VERSION" ] && [ "$APP_VERSION" != "undefined" ]; then
docker tag "documenso-base" "documenso/documenso:$APP_VERSION"
docker tag "documenso-base" "ghcr.io/documenso/documenso:$APP_VERSION"
fi
fi
# Remove the temporary base tag
docker rmi "documenso-base"

View File

@@ -9,11 +9,11 @@ SCRIPT_DIR="$(readlink -f "$(dirname "$0")")"
MONOREPO_ROOT="$(readlink -f "$SCRIPT_DIR/../")" MONOREPO_ROOT="$(readlink -f "$SCRIPT_DIR/../")"
# Get the platform from environment variable or set to linux/amd64 if not set # Get the platform from environment variable or set to linux/amd64 if not set
# quote the string to prevent word splitting
if [ -z "$PLATFORM" ]; then if [ -z "$PLATFORM" ]; then
PLATFORM="linux/amd64" PLATFORM="linux/amd64"
fi fi
# Get Git information
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')" APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
GIT_SHA="$(git rev-parse HEAD)" GIT_SHA="$(git rev-parse HEAD)"
@@ -21,14 +21,41 @@ echo "Building docker image for monorepo at $MONOREPO_ROOT"
echo "App version: $APP_VERSION" echo "App version: $APP_VERSION"
echo "Git SHA: $GIT_SHA" echo "Git SHA: $GIT_SHA"
# Build with temporary base tag
docker buildx build \ docker buildx build \
-f "$SCRIPT_DIR/Dockerfile" \ -f "$SCRIPT_DIR/Dockerfile" \
--platform=$PLATFORM \ --platform=$PLATFORM \
--progress=plain \ --progress=plain \
-t "documenso/documenso:latest" \ -t "documenso-base" \
-t "documenso/documenso:$GIT_SHA" \
-t "documenso/documenso:$APP_VERSION" \
-t "ghcr.io/documenso/documenso:latest" \
-t "ghcr.io/documenso/documenso:$GIT_SHA" \
-t "ghcr.io/documenso/documenso:$APP_VERSION" \
"$MONOREPO_ROOT" "$MONOREPO_ROOT"
# Handle repository tagging
if [ ! -z "$DOCKER_REPOSITORY" ]; then
echo "Using custom repository: $DOCKER_REPOSITORY"
# Add tags for custom repository
docker tag "documenso-base" "$DOCKER_REPOSITORY:latest"
docker tag "documenso-base" "$DOCKER_REPOSITORY:$GIT_SHA"
# Add version tag if available
if [ ! -z "$APP_VERSION" ] && [ "$APP_VERSION" != "undefined" ]; then
docker tag "documenso-base" "$DOCKER_REPOSITORY:$APP_VERSION"
fi
else
echo "Using default repositories: dockerhub and ghcr.io"
# Add tags for both default repositories
docker tag "documenso-base" "documenso/documenso:latest"
docker tag "documenso-base" "documenso/documenso:$GIT_SHA"
docker tag "documenso-base" "ghcr.io/documenso/documenso:latest"
docker tag "documenso-base" "ghcr.io/documenso/documenso:$GIT_SHA"
# Add version tags if available
if [ ! -z "$APP_VERSION" ] && [ "$APP_VERSION" != "undefined" ]; then
docker tag "documenso-base" "documenso/documenso:$APP_VERSION"
docker tag "documenso-base" "ghcr.io/documenso/documenso:$APP_VERSION"
fi
fi
# Remove the temporary base tag
docker rmi "documenso-base"

5
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "@documenso/root", "name": "@documenso/root",
"version": "1.9.0-rc.11", "version": "1.10.0-rc.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@documenso/root", "name": "@documenso/root",
"version": "1.9.0-rc.11", "version": "1.10.0-rc.1",
"workspaces": [ "workspaces": [
"apps/*", "apps/*",
"packages/*" "packages/*"
@@ -95,6 +95,7 @@
}, },
"apps/remix": { "apps/remix": {
"name": "@documenso/remix", "name": "@documenso/remix",
"version": "1.10.0-rc.1",
"dependencies": { "dependencies": {
"@documenso/api": "*", "@documenso/api": "*",
"@documenso/assets": "*", "@documenso/assets": "*",

View File

@@ -1,6 +1,6 @@
{ {
"private": true, "private": true,
"version": "1.9.0-rc.11", "version": "1.10.0-rc.1",
"scripts": { "scripts": {
"build": "turbo run build", "build": "turbo run build",
"dev": "turbo run dev --filter=@documenso/remix", "dev": "turbo run dev --filter=@documenso/remix",

View File

@@ -323,8 +323,11 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
dateFormat: dateFormat?.value, dateFormat: dateFormat?.value,
redirectUrl: body.meta.redirectUrl, redirectUrl: body.meta.redirectUrl,
signingOrder: body.meta.signingOrder, signingOrder: body.meta.signingOrder,
allowDictateNextSigner: body.meta.allowDictateNextSigner,
language: body.meta.language, language: body.meta.language,
typedSignatureEnabled: body.meta.typedSignatureEnabled, typedSignatureEnabled: body.meta.typedSignatureEnabled,
uploadSignatureEnabled: body.meta.uploadSignatureEnabled,
drawSignatureEnabled: body.meta.drawSignatureEnabled,
distributionMethod: body.meta.distributionMethod, distributionMethod: body.meta.distributionMethod,
emailSettings: body.meta.emailSettings, emailSettings: body.meta.emailSettings,
requestMetadata: metadata, requestMetadata: metadata,

View File

@@ -155,8 +155,11 @@ export const ZCreateDocumentMutationSchema = z.object({
}), }),
redirectUrl: z.string(), redirectUrl: z.string(),
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(), signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
allowDictateNextSigner: z.boolean().optional(),
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(), language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
typedSignatureEnabled: z.boolean().optional().default(true), typedSignatureEnabled: z.boolean().optional().default(true),
uploadSignatureEnabled: z.boolean().optional().default(true),
drawSignatureEnabled: z.boolean().optional().default(true),
distributionMethod: z.nativeEnum(DocumentDistributionMethod).optional(), distributionMethod: z.nativeEnum(DocumentDistributionMethod).optional(),
emailSettings: ZDocumentEmailSettingsSchema.optional(), emailSettings: ZDocumentEmailSettingsSchema.optional(),
}) })
@@ -218,6 +221,7 @@ export const ZCreateDocumentFromTemplateMutationSchema = z.object({
dateFormat: z.string(), dateFormat: z.string(),
redirectUrl: z.string(), redirectUrl: z.string(),
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(), signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
allowDictateNextSigner: z.boolean().optional(),
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(), language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
}) })
.partial() .partial()
@@ -285,9 +289,12 @@ export const ZGenerateDocumentFromTemplateMutationSchema = z.object({
dateFormat: z.string(), dateFormat: z.string(),
redirectUrl: ZUrlSchema, redirectUrl: ZUrlSchema,
signingOrder: z.nativeEnum(DocumentSigningOrder), signingOrder: z.nativeEnum(DocumentSigningOrder),
allowDictateNextSigner: z.boolean(),
language: z.enum(SUPPORTED_LANGUAGE_CODES), language: z.enum(SUPPORTED_LANGUAGE_CODES),
distributionMethod: z.nativeEnum(DocumentDistributionMethod), distributionMethod: z.nativeEnum(DocumentDistributionMethod),
typedSignatureEnabled: z.boolean(), typedSignatureEnabled: z.boolean(),
uploadSignatureEnabled: z.boolean(),
drawSignatureEnabled: z.boolean(),
emailSettings: ZDocumentEmailSettingsSchema, emailSettings: ZDocumentEmailSettingsSchema,
}) })
.partial() .partial()

View File

@@ -210,7 +210,7 @@ test('[DOCUMENT_AUTH]: should allow field signing when required for recipient au
}), }),
}, },
], ],
fields: [FieldType.DATE], fields: [FieldType.DATE, FieldType.SIGNATURE],
}); });
for (const recipient of recipients) { for (const recipient of recipients) {
@@ -246,7 +246,9 @@ test('[DOCUMENT_AUTH]: should allow field signing when required for recipient au
}); });
} }
if (fields.some((field) => field.type === FieldType.SIGNATURE)) {
await signSignaturePad(page); await signSignaturePad(page);
}
for (const field of fields) { for (const field of fields) {
await page.locator(`#field-${field.id}`).getByRole('button').click(); await page.locator(`#field-${field.id}`).getByRole('button').click();
@@ -307,7 +309,7 @@ test('[DOCUMENT_AUTH]: should allow field signing when required for recipient an
}), }),
}, },
], ],
fields: [FieldType.DATE], fields: [FieldType.DATE, FieldType.SIGNATURE],
updateDocumentOptions: { updateDocumentOptions: {
authOptions: createDocumentAuthOptions({ authOptions: createDocumentAuthOptions({
globalAccessAuth: null, globalAccessAuth: null,
@@ -349,7 +351,9 @@ test('[DOCUMENT_AUTH]: should allow field signing when required for recipient an
}); });
} }
if (fields.some((field) => field.type === FieldType.SIGNATURE)) {
await signSignaturePad(page); await signSignaturePad(page);
}
for (const field of fields) { for (const field of fields) {
await page.locator(`#field-${field.id}`).getByRole('button').click(); await page.locator(`#field-${field.id}`).getByRole('button').click();

View File

@@ -0,0 +1,390 @@
import { expect, test } from '@playwright/test';
import {
DocumentSigningOrder,
DocumentStatus,
FieldType,
RecipientRole,
SigningStatus,
} from '@prisma/client';
import { prisma } from '@documenso/prisma';
import { seedPendingDocumentWithFullFields } from '@documenso/prisma/seed/documents';
import { seedUser } from '@documenso/prisma/seed/users';
import { signDirectSignaturePad, signSignaturePad } from '../fixtures/signature';
test('[NEXT_RECIPIENT_DICTATION]: should allow updating next recipient when dictation is enabled', async ({
page,
}) => {
const user = await seedUser();
const firstSigner = await seedUser();
const secondSigner = await seedUser();
const thirdSigner = await seedUser();
const { recipients, document } = await seedPendingDocumentWithFullFields({
owner: user,
recipients: [firstSigner, secondSigner, thirdSigner],
recipientsCreateOptions: [{ signingOrder: 1 }, { signingOrder: 2 }, { signingOrder: 3 }],
updateDocumentOptions: {
documentMeta: {
upsert: {
create: {
allowDictateNextSigner: true,
signingOrder: DocumentSigningOrder.SEQUENTIAL,
},
update: {
allowDictateNextSigner: true,
signingOrder: DocumentSigningOrder.SEQUENTIAL,
},
},
},
},
});
const firstRecipient = recipients[0];
const { token, fields } = firstRecipient;
const signUrl = `/sign/${token}`;
await page.goto(signUrl);
await expect(page.getByRole('heading', { name: 'Sign Document' })).toBeVisible();
await signSignaturePad(page);
// Fill in all fields
for (const field of fields) {
await page.locator(`#field-${field.id}`).getByRole('button').click();
if (field.type === FieldType.TEXT) {
await page.locator('#custom-text').fill('TEXT');
await page.getByRole('button', { name: 'Save' }).click();
}
await expect(page.locator(`#field-${field.id}`)).toHaveAttribute('data-inserted', 'true');
}
// Complete signing and update next recipient
await page.getByRole('button', { name: 'Complete' }).click();
// Verify next recipient info is shown
await expect(page.getByRole('dialog')).toBeVisible();
await expect(page.getByText('The next recipient to sign this document will be')).toBeVisible();
// Update next recipient
await page.locator('button').filter({ hasText: 'Update Recipient' }).click();
await page.waitForTimeout(1000);
// Use dialog context to ensure we're targeting the correct form fields
const dialog = page.getByRole('dialog');
await dialog.getByLabel('Name').fill('New Recipient');
await dialog.getByLabel('Email').fill('new.recipient@example.com');
// Submit and verify completion
await page.getByRole('button', { name: 'Sign' }).click();
await page.waitForURL(`${signUrl}/complete`);
// Verify document and recipient states
const updatedDocument = await prisma.document.findUniqueOrThrow({
where: { id: document.id },
include: {
recipients: {
orderBy: { signingOrder: 'asc' },
},
},
});
// Document should still be pending as there are more recipients
expect(updatedDocument.status).toBe(DocumentStatus.PENDING);
// First recipient should be completed
const updatedFirstRecipient = updatedDocument.recipients[0];
expect(updatedFirstRecipient.signingStatus).toBe(SigningStatus.SIGNED);
// Second recipient should be the new recipient
const updatedSecondRecipient = updatedDocument.recipients[1];
expect(updatedSecondRecipient.name).toBe('New Recipient');
expect(updatedSecondRecipient.email).toBe('new.recipient@example.com');
expect(updatedSecondRecipient.signingOrder).toBe(2);
expect(updatedSecondRecipient.signingStatus).toBe(SigningStatus.NOT_SIGNED);
});
test('[NEXT_RECIPIENT_DICTATION]: should not show dictation UI when disabled', async ({ page }) => {
const user = await seedUser();
const firstSigner = await seedUser();
const secondSigner = await seedUser();
const { recipients, document } = await seedPendingDocumentWithFullFields({
owner: user,
recipients: [firstSigner, secondSigner],
recipientsCreateOptions: [{ signingOrder: 1 }, { signingOrder: 2 }],
updateDocumentOptions: {
documentMeta: {
upsert: {
create: {
allowDictateNextSigner: false,
signingOrder: DocumentSigningOrder.SEQUENTIAL,
},
update: {
allowDictateNextSigner: false,
signingOrder: DocumentSigningOrder.SEQUENTIAL,
},
},
},
},
});
const firstRecipient = recipients[0];
const { token, fields } = firstRecipient;
const signUrl = `/sign/${token}`;
await page.goto(signUrl);
await expect(page.getByRole('heading', { name: 'Sign Document' })).toBeVisible();
await signSignaturePad(page);
// Fill in all fields
for (const field of fields) {
await page.locator(`#field-${field.id}`).getByRole('button').click();
if (field.type === FieldType.TEXT) {
await page.locator('#custom-text').fill('TEXT');
await page.getByRole('button', { name: 'Save' }).click();
}
await expect(page.locator(`#field-${field.id}`)).toHaveAttribute('data-inserted', 'true');
}
// Complete signing
await page.getByRole('button', { name: 'Complete' }).click();
// Verify next recipient UI is not shown
await expect(
page.getByText('The next recipient to sign this document will be'),
).not.toBeVisible();
await expect(page.getByRole('button', { name: 'Update Recipient' })).not.toBeVisible();
// Submit and verify completion
await page.getByRole('button', { name: 'Sign' }).click();
await page.waitForURL(`${signUrl}/complete`);
// Verify document and recipient states
const updatedDocument = await prisma.document.findUniqueOrThrow({
where: { id: document.id },
include: {
recipients: {
orderBy: { signingOrder: 'asc' },
},
},
});
// Document should still be pending as there are more recipients
expect(updatedDocument.status).toBe(DocumentStatus.PENDING);
// First recipient should be completed
const updatedFirstRecipient = updatedDocument.recipients[0];
expect(updatedFirstRecipient.signingStatus).toBe(SigningStatus.SIGNED);
// Second recipient should remain unchanged
const updatedSecondRecipient = updatedDocument.recipients[1];
expect(updatedSecondRecipient.email).toBe(secondSigner.email);
expect(updatedSecondRecipient.signingOrder).toBe(2);
expect(updatedSecondRecipient.signingStatus).toBe(SigningStatus.NOT_SIGNED);
});
test('[NEXT_RECIPIENT_DICTATION]: should work with parallel signing flow', async ({ page }) => {
const user = await seedUser();
const firstSigner = await seedUser();
const secondSigner = await seedUser();
const { recipients, document } = await seedPendingDocumentWithFullFields({
owner: user,
recipients: [firstSigner, secondSigner],
recipientsCreateOptions: [{ signingOrder: 1 }, { signingOrder: 2 }],
updateDocumentOptions: {
documentMeta: {
upsert: {
create: {
allowDictateNextSigner: false,
signingOrder: DocumentSigningOrder.PARALLEL,
},
update: {
allowDictateNextSigner: false,
signingOrder: DocumentSigningOrder.PARALLEL,
},
},
},
},
});
// Test both recipients can sign in parallel
for (const recipient of recipients) {
const { token, fields } = recipient;
const signUrl = `/sign/${token}`;
await page.goto(signUrl);
await expect(page.getByRole('heading', { name: 'Sign Document' })).toBeVisible();
await signSignaturePad(page);
// Fill in all fields
for (const field of fields) {
await page.locator(`#field-${field.id}`).getByRole('button').click();
if (field.type === FieldType.TEXT) {
await page.locator('#custom-text').fill('TEXT');
await page.getByRole('button', { name: 'Save' }).click();
}
await expect(page.locator(`#field-${field.id}`)).toHaveAttribute('data-inserted', 'true');
}
// Complete signing
await page.getByRole('button', { name: 'Complete' }).click();
// Verify next recipient UI is not shown in parallel flow
await expect(
page.getByText('The next recipient to sign this document will be'),
).not.toBeVisible();
await expect(page.getByRole('button', { name: 'Update Recipient' })).not.toBeVisible();
// Submit and verify completion
await page.getByRole('button', { name: 'Sign' }).click();
await page.waitForURL(`${signUrl}/complete`);
}
// Verify final document and recipient states
await expect(async () => {
const updatedDocument = await prisma.document.findUniqueOrThrow({
where: { id: document.id },
include: {
recipients: {
orderBy: { signingOrder: 'asc' },
},
},
});
// Document should be completed since all recipients have signed
expect(updatedDocument.status).toBe(DocumentStatus.COMPLETED);
// All recipients should be completed
for (const recipient of updatedDocument.recipients) {
expect(recipient.signingStatus).toBe(SigningStatus.SIGNED);
}
}).toPass();
});
test('[NEXT_RECIPIENT_DICTATION]: should allow assistant to dictate next signer', async ({
page,
}) => {
const user = await seedUser();
const assistant = await seedUser();
const signer = await seedUser();
const thirdSigner = await seedUser();
const { recipients, document } = await seedPendingDocumentWithFullFields({
owner: user,
recipients: [assistant, signer, thirdSigner],
recipientsCreateOptions: [
{ signingOrder: 1, role: RecipientRole.ASSISTANT },
{ signingOrder: 2, role: RecipientRole.SIGNER },
{ signingOrder: 3, role: RecipientRole.SIGNER },
],
updateDocumentOptions: {
documentMeta: {
upsert: {
create: {
allowDictateNextSigner: true,
signingOrder: DocumentSigningOrder.SEQUENTIAL,
},
update: {
allowDictateNextSigner: true,
signingOrder: DocumentSigningOrder.SEQUENTIAL,
},
},
},
},
});
const assistantRecipient = recipients[0];
const { token, fields } = assistantRecipient;
const signUrl = `/sign/${token}`;
await page.goto(signUrl);
await expect(page.getByRole('heading', { name: 'Assist Document' })).toBeVisible();
await page.getByRole('radio', { name: assistantRecipient.name }).click();
// Fill in all fields
for (const field of fields) {
await page.locator(`#field-${field.id}`).getByRole('button').click();
if (field.type === FieldType.SIGNATURE) {
await signDirectSignaturePad(page);
await page.getByRole('button', { name: 'Sign', exact: true }).click();
}
if (field.type === FieldType.TEXT) {
await page.locator('#custom-text').fill('TEXT');
await page.getByRole('button', { name: 'Save' }).click();
}
await expect(page.locator(`#field-${field.id}`)).toHaveAttribute('data-inserted', 'true');
}
// Complete assisting and update next recipient
await page.getByRole('button', { name: 'Continue' }).click();
// Verify next recipient info is shown
await expect(page.getByRole('dialog')).toBeVisible();
await expect(page.getByText('The next recipient to sign this document will be')).toBeVisible();
// Update next recipient
await page.locator('button').filter({ hasText: 'Update Recipient' }).click();
// Use dialog context to ensure we're targeting the correct form fields
const dialog = page.getByRole('dialog');
await dialog.getByLabel('Name').fill('New Signer');
await dialog.getByLabel('Email').fill('new.signer@example.com');
// Submit and verify completion
await page.getByRole('button', { name: /Continue|Proceed/i }).click();
await page.waitForURL(`${signUrl}/complete`);
// Verify document and recipient states
await expect(async () => {
const updatedDocument = await prisma.document.findUniqueOrThrow({
where: { id: document.id },
include: {
recipients: {
orderBy: { signingOrder: 'asc' },
},
},
});
// Document should still be pending as there are more recipients
expect(updatedDocument.status).toBe(DocumentStatus.PENDING);
// Assistant should be completed
const updatedAssistant = updatedDocument.recipients[0];
expect(updatedAssistant.signingStatus).toBe(SigningStatus.SIGNED);
expect(updatedAssistant.role).toBe(RecipientRole.ASSISTANT);
// Second recipient should be the new signer
const updatedSigner = updatedDocument.recipients[1];
expect(updatedSigner.name).toBe('New Signer');
expect(updatedSigner.email).toBe('new.signer@example.com');
expect(updatedSigner.signingOrder).toBe(2);
expect(updatedSigner.signingStatus).toBe(SigningStatus.NOT_SIGNED);
expect(updatedSigner.role).toBe(RecipientRole.SIGNER);
// Third recipient should remain unchanged
const thirdRecipient = updatedDocument.recipients[2];
expect(thirdRecipient.email).toBe(thirdSigner.email);
expect(thirdRecipient.signingOrder).toBe(3);
expect(thirdRecipient.signingStatus).toBe(SigningStatus.NOT_SIGNED);
expect(thirdRecipient.role).toBe(RecipientRole.SIGNER);
}).toPass();
});

View File

@@ -222,7 +222,10 @@ test.describe('Signing Certificate Tests', () => {
// Toggle signing certificate setting // Toggle signing certificate setting
await page.getByLabel('Include the Signing Certificate in the Document').click(); await page.getByLabel('Include the Signing Certificate in the Document').click();
await page.getByRole('button', { name: /Save/ }).first().click(); await page
.getByRole('button', { name: /Update/ })
.first()
.click();
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
@@ -236,7 +239,10 @@ test.describe('Signing Certificate Tests', () => {
// Toggle the setting back to true // Toggle the setting back to true
await page.getByLabel('Include the Signing Certificate in the Document').click(); await page.getByLabel('Include the Signing Certificate in the Document').click();
await page.getByRole('button', { name: /Save/ }).first().click(); await page
.getByRole('button', { name: /Update/ })
.first()
.click();
await page.waitForTimeout(1000); await page.waitForTimeout(1000);

View File

@@ -1,40 +1,28 @@
import type { Page } from '@playwright/test'; import type { Page } from '@playwright/test';
/**
* Will open the signature pad dialog and sign it.
*/
export const signSignaturePad = async (page: Page) => { export const signSignaturePad = async (page: Page) => {
await page.waitForTimeout(200); await page.waitForTimeout(200);
const canvas = page.getByTestId('signature-pad'); await page.getByTestId('signature-pad-dialog-button').click();
const box = await canvas.boundingBox(); // Click type tab
await page.getByRole('tab', { name: 'Type' }).click();
await page.getByTestId('signature-pad-type-input').fill('Signature');
if (!box) { // Click Next button
throw new Error('Signature pad not found'); await page.getByRole('button', { name: 'Next' }).click();
} };
// Calculate center point /**
const centerX = box.x + box.width / 2; * For when the signature pad is already open.
const centerY = box.y + box.height / 2; */
export const signDirectSignaturePad = async (page: Page) => {
// Calculate square size (making it slightly smaller than the canvas) await page.waitForTimeout(200);
const squareSize = Math.min(box.width, box.height) * 0.4; // 40% of the smallest dimension
// Click type tab
// Move to center await page.getByRole('tab', { name: 'Type' }).click();
await page.mouse.move(centerX, centerY); await page.getByTestId('signature-pad-type-input').fill('Signature');
await page.mouse.down();
// Draw square clockwise from center
// Move right
await page.mouse.move(centerX + squareSize, centerY, { steps: 10 });
// Move down
await page.mouse.move(centerX + squareSize, centerY + squareSize, { steps: 10 });
// Move left
await page.mouse.move(centerX - squareSize, centerY + squareSize, { steps: 10 });
// Move up
await page.mouse.move(centerX - squareSize, centerY - squareSize, { steps: 10 });
// Move right
await page.mouse.move(centerX + squareSize, centerY - squareSize, { steps: 10 });
// Move down to close the square
await page.mouse.move(centerX + squareSize, centerY, { steps: 10 });
await page.mouse.up();
}; };

View File

@@ -56,6 +56,7 @@ test('[PUBLIC_PROFILE]: create profile', async ({ page }) => {
// Go back to public profile page. // Go back to public profile page.
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/settings/public-profile`); await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/settings/public-profile`);
await page.getByRole('switch').click(); await page.getByRole('switch').click();
await page.waitForTimeout(1000);
// Assert values. // Assert values.
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/p/${publicProfileUrl}`); await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/p/${publicProfileUrl}`);
@@ -127,6 +128,7 @@ test('[PUBLIC_PROFILE]: create team profile', async ({ page }) => {
// Go back to public profile page. // Go back to public profile page.
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/t/${team.url}/settings/public-profile`); await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/t/${team.url}/settings/public-profile`);
await page.getByRole('switch').click(); await page.getByRole('switch').click();
await page.waitForTimeout(1000);
// Assert values. // Assert values.
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/p/${publicProfileUrl}`); await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/p/${publicProfileUrl}`);

View File

@@ -23,7 +23,7 @@ test('[TEAMS]: update the default document visibility in the team global setting
// !: Brittle selector // !: Brittle selector
await page.getByRole('combobox').first().click(); await page.getByRole('combobox').first().click();
await page.getByRole('option', { name: 'Admin' }).click(); await page.getByRole('option', { name: 'Admin' }).click();
await page.getByRole('button', { name: 'Save' }).first().click(); await page.getByRole('button', { name: 'Update' }).first().click();
const toast = page.locator('li[role="status"][data-state="open"]').first(); const toast = page.locator('li[role="status"][data-state="open"]').first();
await expect(toast).toBeVisible(); await expect(toast).toBeVisible();
@@ -47,7 +47,7 @@ test('[TEAMS]: update the sender details in the team global settings', async ({
await expect(checkbox).toBeChecked(); await expect(checkbox).toBeChecked();
await page.getByRole('button', { name: 'Save' }).first().click(); await page.getByRole('button', { name: 'Update' }).first().click();
const toast = page.locator('li[role="status"][data-state="open"]').first(); const toast = page.locator('li[role="status"][data-state="open"]').first();
await expect(toast).toBeVisible(); await expect(toast).toBeVisible();

View File

@@ -0,0 +1,182 @@
import { expect, test } from '@playwright/test';
import { prisma } from '@documenso/prisma';
import {
seedTeamDocumentWithMeta,
seedTeamDocuments,
seedTeamTemplateWithMeta,
} from '@documenso/prisma/seed/documents';
import { apiSignin } from '../fixtures/authentication';
test.describe.configure({ mode: 'parallel' });
test('[TEAMS]: check that default team signature settings are all enabled', async ({ page }) => {
const { team } = await seedTeamDocuments();
await apiSignin({
page,
email: team.owner.email,
password: 'password',
redirectPath: `/t/${team.url}/settings/preferences`,
});
// Verify that the default created team settings has all signatures enabled
await expect(page.getByRole('combobox').filter({ hasText: 'Type' })).toBeVisible();
await expect(page.getByRole('combobox').filter({ hasText: 'Upload' })).toBeVisible();
await expect(page.getByRole('combobox').filter({ hasText: 'Draw' })).toBeVisible();
const document = await seedTeamDocumentWithMeta(team);
// Create a document and check the settings
await page.goto(`/t/${team.url}/documents/${document.id}/edit`);
// Verify that the settings match
await page.getByRole('button', { name: 'Advanced Options' }).click();
await expect(page.getByRole('combobox').filter({ hasText: 'Type' })).toBeVisible();
await expect(page.getByRole('combobox').filter({ hasText: 'Upload' })).toBeVisible();
await expect(page.getByRole('combobox').filter({ hasText: 'Draw' })).toBeVisible();
// Go to document and check that the signatured tabs are correct.
await page.goto(`/sign/${document.recipients[0].token}`);
await page.getByTestId('signature-pad-dialog-button').click();
// Check the tab values
await expect(page.getByRole('tab', { name: 'Type' })).toBeVisible();
await expect(page.getByRole('tab', { name: 'Upload' })).toBeVisible();
await expect(page.getByRole('tab', { name: 'Draw' })).toBeVisible();
});
test('[TEAMS]: check signature modes can be disabled', async ({ page }) => {
const { team } = await seedTeamDocuments();
await apiSignin({
page,
email: team.owner.email,
password: 'password',
redirectPath: `/t/${team.url}/settings/preferences`,
});
const allTabs = ['Type', 'Upload', 'Draw'];
const tabTest = [['Type', 'Upload', 'Draw'], ['Type', 'Upload'], ['Type']];
for (const tabs of tabTest) {
await page.goto(`/t/${team.url}/settings/preferences`);
// Update combobox to have the correct tabs
await page.getByTestId('signature-types-combobox').click();
await expect(page.getByRole('option', { name: 'Type' })).toBeVisible();
await expect(page.getByRole('option', { name: 'Upload' })).toBeVisible();
await expect(page.getByRole('option', { name: 'Draw' })).toBeVisible();
// Clear all selected items.
for (const tab of allTabs) {
const item = page.getByRole('option', { name: tab });
const isSelected = (await item.innerHTML()).includes('opacity-100');
if (isSelected) {
await item.click();
}
}
// Selected wanted items.
for (const tab of tabs) {
const item = page.getByRole('option', { name: tab });
await item.click();
}
await page.getByRole('button', { name: 'Update' }).first().click();
const document = await seedTeamDocumentWithMeta(team);
// Go to document and check that the signatured tabs are correct.
await page.goto(`/sign/${document.recipients[0].token}`);
await page.getByTestId('signature-pad-dialog-button').click();
// Check the tab values
for (const tab of allTabs) {
if (tabs.includes(tab)) {
await expect(page.getByRole('tab', { name: tab })).toBeVisible();
} else {
await expect(page.getByRole('tab', { name: tab })).not.toBeVisible();
}
}
}
});
test('[TEAMS]: check signature modes work for templates', async ({ page }) => {
const { team } = await seedTeamDocuments();
await apiSignin({
page,
email: team.owner.email,
password: 'password',
redirectPath: `/t/${team.url}/settings/preferences`,
});
const allTabs = ['Type', 'Upload', 'Draw'];
const tabTest = [['Type', 'Upload', 'Draw'], ['Type', 'Upload'], ['Type']];
for (const tabs of tabTest) {
await page.goto(`/t/${team.url}/settings/preferences`);
// Update combobox to have the correct tabs
await page.getByTestId('signature-types-combobox').click();
await expect(page.getByRole('option', { name: 'Type' })).toBeVisible();
await expect(page.getByRole('option', { name: 'Upload' })).toBeVisible();
await expect(page.getByRole('option', { name: 'Draw' })).toBeVisible();
// Clear all selected items.
for (const tab of allTabs) {
const item = page.getByRole('option', { name: tab });
const isSelected = (await item.innerHTML()).includes('opacity-100');
if (isSelected) {
await item.click();
}
}
// Selected wanted items.
for (const tab of tabs) {
const item = page.getByRole('option', { name: tab });
await item.click();
}
await page.getByRole('button', { name: 'Update' }).first().click();
const template = await seedTeamTemplateWithMeta(team);
await page.goto(`/t/${team.url}/templates/${template.id}`);
await page.getByRole('button', { name: 'Use' }).click();
// Check the send document checkbox to true
await page.getByLabel('Send document').click();
await page.getByRole('button', { name: 'Create and send' }).click();
await page.waitForTimeout(1000);
const document = await prisma.document.findFirst({
where: {
templateId: template.id,
},
include: {
documentMeta: true,
},
});
// Test kinda flaky, debug here.
// console.log({
// tabs,
// typedSignatureEnabled: document?.documentMeta?.typedSignatureEnabled,
// uploadSignatureEnabled: document?.documentMeta?.uploadSignatureEnabled,
// drawSignatureEnabled: document?.documentMeta?.drawSignatureEnabled,
// });
expect(document?.documentMeta?.typedSignatureEnabled).toEqual(tabs.includes('Type'));
expect(document?.documentMeta?.uploadSignatureEnabled).toEqual(tabs.includes('Upload'));
expect(document?.documentMeta?.drawSignatureEnabled).toEqual(tabs.includes('Draw'));
}
});

View File

@@ -298,6 +298,7 @@ test('[DIRECT_TEMPLATES]: use direct template link with 2 recipients', async ({
await page.goto(formatDirectTemplatePath(template.directLink?.token || '')); await page.goto(formatDirectTemplatePath(template.directLink?.token || ''));
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
await page.waitForTimeout(1000);
await page.getByPlaceholder('recipient@documenso.com').fill(seedTestEmail()); await page.getByPlaceholder('recipient@documenso.com').fill(seedTestEmail());
await page.getByRole('button', { name: 'Continue' }).click(); await page.getByRole('button', { name: 'Continue' }).click();

View File

@@ -4,6 +4,7 @@ import { getUserByEmail } from '@documenso/lib/server-only/user/get-user-by-emai
import { seedUser } from '@documenso/prisma/seed/users'; import { seedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication'; import { apiSignin } from '../fixtures/authentication';
import { signSignaturePad } from '../fixtures/signature';
test('[USER] update full name', async ({ page }) => { test('[USER] update full name', async ({ page }) => {
const user = await seedUser(); const user = await seedUser();
@@ -12,7 +13,7 @@ test('[USER] update full name', async ({ page }) => {
await page.getByLabel('Full Name').fill('John Doe'); await page.getByLabel('Full Name').fill('John Doe');
await page.getByPlaceholder('Type your signature').fill('John Doe'); await signSignaturePad(page);
await page.getByRole('button', { name: 'Update profile' }).click(); await page.getByRole('button', { name: 'Update profile' }).click();

View File

@@ -34,3 +34,29 @@ export const DOCUMENT_DISTRIBUTION_METHODS: Record<string, DocumentDistributionM
description: msg`None`, description: msg`None`,
}, },
} satisfies Record<DocumentDistributionMethod, DocumentDistributionMethodTypeData>; } satisfies Record<DocumentDistributionMethod, DocumentDistributionMethodTypeData>;
export enum DocumentSignatureType {
DRAW = 'draw',
TYPE = 'type',
UPLOAD = 'upload',
}
type DocumentSignatureTypeData = {
label: MessageDescriptor;
value: DocumentSignatureType;
};
export const DOCUMENT_SIGNATURE_TYPES = {
[DocumentSignatureType.DRAW]: {
label: msg`Draw`,
value: DocumentSignatureType.DRAW,
},
[DocumentSignatureType.TYPE]: {
label: msg`Type`,
value: DocumentSignatureType.TYPE,
},
[DocumentSignatureType.UPLOAD]: {
label: msg`Upload`,
value: DocumentSignatureType.UPLOAD,
},
} satisfies Record<DocumentSignatureType, DocumentSignatureTypeData>;

View File

@@ -0,0 +1,4 @@
export const SIGNATURE_CANVAS_DPI = 2;
export const SIGNATURE_MIN_COVERAGE_THRESHOLD = 0.01;
export const isBase64Image = (value: string) => value.startsWith('data:image/png;base64,');

View File

@@ -23,6 +23,8 @@ const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
brandingHidePoweredBy: z.boolean(), brandingHidePoweredBy: z.boolean(),
teamId: z.number(), teamId: z.number(),
typedSignatureEnabled: z.boolean(), typedSignatureEnabled: z.boolean(),
uploadSignatureEnabled: z.boolean(),
drawSignatureEnabled: z.boolean(),
}) })
.nullish(), .nullish(),
}), }),

View File

@@ -24,8 +24,11 @@ export type CreateDocumentMetaOptions = {
redirectUrl?: string; redirectUrl?: string;
emailSettings?: TDocumentEmailSettings; emailSettings?: TDocumentEmailSettings;
signingOrder?: DocumentSigningOrder; signingOrder?: DocumentSigningOrder;
allowDictateNextSigner?: boolean;
distributionMethod?: DocumentDistributionMethod; distributionMethod?: DocumentDistributionMethod;
typedSignatureEnabled?: boolean; typedSignatureEnabled?: boolean;
uploadSignatureEnabled?: boolean;
drawSignatureEnabled?: boolean;
language?: SupportedLanguageCodes; language?: SupportedLanguageCodes;
requestMetadata: ApiRequestMetadata; requestMetadata: ApiRequestMetadata;
}; };
@@ -41,9 +44,12 @@ export const upsertDocumentMeta = async ({
password, password,
redirectUrl, redirectUrl,
signingOrder, signingOrder,
allowDictateNextSigner,
emailSettings, emailSettings,
distributionMethod, distributionMethod,
typedSignatureEnabled, typedSignatureEnabled,
uploadSignatureEnabled,
drawSignatureEnabled,
language, language,
requestMetadata, requestMetadata,
}: CreateDocumentMetaOptions) => { }: CreateDocumentMetaOptions) => {
@@ -93,9 +99,12 @@ export const upsertDocumentMeta = async ({
documentId, documentId,
redirectUrl, redirectUrl,
signingOrder, signingOrder,
allowDictateNextSigner,
emailSettings, emailSettings,
distributionMethod, distributionMethod,
typedSignatureEnabled, typedSignatureEnabled,
uploadSignatureEnabled,
drawSignatureEnabled,
language, language,
}, },
update: { update: {
@@ -106,9 +115,12 @@ export const upsertDocumentMeta = async ({
timezone, timezone,
redirectUrl, redirectUrl,
signingOrder, signingOrder,
allowDictateNextSigner,
emailSettings, emailSettings,
distributionMethod, distributionMethod,
typedSignatureEnabled, typedSignatureEnabled,
uploadSignatureEnabled,
drawSignatureEnabled,
language, language,
}, },
}); });

View File

@@ -7,7 +7,10 @@ import {
WebhookTriggerEvents, WebhookTriggerEvents,
} from '@prisma/client'; } from '@prisma/client';
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs'; import {
DOCUMENT_AUDIT_LOG_TYPE,
RECIPIENT_DIFF_TYPE,
} from '@documenso/lib/types/document-audit-logs';
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata'; import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { fieldsContainUnsignedRequiredField } from '@documenso/lib/utils/advanced-fields-helpers'; import { fieldsContainUnsignedRequiredField } from '@documenso/lib/utils/advanced-fields-helpers';
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs'; import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
@@ -30,6 +33,10 @@ export type CompleteDocumentWithTokenOptions = {
userId?: number; userId?: number;
authOptions?: TRecipientActionAuth; authOptions?: TRecipientActionAuth;
requestMetadata?: RequestMetadata; requestMetadata?: RequestMetadata;
nextSigner?: {
email: string;
name: string;
};
}; };
const getDocument = async ({ token, documentId }: CompleteDocumentWithTokenOptions) => { const getDocument = async ({ token, documentId }: CompleteDocumentWithTokenOptions) => {
@@ -57,6 +64,7 @@ export const completeDocumentWithToken = async ({
token, token,
documentId, documentId,
requestMetadata, requestMetadata,
nextSigner,
}: CompleteDocumentWithTokenOptions) => { }: CompleteDocumentWithTokenOptions) => {
const document = await getDocument({ token, documentId }); const document = await getDocument({ token, documentId });
@@ -146,7 +154,6 @@ export const completeDocumentWithToken = async ({
recipientName: recipient.name, recipientName: recipient.name,
recipientId: recipient.id, recipientId: recipient.id,
recipientRole: recipient.role, recipientRole: recipient.role,
// actionAuth: derivedRecipientActionAuth || undefined,
}, },
}), }),
}); });
@@ -164,6 +171,9 @@ export const completeDocumentWithToken = async ({
select: { select: {
id: true, id: true,
signingOrder: true, signingOrder: true,
name: true,
email: true,
role: true,
}, },
where: { where: {
documentId: document.id, documentId: document.id,
@@ -186,9 +196,49 @@ export const completeDocumentWithToken = async ({
const [nextRecipient] = pendingRecipients; const [nextRecipient] = pendingRecipients;
await prisma.$transaction(async (tx) => { await prisma.$transaction(async (tx) => {
if (nextSigner && document.documentMeta?.allowDictateNextSigner) {
await tx.documentAuditLog.create({
data: createDocumentAuditLogData({
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_UPDATED,
documentId: document.id,
user: {
name: recipient.name,
email: recipient.email,
},
requestMetadata,
data: {
recipientEmail: nextRecipient.email,
recipientName: nextRecipient.name,
recipientId: nextRecipient.id,
recipientRole: nextRecipient.role,
changes: [
{
type: RECIPIENT_DIFF_TYPE.NAME,
from: nextRecipient.name,
to: nextSigner.name,
},
{
type: RECIPIENT_DIFF_TYPE.EMAIL,
from: nextRecipient.email,
to: nextSigner.email,
},
],
},
}),
});
}
await tx.recipient.update({ await tx.recipient.update({
where: { id: nextRecipient.id }, where: { id: nextRecipient.id },
data: { sendStatus: SendStatus.SENT }, data: {
sendStatus: SendStatus.SENT,
...(nextSigner && document.documentMeta?.allowDictateNextSigner
? {
name: nextSigner.name,
email: nextSigner.email,
}
: {}),
},
}); });
await jobs.triggerJob({ await jobs.triggerJob({

View File

@@ -158,6 +158,10 @@ export const createDocumentV2 = async ({
language: meta?.language || team?.teamGlobalSettings?.documentLanguage, language: meta?.language || team?.teamGlobalSettings?.documentLanguage,
typedSignatureEnabled: typedSignatureEnabled:
meta?.typedSignatureEnabled ?? team?.teamGlobalSettings?.typedSignatureEnabled, meta?.typedSignatureEnabled ?? team?.teamGlobalSettings?.typedSignatureEnabled,
uploadSignatureEnabled:
meta?.uploadSignatureEnabled ?? team?.teamGlobalSettings?.uploadSignatureEnabled,
drawSignatureEnabled:
meta?.drawSignatureEnabled ?? team?.teamGlobalSettings?.drawSignatureEnabled,
}, },
}, },
}, },

View File

@@ -128,8 +128,10 @@ export const createDocument = async ({
documentMeta: { documentMeta: {
create: { create: {
language: team?.teamGlobalSettings?.documentLanguage, language: team?.teamGlobalSettings?.documentLanguage,
typedSignatureEnabled: team?.teamGlobalSettings?.typedSignatureEnabled,
timezone: timezone, timezone: timezone,
typedSignatureEnabled: team?.teamGlobalSettings?.typedSignatureEnabled ?? true,
uploadSignatureEnabled: team?.teamGlobalSettings?.uploadSignatureEnabled ?? true,
drawSignatureEnabled: team?.teamGlobalSettings?.drawSignatureEnabled ?? true,
}, },
}, },
}, },

View File

@@ -201,7 +201,7 @@ export const signFieldWithToken = async ({
throw new Error('Signature field must have a signature'); throw new Error('Signature field must have a signature');
} }
if (isSignatureField && !documentMeta?.typedSignatureEnabled && typedSignature) { if (isSignatureField && documentMeta?.typedSignatureEnabled === false && typedSignature) {
throw new Error('Typed signatures are not allowed. Please draw your signature'); throw new Error('Typed signatures are not allowed. Please draw your signature');
} }

View File

@@ -0,0 +1,37 @@
import { prisma } from '@documenso/prisma';
export const getNextPendingRecipient = async ({
documentId,
currentRecipientId,
}: {
documentId: number;
currentRecipientId: number;
}) => {
const recipients = await prisma.recipient.findMany({
where: {
documentId,
},
orderBy: [
{
signingOrder: {
sort: 'asc',
nulls: 'last',
},
},
{
id: 'asc',
},
],
});
const currentIndex = recipients.findIndex((r) => r.id === currentRecipientId);
if (currentIndex === -1 || currentIndex === recipients.length - 1) {
return null;
}
return {
...recipients[currentIndex + 1],
token: '',
};
};

View File

@@ -1,73 +0,0 @@
import type { DocumentVisibility } from '@prisma/client';
import { TeamMemberRole } from '@prisma/client';
import type { z } from 'zod';
import { prisma } from '@documenso/prisma';
import { TeamGlobalSettingsSchema } from '@documenso/prisma/generated/zod/modelSchema/TeamGlobalSettingsSchema';
import type { SupportedLanguageCodes } from '../../constants/i18n';
export type UpdateTeamDocumentSettingsOptions = {
userId: number;
teamId: number;
settings: {
documentVisibility: DocumentVisibility;
documentLanguage: SupportedLanguageCodes;
includeSenderDetails: boolean;
typedSignatureEnabled: boolean;
includeSigningCertificate: boolean;
};
};
export const ZUpdateTeamDocumentSettingsResponseSchema = TeamGlobalSettingsSchema;
export type TUpdateTeamDocumentSettingsResponse = z.infer<
typeof ZUpdateTeamDocumentSettingsResponseSchema
>;
export const updateTeamDocumentSettings = async ({
userId,
teamId,
settings,
}: UpdateTeamDocumentSettingsOptions): Promise<TUpdateTeamDocumentSettingsResponse> => {
const {
documentVisibility,
documentLanguage,
includeSenderDetails,
includeSigningCertificate,
typedSignatureEnabled,
} = settings;
const member = await prisma.teamMember.findFirst({
where: {
userId,
teamId,
},
});
if (!member || member.role !== TeamMemberRole.ADMIN) {
throw new Error('You do not have permission to update this team.');
}
return await prisma.teamGlobalSettings.upsert({
where: {
teamId,
},
create: {
teamId,
documentVisibility,
documentLanguage,
includeSenderDetails,
typedSignatureEnabled,
includeSigningCertificate,
},
update: {
documentVisibility,
documentLanguage,
includeSenderDetails,
typedSignatureEnabled,
includeSigningCertificate,
},
});
};

View File

@@ -324,6 +324,9 @@ export const createDocumentFromDirectTemplate = async ({
language: metaLanguage, language: metaLanguage,
signingOrder: metaSigningOrder, signingOrder: metaSigningOrder,
distributionMethod: template.templateMeta?.distributionMethod, distributionMethod: template.templateMeta?.distributionMethod,
typedSignatureEnabled: template.templateMeta?.typedSignatureEnabled,
uploadSignatureEnabled: template.templateMeta?.uploadSignatureEnabled,
drawSignatureEnabled: template.templateMeta?.drawSignatureEnabled,
}, },
}, },
}, },

View File

@@ -96,6 +96,9 @@ export const createDocumentFromTemplateLegacy = async ({
signingOrder: template.templateMeta?.signingOrder ?? undefined, signingOrder: template.templateMeta?.signingOrder ?? undefined,
language: language:
template.templateMeta?.language || template.team?.teamGlobalSettings?.documentLanguage, template.templateMeta?.language || template.team?.teamGlobalSettings?.documentLanguage,
typedSignatureEnabled: template.templateMeta?.typedSignatureEnabled,
uploadSignatureEnabled: template.templateMeta?.uploadSignatureEnabled,
drawSignatureEnabled: template.templateMeta?.drawSignatureEnabled,
}, },
}, },
}, },

View File

@@ -82,8 +82,11 @@ export type CreateDocumentFromTemplateOptions = {
signingOrder?: DocumentSigningOrder; signingOrder?: DocumentSigningOrder;
language?: SupportedLanguageCodes; language?: SupportedLanguageCodes;
distributionMethod?: DocumentDistributionMethod; distributionMethod?: DocumentDistributionMethod;
typedSignatureEnabled?: boolean; allowDictateNextSigner?: boolean;
emailSettings?: TDocumentEmailSettings; emailSettings?: TDocumentEmailSettings;
typedSignatureEnabled?: boolean;
uploadSignatureEnabled?: boolean;
drawSignatureEnabled?: boolean;
}; };
requestMetadata: ApiRequestMetadata; requestMetadata: ApiRequestMetadata;
}; };
@@ -404,6 +407,14 @@ export const createDocumentFromTemplate = async ({
template.team?.teamGlobalSettings?.documentLanguage, template.team?.teamGlobalSettings?.documentLanguage,
typedSignatureEnabled: typedSignatureEnabled:
override?.typedSignatureEnabled ?? template.templateMeta?.typedSignatureEnabled, override?.typedSignatureEnabled ?? template.templateMeta?.typedSignatureEnabled,
uploadSignatureEnabled:
override?.uploadSignatureEnabled ?? template.templateMeta?.uploadSignatureEnabled,
drawSignatureEnabled:
override?.drawSignatureEnabled ?? template.templateMeta?.drawSignatureEnabled,
allowDictateNextSigner:
override?.allowDictateNextSigner ??
template.templateMeta?.allowDictateNextSigner ??
false,
}, },
}, },
recipients: { recipients: {

View File

@@ -4,6 +4,8 @@ import { prisma } from '@documenso/prisma';
import { TemplateSchema } from '@documenso/prisma/generated/zod/modelSchema//TemplateSchema'; import { TemplateSchema } from '@documenso/prisma/generated/zod/modelSchema//TemplateSchema';
import type { TCreateTemplateMutationSchema } from '@documenso/trpc/server/template-router/schema'; import type { TCreateTemplateMutationSchema } from '@documenso/trpc/server/template-router/schema';
import { AppError, AppErrorCode } from '../../errors/app-error';
export type CreateTemplateOptions = TCreateTemplateMutationSchema & { export type CreateTemplateOptions = TCreateTemplateMutationSchema & {
userId: number; userId: number;
teamId?: number; teamId?: number;
@@ -19,8 +21,10 @@ export const createTemplate = async ({
teamId, teamId,
templateDocumentDataId, templateDocumentDataId,
}: CreateTemplateOptions) => { }: CreateTemplateOptions) => {
let team = null;
if (teamId) { if (teamId) {
await prisma.team.findFirstOrThrow({ team = await prisma.team.findFirst({
where: { where: {
id: teamId, id: teamId,
members: { members: {
@@ -29,7 +33,14 @@ export const createTemplate = async ({
}, },
}, },
}, },
include: {
teamGlobalSettings: true,
},
}); });
if (!team) {
throw new AppError(AppErrorCode.NOT_FOUND);
}
} }
return await prisma.template.create({ return await prisma.template.create({
@@ -38,6 +49,14 @@ export const createTemplate = async ({
userId, userId,
templateDocumentDataId, templateDocumentDataId,
teamId, teamId,
templateMeta: {
create: {
language: team?.teamGlobalSettings?.documentLanguage,
typedSignatureEnabled: team?.teamGlobalSettings?.typedSignatureEnabled ?? true,
uploadSignatureEnabled: team?.teamGlobalSettings?.uploadSignatureEnabled ?? true,
drawSignatureEnabled: team?.teamGlobalSettings?.drawSignatureEnabled ?? true,
},
},
}, },
}); });
}; };

View File

@@ -419,6 +419,10 @@ msgstr "<0>{teamName}</0> hat angefragt, Ihre E-Mail-Adresse für ihr Team bei D
msgid "<0>Click to upload</0> or drag and drop" msgid "<0>Click to upload</0> or drag and drop"
msgstr "<0>Klicken Sie hier, um hochzuladen</0> oder ziehen Sie die Datei per Drag & Drop" msgstr "<0>Klicken Sie hier, um hochzuladen</0> oder ziehen Sie die Datei per Drag & Drop"
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "<0>Drawn</0> - A signature that is drawn using a mouse or stylus."
msgstr ""
#: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/template-flow/add-template-settings.tsx
msgid "<0>Email</0> - The recipient will be emailed the document to sign, approve, etc." msgid "<0>Email</0> - The recipient will be emailed the document to sign, approve, etc."
msgstr "<0>E-Mail</0> - Der Empfänger erhält das Dokument zur Unterschrift, Genehmigung usw." msgstr "<0>E-Mail</0> - Der Empfänger erhält das Dokument zur Unterschrift, Genehmigung usw."
@@ -465,6 +469,14 @@ msgstr "<0>Passkey erforderlich</0> - Der Empfänger muss ein Konto haben und de
msgid "<0>Sender:</0> All" msgid "<0>Sender:</0> All"
msgstr "<0>Absender:</0> Alle" msgstr "<0>Absender:</0> Alle"
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "<0>Typed</0> - A signature that is typed using a keyboard."
msgstr ""
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "<0>Uploaded</0> - A signature that is uploaded from a file."
msgstr ""
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
msgid "<0>You are about to complete approving <1>\"{documentTitle}\"</1>.</0><2/> Are you sure?" msgid "<0>You are about to complete approving <1>\"{documentTitle}\"</1>.</0><2/> Are you sure?"
msgstr "<0>Sie sind dabei, die Genehmigung von <1>\"{documentTitle}\"</1> abzuschließen.</0><2/> Sind Sie sicher?" msgstr "<0>Sie sind dabei, die Genehmigung von <1>\"{documentTitle}\"</1> abzuschließen.</0><2/> Sind Sie sicher?"
@@ -898,6 +910,16 @@ msgstr "Alle Zeiten"
msgid "Allow document recipients to reply directly to this email address" msgid "Allow document recipients to reply directly to this email address"
msgstr "Erlauben Sie den Dokumentempfängern, direkt an diese E-Mail-Adresse zu antworten" msgstr "Erlauben Sie den Dokumentempfängern, direkt an diese E-Mail-Adresse zu antworten"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/document-flow/add-signers.tsx
msgid "Allow signers to dictate next signer"
msgstr ""
#: packages/ui/primitives/template-flow/add-template-settings.tsx
#: packages/ui/primitives/document-flow/add-settings.tsx
msgid "Allowed Signature Types"
msgstr ""
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx #: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "Allows authenticating using biometrics, password managers, hardware keys, etc." msgid "Allows authenticating using biometrics, password managers, hardware keys, etc."
msgstr "Erlaubt die Authentifizierung mit biometrischen Daten, Passwort-Managern, Hardware-Schlüsseln usw." msgstr "Erlaubt die Authentifizierung mit biometrischen Daten, Passwort-Managern, Hardware-Schlüsseln usw."
@@ -1237,6 +1259,13 @@ msgstr ""
msgid "Assisting" msgid "Assisting"
msgstr "" msgstr ""
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
#: packages/ui/primitives/template-flow/add-template-settings.types.tsx
#: packages/ui/primitives/document-flow/add-settings.types.ts
#: packages/lib/types/document-meta.ts
msgid "At least one signature type must be enabled"
msgstr ""
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
msgid "Attempts sealing the document again, useful for after a code change has occurred to resolve an erroneous document." msgid "Attempts sealing the document again, useful for after a code change has occurred to resolve an erroneous document."
msgstr "Versuche, das Dokument erneut zu versiegeln, nützlich nach einer Codeänderung, um ein fehlerhaftes Dokument zu beheben." msgstr "Versuche, das Dokument erneut zu versiegeln, nützlich nach einer Codeänderung, um ein fehlerhaftes Dokument zu beheben."
@@ -1309,11 +1338,11 @@ msgstr "Bitte bestätige vor dem Start deine E-Mail-Adresse, indem du auf den Bu
msgid "Billing" msgid "Billing"
msgstr "Abrechnung" msgstr "Abrechnung"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Black" msgid "Black"
msgstr "Schwarz" msgstr "Schwarz"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Blue" msgid "Blue"
msgstr "Blau" msgstr "Blau"
@@ -1384,6 +1413,10 @@ msgstr "Indem Sie den elektronischen Unterzeichnungsdienst von Documenso verwend
msgid "By proceeding with your electronic signature, you acknowledge and consent that it will be used to sign the given document and holds the same legal validity as a handwritten signature. By completing the electronic signing process, you affirm your understanding and acceptance of these conditions." msgid "By proceeding with your electronic signature, you acknowledge and consent that it will be used to sign the given document and holds the same legal validity as a handwritten signature. By completing the electronic signing process, you affirm your understanding and acceptance of these conditions."
msgstr "Indem Sie fortfahren, Ihre elektronische Unterschrift zu leisten, erkennen Sie an und stimmen zu, dass sie verwendet wird, um das gegebene Dokument zu unterzeichnen, und die gleiche rechtliche Gültigkeit wie eine handschriftliche Unterschrift hat. Durch den Abschluss des elektronischen Unterzeichnungsprozesses bestätigen Sie Ihr Verständnis und Ihre Akzeptanz dieser Bedingungen." msgstr "Indem Sie fortfahren, Ihre elektronische Unterschrift zu leisten, erkennen Sie an und stimmen zu, dass sie verwendet wird, um das gegebene Dokument zu unterzeichnen, und die gleiche rechtliche Gültigkeit wie eine handschriftliche Unterschrift hat. Durch den Abschluss des elektronischen Unterzeichnungsprozesses bestätigen Sie Ihr Verständnis und Ihre Akzeptanz dieser Bedingungen."
#: apps/remix/app/components/forms/signup.tsx
msgid "By proceeding, you agree to our <0>Terms of Service</0> and <1>Privacy Policy</1>."
msgstr ""
#: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx #: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx
msgid "By using the electronic signature feature, you are consenting to conduct transactions and receive disclosures electronically. You acknowledge that your electronic signature on documents is binding and that you accept the terms outlined in the documents you are signing." msgid "By using the electronic signature feature, you are consenting to conduct transactions and receive disclosures electronically. You acknowledge that your electronic signature on documents is binding and that you accept the terms outlined in the documents you are signing."
msgstr "Durch die Verwendung der elektronischen Unterschriftsfunktion stimmen Sie zu, Transaktionen durchzuführen und Offenlegungen elektronisch zu erhalten. Sie erkennen an, dass Ihre elektronische Unterschrift auf Dokumenten bindend ist und dass Sie die Bedingungen akzeptieren, die in den Dokumenten dargelegt sind, die Sie unterzeichnen." msgstr "Durch die Verwendung der elektronischen Unterschriftsfunktion stimmen Sie zu, Transaktionen durchzuführen und Offenlegungen elektronisch zu erhalten. Sie erkennen an, dass Ihre elektronische Unterschrift auf Dokumenten bindend ist und dass Sie die Bedingungen akzeptieren, die in den Dokumenten dargelegt sind, die Sie unterzeichnen."
@@ -1436,6 +1469,8 @@ msgstr ""
#: apps/remix/app/components/dialogs/document-move-dialog.tsx #: apps/remix/app/components/dialogs/document-move-dialog.tsx
#: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx #: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx #: packages/ui/primitives/document-flow/send-document-action-dialog.tsx
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx #: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
msgid "Cancel" msgid "Cancel"
@@ -1522,7 +1557,7 @@ msgstr "Datei löschen"
msgid "Clear filters" msgid "Clear filters"
msgstr "Filter löschen" msgstr "Filter löschen"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
msgid "Clear Signature" msgid "Clear Signature"
msgstr "Unterschrift löschen" msgstr "Unterschrift löschen"
@@ -1697,6 +1732,7 @@ msgstr "Inhalt"
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/document-flow/document-flow-root.tsx #: packages/ui/primitives/document-flow/document-flow-root.tsx
msgid "Continue" msgid "Continue"
msgstr "Fortsetzen" msgstr "Fortsetzen"
@@ -1737,14 +1773,18 @@ msgstr "Steuert die Standard-sichtbarkeit eines hochgeladenen Dokuments."
msgid "Controls the formatting of the message that will be sent when inviting a recipient to sign a document. If a custom message has been provided while configuring the document, it will be used instead." msgid "Controls the formatting of the message that will be sent when inviting a recipient to sign a document. If a custom message has been provided while configuring the document, it will be used instead."
msgstr "Steuert das Format der Nachricht, die gesendet wird, wenn ein Empfänger eingeladen wird, ein Dokument zu unterschreiben. Wenn eine benutzerdefinierte Nachricht beim Konfigurieren des Dokuments bereitgestellt wurde, wird diese stattdessen verwendet." msgstr "Steuert das Format der Nachricht, die gesendet wird, wenn ein Empfänger eingeladen wird, ein Dokument zu unterschreiben. Wenn eine benutzerdefinierte Nachricht beim Konfigurieren des Dokuments bereitgestellt wurde, wird diese stattdessen verwendet."
#: apps/remix/app/components/forms/team-document-preferences-form.tsx #: packages/ui/primitives/document-flow/add-settings.tsx
msgid "Controls whether the recipients can sign the documents using a typed signature. Enable or disable the typed signature globally." msgid "Controls the language for the document, including the language to be used for email notifications, and the final certificate that is generated and attached to the document."
msgstr "Legt fest, ob die Empfänger die Dokumente mit einer getippten Unterschrift unterschreiben können. Aktivieren oder deaktivieren Sie die getippte Unterschrift global." msgstr ""
#: apps/remix/app/components/forms/team-document-preferences-form.tsx #: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "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." msgid "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."
msgstr "Legt fest, ob das Signaturzertifikat in das Dokument aufgenommen wird, wenn es heruntergeladen wird. Das Signaturzertifikat kann weiterhin separat von der Protokollseite heruntergeladen werden." msgstr "Legt fest, ob das Signaturzertifikat in das Dokument aufgenommen wird, wenn es heruntergeladen wird. Das Signaturzertifikat kann weiterhin separat von der Protokollseite heruntergeladen werden."
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "Controls which signatures are allowed to be used when signing a document."
msgstr ""
#: apps/remix/app/components/general/document/document-recipient-link-copy-dialog.tsx #: apps/remix/app/components/general/document/document-recipient-link-copy-dialog.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
msgid "Copied" msgid "Copied"
@@ -1975,6 +2015,10 @@ msgstr "Standardsprache des Dokuments"
msgid "Default Document Visibility" msgid "Default Document Visibility"
msgstr "Standard Sichtbarkeit des Dokuments" msgstr "Standard Sichtbarkeit des Dokuments"
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "Default Signature Settings"
msgstr ""
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx
msgid "delete" msgid "delete"
msgstr "löschen" msgstr "löschen"
@@ -2201,6 +2245,11 @@ msgstr "Dokument \"{0}\" - Abgelehnt von {1}"
msgid "Document \"{0}\" - Rejection Confirmed" msgid "Document \"{0}\" - Rejection Confirmed"
msgstr "Dokument \"{0}\" - Ablehnung Bestätigt" msgstr "Dokument \"{0}\" - Ablehnung Bestätigt"
#. placeholder {0}: document.title
#: packages/lib/jobs/definitions/emails/send-document-cancelled-emails.handler.ts
msgid "Document \"{0}\" Cancelled"
msgstr ""
#: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/template-flow/add-template-settings.tsx
#: packages/ui/primitives/document-flow/add-settings.tsx #: packages/ui/primitives/document-flow/add-settings.tsx
#: packages/ui/components/document/document-global-auth-access-select.tsx #: packages/ui/components/document/document-global-auth-access-select.tsx
@@ -2349,6 +2398,10 @@ msgstr "Dokumentpräferenzen aktualisiert"
msgid "Document re-sent" msgid "Document re-sent"
msgstr "Dokument erneut gesendet" msgstr "Dokument erneut gesendet"
#: apps/remix/app/components/general/document/document-status.tsx
msgid "Document rejected"
msgstr ""
#: apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx #: apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx
#: apps/remix/app/components/embed/embed-document-rejected.tsx #: apps/remix/app/components/embed/embed-document-rejected.tsx
#: packages/email/template-components/template-document-rejected.tsx #: packages/email/template-components/template-document-rejected.tsx
@@ -2486,6 +2539,10 @@ msgstr "Entwurfte Dokumente"
msgid "Drag & drop your PDF here." msgid "Drag & drop your PDF here."
msgstr "Ziehen Sie Ihr PDF hierher." msgstr "Ziehen Sie Ihr PDF hierher."
#: packages/lib/constants/document.ts
msgid "Draw"
msgstr ""
#: packages/ui/primitives/template-flow/add-template-fields.tsx #: packages/ui/primitives/template-flow/add-template-fields.tsx
#: packages/ui/primitives/document-flow/add-fields.tsx #: packages/ui/primitives/document-flow/add-fields.tsx
msgid "Dropdown" msgid "Dropdown"
@@ -2543,6 +2600,7 @@ msgstr "Offenlegung der elektronischen Unterschrift"
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx
#: apps/remix/app/components/general/document-signing/document-signing-email-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-email-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
#: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx #: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx
#: apps/remix/app/components/forms/signin.tsx #: apps/remix/app/components/forms/signin.tsx
#: apps/remix/app/components/forms/profile.tsx #: apps/remix/app/components/forms/profile.tsx
@@ -2553,6 +2611,7 @@ msgstr "Offenlegung der elektronischen Unterschrift"
#: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-add-dialog.tsx #: apps/remix/app/components/dialogs/team-email-add-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-fields.tsx #: packages/ui/primitives/template-flow/add-template-fields.tsx
@@ -2646,15 +2705,6 @@ msgstr "Direktlink-Signierung aktivieren"
msgid "Enable signing order" msgid "Enable signing order"
msgstr "Aktiviere die Signaturreihenfolge" msgstr "Aktiviere die Signaturreihenfolge"
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "Enable Typed Signature"
msgstr "Getippte Unterschrift aktivieren"
#: packages/ui/primitives/template-flow/add-template-fields.tsx
#: packages/ui/primitives/document-flow/add-fields.tsx
msgid "Enable Typed Signatures"
msgstr "Aktivieren Sie getippte Unterschriften"
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks._index.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks._index.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx
#: apps/remix/app/routes/_authenticated+/settings+/webhooks._index.tsx #: apps/remix/app/routes/_authenticated+/settings+/webhooks._index.tsx
@@ -2929,7 +2979,7 @@ msgstr "Zum Eigentümer gehen"
msgid "Go to your <0>public profile settings</0> to add documents." msgid "Go to your <0>public profile settings</0> to add documents."
msgstr "Gehen Sie zu Ihren <0>öffentlichen Profileinstellungen</0>, um Dokumente hinzuzufügen." msgstr "Gehen Sie zu Ihren <0>öffentlichen Profileinstellungen</0>, um Dokumente hinzuzufügen."
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Green" msgid "Green"
msgstr "Grün" msgstr "Grün"
@@ -3483,10 +3533,12 @@ msgstr "Meine Vorlagen"
#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx
#: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/general/claim-account.tsx
#: apps/remix/app/components/general/document-signing/document-signing-name-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-name-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
#: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx
#: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-add-dialog.tsx #: apps/remix/app/components/dialogs/team-email-add-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-fields.tsx #: packages/ui/primitives/template-flow/add-template-fields.tsx
@@ -3537,6 +3589,7 @@ msgstr "Neue Vorlage"
#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/embed/embed-document-signing-page.tsx #: apps/remix/app/components/embed/embed-document-signing-page.tsx
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx #: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
#: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx
msgid "Next" msgid "Next"
msgstr "Nächster" msgstr "Nächster"
@@ -3954,6 +4007,7 @@ msgstr "Bitte geben Sie einen aussagekräftigen Namen für Ihr Token ein. Dies w
#: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/general/claim-account.tsx
#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/forms/profile.tsx
msgid "Please enter a valid name." msgid "Please enter a valid name."
msgstr "Bitte geben Sie einen gültigen Namen ein." msgstr "Bitte geben Sie einen gültigen Namen ein."
@@ -3989,10 +4043,6 @@ msgstr "Bitte beachten Sie, dass Sie den Zugriff auf alle Dokumente, die mit die
msgid "Please note that you will lose access to all documents associated with this team & all the members will be removed and notified" msgid "Please note that you will lose access to all documents associated with this team & all the members will be removed and notified"
msgstr "Bitte beachten Sie, dass Sie den Zugriff auf alle mit diesem Team verbundenen Dokumente verlieren werden und alle Mitglieder entfernt und benachrichtigt werden." msgstr "Bitte beachten Sie, dass Sie den Zugriff auf alle mit diesem Team verbundenen Dokumente verlieren werden und alle Mitglieder entfernt und benachrichtigt werden."
#: apps/remix/app/components/general/document-signing/document-signing-reject-dialog.tsx
msgid "Please provide a reason"
msgstr "Bitte geben Sie einen Grund an"
#: apps/remix/app/components/forms/2fa/disable-authenticator-app-dialog.tsx #: apps/remix/app/components/forms/2fa/disable-authenticator-app-dialog.tsx
msgid "Please provide a token from the authenticator, or a backup code. If you do not have a backup code available, please contact support." msgid "Please provide a token from the authenticator, or a backup code. If you do not have a backup code available, please contact support."
msgstr "Bitte geben Sie ein Token von der Authentifizierungs-App oder einen Backup-Code an. Wenn Sie keinen Backup-Code haben, kontaktieren Sie bitte den Support." msgstr "Bitte geben Sie ein Token von der Authentifizierungs-App oder einen Backup-Code an. Wenn Sie keinen Backup-Code haben, kontaktieren Sie bitte den Support."
@@ -4063,6 +4113,10 @@ msgstr "Privat"
msgid "Private templates can only be modified and viewed by you." msgid "Private templates can only be modified and viewed by you."
msgstr "Private Vorlagen können nur von Ihnen bearbeitet und angezeigt werden." msgstr "Private Vorlagen können nur von Ihnen bearbeitet und angezeigt werden."
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
msgid "Proceed"
msgstr ""
#: apps/remix/app/routes/_authenticated+/settings+/profile.tsx #: apps/remix/app/routes/_authenticated+/settings+/profile.tsx
#: apps/remix/app/components/general/settings-nav-mobile.tsx #: apps/remix/app/components/general/settings-nav-mobile.tsx
#: apps/remix/app/components/general/settings-nav-desktop.tsx #: apps/remix/app/components/general/settings-nav-desktop.tsx
@@ -4140,6 +4194,10 @@ msgstr "Bereit"
msgid "Reason" msgid "Reason"
msgstr "Grund" msgstr "Grund"
#: packages/email/template-components/template-document-cancel.tsx
msgid "Reason for cancellation: {cancellationReason}"
msgstr ""
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx #: apps/remix/app/components/general/document/document-page-view-recipients.tsx
msgid "Reason for rejection: " msgid "Reason for rejection: "
msgstr "" msgstr ""
@@ -4222,7 +4280,7 @@ msgstr "Wiederherstellungscode kopiert"
msgid "Recovery codes" msgid "Recovery codes"
msgstr "Wiederherstellungscodes" msgstr "Wiederherstellungscodes"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Red" msgid "Red"
msgstr "Rot" msgstr "Rot"
@@ -4243,8 +4301,11 @@ msgstr "Registrierung erfolgreich"
msgid "Reject Document" msgid "Reject Document"
msgstr "Dokument Ablehnen" msgstr "Dokument Ablehnen"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx #: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx
#: apps/remix/app/components/general/document/document-status.tsx
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx #: apps/remix/app/components/general/document/document-page-view-recipients.tsx
#: packages/lib/constants/document.ts
msgid "Rejected" msgid "Rejected"
msgstr "Abgelehnt" msgstr "Abgelehnt"
@@ -4429,8 +4490,6 @@ msgstr "Zeilen pro Seite"
#: apps/remix/app/components/general/document-signing/document-signing-text-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-text-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-number-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-number-field.tsx
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
#: apps/remix/app/components/forms/team-branding-preferences-form.tsx
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx #: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
msgid "Save" msgid "Save"
@@ -4740,7 +4799,6 @@ msgstr "Registrieren mit OIDC"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx #: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx
#: apps/remix/app/components/forms/profile.tsx #: apps/remix/app/components/forms/profile.tsx
@@ -4757,12 +4815,17 @@ msgstr "Unterschrift"
msgid "Signature ID" msgid "Signature ID"
msgstr "Signatur-ID" msgstr "Signatur-ID"
#: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx #: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx msgid "Signature is too small"
#: apps/remix/app/components/embed/embed-document-signing-page.tsx msgstr ""
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
msgid "Signature is too small. Please provide a more complete signature." #: apps/remix/app/components/forms/profile.tsx
msgstr "Die Unterschrift ist zu klein. Bitte geben Sie eine vollständigere Unterschrift an." msgid "Signature Pad cannot be empty."
msgstr ""
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "Signature types"
msgstr ""
#: apps/remix/app/routes/_authenticated+/admin+/stats.tsx #: apps/remix/app/routes/_authenticated+/admin+/stats.tsx
msgid "Signatures Collected" msgid "Signatures Collected"
@@ -4987,6 +5050,7 @@ msgid "Subject <0>(Optional)</0>"
msgstr "Betreff <0>(Optional)</0>" msgstr "Betreff <0>(Optional)</0>"
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
msgid "Submitting..." msgid "Submitting..."
msgstr "" msgstr ""
@@ -5501,6 +5565,10 @@ msgstr "Der Token, den Sie zur Zurücksetzung Ihres Passworts verwendet haben, i
msgid "The two-factor authentication code provided is incorrect" msgid "The two-factor authentication code provided is incorrect"
msgstr "" msgstr ""
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "The types of signatures that recipients are allowed to use when signing the document."
msgstr ""
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx
#: apps/remix/app/routes/_authenticated+/settings+/webhooks.$id.tsx #: apps/remix/app/routes/_authenticated+/settings+/webhooks.$id.tsx
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx #: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
@@ -5582,6 +5650,10 @@ msgstr "Dieses Dokument wurde vom Eigentümer storniert und steht anderen nicht
msgid "This document has been cancelled by the owner." msgid "This document has been cancelled by the owner."
msgstr "Dieses Dokument wurde vom Eigentümer storniert." msgstr "Dieses Dokument wurde vom Eigentümer storniert."
#: apps/remix/app/routes/_authenticated+/documents.$id._index.tsx
msgid "This document has been rejected by a recipient"
msgstr ""
#: apps/remix/app/routes/_authenticated+/documents.$id._index.tsx #: apps/remix/app/routes/_authenticated+/documents.$id._index.tsx
msgid "This document has been signed by all recipients" msgid "This document has been signed by all recipients"
msgstr "Dieses Dokument wurde von allen Empfängern unterschrieben" msgstr "Dieses Dokument wurde von allen Empfängern unterschrieben"
@@ -5730,6 +5802,10 @@ msgstr "Zeitzone"
msgid "Title" msgid "Title"
msgstr "Titel" msgstr "Titel"
#: packages/ui/primitives/document-flow/add-settings.types.ts
msgid "Title cannot be empty"
msgstr ""
#: apps/remix/app/routes/_unauthenticated+/team.invite.$token.tsx #: apps/remix/app/routes/_unauthenticated+/team.invite.$token.tsx
msgid "To accept this invitation you must create an account." msgid "To accept this invitation you must create an account."
msgstr "Um diese Einladung anzunehmen, müssen Sie ein Konto erstellen." msgstr "Um diese Einladung anzunehmen, müssen Sie ein Konto erstellen."
@@ -5897,6 +5973,7 @@ msgstr "Zwei-Faktor-Wiederauthentifizierung"
#: apps/remix/app/components/tables/templates-table.tsx #: apps/remix/app/components/tables/templates-table.tsx
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: packages/lib/constants/document.ts
msgid "Type" msgid "Type"
msgstr "Typ" msgstr "Typ"
@@ -6004,6 +6081,7 @@ msgstr "Unvollendet"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.billing.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.billing.tsx
msgid "Unknown" msgid "Unknown"
msgstr "Unbekannt" msgstr "Unbekannt"
@@ -6014,11 +6092,14 @@ msgstr "Unbezahlt"
#: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
#: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx #: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
#: apps/remix/app/components/forms/team-branding-preferences-form.tsx
#: apps/remix/app/components/forms/public-profile-form.tsx #: apps/remix/app/components/forms/public-profile-form.tsx
#: apps/remix/app/components/dialogs/team-member-update-dialog.tsx #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
msgid "Update" msgid "Update"
msgstr "Aktualisieren" msgstr "Aktualisieren"
@@ -6039,6 +6120,8 @@ msgid "Update profile"
msgstr "Profil aktualisieren" msgstr "Profil aktualisieren"
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
msgid "Update Recipient" msgid "Update Recipient"
msgstr "Empfänger aktualisieren" msgstr "Empfänger aktualisieren"
@@ -6077,10 +6160,6 @@ msgstr "Webhook aktualisieren"
msgid "Updating password..." msgid "Updating password..."
msgstr "Passwort wird aktualisiert..." msgstr "Passwort wird aktualisiert..."
#: apps/remix/app/components/forms/profile.tsx
msgid "Updating profile..."
msgstr "Profil wird aktualisiert..."
#: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx #: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx
msgid "Updating Your Information" msgid "Updating Your Information"
msgstr "Aktualisierung Ihrer Informationen" msgstr "Aktualisierung Ihrer Informationen"
@@ -6089,6 +6168,10 @@ msgstr "Aktualisierung Ihrer Informationen"
msgid "Upgrade" msgid "Upgrade"
msgstr "Upgrade" msgstr "Upgrade"
#: packages/lib/constants/document.ts
msgid "Upload"
msgstr ""
#: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx #: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx
msgid "Upload a CSV file to create multiple documents from this template. Each row represents one document with its recipient details." msgid "Upload a CSV file to create multiple documents from this template. Each row represents one document with its recipient details."
msgstr "Laden Sie eine CSV-Datei hoch, um mehrere Dokumente aus dieser Vorlage zu erstellen. Jede Zeile repräsentiert ein Dokument mit den Empfängerdaten." msgstr "Laden Sie eine CSV-Datei hoch, um mehrere Dokumente aus dieser Vorlage zu erstellen. Jede Zeile repräsentiert ein Dokument mit den Empfängerdaten."
@@ -6113,7 +6196,7 @@ msgstr "CSV hochladen"
msgid "Upload custom document" msgid "Upload custom document"
msgstr "Benutzerdefiniertes Dokument hochladen" msgstr "Benutzerdefiniertes Dokument hochladen"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-upload.tsx
msgid "Upload Signature" msgid "Upload Signature"
msgstr "Signatur hochladen" msgstr "Signatur hochladen"
@@ -6287,6 +6370,7 @@ msgstr "Dokument anzeigen"
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/email/template-components/template-document-rejected.tsx #: packages/email/template-components/template-document-rejected.tsx
#: packages/email/template-components/template-document-invite.tsx #: packages/email/template-components/template-document-invite.tsx
msgid "View Document" msgid "View Document"
@@ -6653,6 +6737,11 @@ msgstr "Willkommen bei Documenso!"
msgid "Were you trying to edit this document instead?" msgid "Were you trying to edit this document instead?"
msgstr "Hast du stattdessen versucht, dieses Dokument zu bearbeiten?" msgstr "Hast du stattdessen versucht, dieses Dokument zu bearbeiten?"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/document-flow/add-signers.tsx
msgid "When enabled, signers can choose who should sign next in the sequence instead of following the predefined order."
msgstr ""
#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
msgid "When you click continue, you will be prompted to add the first available authenticator on your system." msgid "When you click continue, you will be prompted to add the first available authenticator on your system."
msgstr "Wenn Sie auf Fortfahren klicken, werden Sie aufgefordert, den ersten verfügbaren Authenticator auf Ihrem System hinzuzufügen." msgstr "Wenn Sie auf Fortfahren klicken, werden Sie aufgefordert, den ersten verfügbaren Authenticator auf Ihrem System hinzuzufügen."

View File

@@ -414,6 +414,10 @@ msgstr "<0>{teamName}</0> has requested to use your email address for their team
msgid "<0>Click to upload</0> or drag and drop" msgid "<0>Click to upload</0> or drag and drop"
msgstr "<0>Click to upload</0> or drag and drop" msgstr "<0>Click to upload</0> or drag and drop"
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "<0>Drawn</0> - A signature that is drawn using a mouse or stylus."
msgstr "<0>Drawn</0> - A signature that is drawn using a mouse or stylus."
#: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/template-flow/add-template-settings.tsx
msgid "<0>Email</0> - The recipient will be emailed the document to sign, approve, etc." msgid "<0>Email</0> - The recipient will be emailed the document to sign, approve, etc."
msgstr "<0>Email</0> - The recipient will be emailed the document to sign, approve, etc." msgstr "<0>Email</0> - The recipient will be emailed the document to sign, approve, etc."
@@ -460,6 +464,14 @@ msgstr "<0>Require passkey</0> - The recipient must have an account and passkey
msgid "<0>Sender:</0> All" msgid "<0>Sender:</0> All"
msgstr "<0>Sender:</0> All" msgstr "<0>Sender:</0> All"
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "<0>Typed</0> - A signature that is typed using a keyboard."
msgstr "<0>Typed</0> - A signature that is typed using a keyboard."
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "<0>Uploaded</0> - A signature that is uploaded from a file."
msgstr "<0>Uploaded</0> - A signature that is uploaded from a file."
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
msgid "<0>You are about to complete approving <1>\"{documentTitle}\"</1>.</0><2/> Are you sure?" msgid "<0>You are about to complete approving <1>\"{documentTitle}\"</1>.</0><2/> Are you sure?"
msgstr "<0>You are about to complete approving <1>\"{documentTitle}\"</1>.</0><2/> Are you sure?" msgstr "<0>You are about to complete approving <1>\"{documentTitle}\"</1>.</0><2/> Are you sure?"
@@ -893,6 +905,16 @@ msgstr "All Time"
msgid "Allow document recipients to reply directly to this email address" msgid "Allow document recipients to reply directly to this email address"
msgstr "Allow document recipients to reply directly to this email address" msgstr "Allow document recipients to reply directly to this email address"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/document-flow/add-signers.tsx
msgid "Allow signers to dictate next signer"
msgstr "Allow signers to dictate next signer"
#: packages/ui/primitives/template-flow/add-template-settings.tsx
#: packages/ui/primitives/document-flow/add-settings.tsx
msgid "Allowed Signature Types"
msgstr "Allowed Signature Types"
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx #: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "Allows authenticating using biometrics, password managers, hardware keys, etc." msgid "Allows authenticating using biometrics, password managers, hardware keys, etc."
msgstr "Allows authenticating using biometrics, password managers, hardware keys, etc." msgstr "Allows authenticating using biometrics, password managers, hardware keys, etc."
@@ -1232,6 +1254,13 @@ msgstr "Assisted"
msgid "Assisting" msgid "Assisting"
msgstr "Assisting" msgstr "Assisting"
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
#: packages/ui/primitives/template-flow/add-template-settings.types.tsx
#: packages/ui/primitives/document-flow/add-settings.types.ts
#: packages/lib/types/document-meta.ts
msgid "At least one signature type must be enabled"
msgstr "At least one signature type must be enabled"
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
msgid "Attempts sealing the document again, useful for after a code change has occurred to resolve an erroneous document." msgid "Attempts sealing the document again, useful for after a code change has occurred to resolve an erroneous document."
msgstr "Attempts sealing the document again, useful for after a code change has occurred to resolve an erroneous document." msgstr "Attempts sealing the document again, useful for after a code change has occurred to resolve an erroneous document."
@@ -1304,11 +1333,11 @@ msgstr "Before you get started, please confirm your email address by clicking th
msgid "Billing" msgid "Billing"
msgstr "Billing" msgstr "Billing"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Black" msgid "Black"
msgstr "Black" msgstr "Black"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Blue" msgid "Blue"
msgstr "Blue" msgstr "Blue"
@@ -1379,6 +1408,10 @@ msgstr "By proceeding to use the electronic signature service provided by Docume
msgid "By proceeding with your electronic signature, you acknowledge and consent that it will be used to sign the given document and holds the same legal validity as a handwritten signature. By completing the electronic signing process, you affirm your understanding and acceptance of these conditions." msgid "By proceeding with your electronic signature, you acknowledge and consent that it will be used to sign the given document and holds the same legal validity as a handwritten signature. By completing the electronic signing process, you affirm your understanding and acceptance of these conditions."
msgstr "By proceeding with your electronic signature, you acknowledge and consent that it will be used to sign the given document and holds the same legal validity as a handwritten signature. By completing the electronic signing process, you affirm your understanding and acceptance of these conditions." msgstr "By proceeding with your electronic signature, you acknowledge and consent that it will be used to sign the given document and holds the same legal validity as a handwritten signature. By completing the electronic signing process, you affirm your understanding and acceptance of these conditions."
#: apps/remix/app/components/forms/signup.tsx
msgid "By proceeding, you agree to our <0>Terms of Service</0> and <1>Privacy Policy</1>."
msgstr "By proceeding, you agree to our <0>Terms of Service</0> and <1>Privacy Policy</1>."
#: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx #: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx
msgid "By using the electronic signature feature, you are consenting to conduct transactions and receive disclosures electronically. You acknowledge that your electronic signature on documents is binding and that you accept the terms outlined in the documents you are signing." msgid "By using the electronic signature feature, you are consenting to conduct transactions and receive disclosures electronically. You acknowledge that your electronic signature on documents is binding and that you accept the terms outlined in the documents you are signing."
msgstr "By using the electronic signature feature, you are consenting to conduct transactions and receive disclosures electronically. You acknowledge that your electronic signature on documents is binding and that you accept the terms outlined in the documents you are signing." msgstr "By using the electronic signature feature, you are consenting to conduct transactions and receive disclosures electronically. You acknowledge that your electronic signature on documents is binding and that you accept the terms outlined in the documents you are signing."
@@ -1431,6 +1464,8 @@ msgstr "Can prepare"
#: apps/remix/app/components/dialogs/document-move-dialog.tsx #: apps/remix/app/components/dialogs/document-move-dialog.tsx
#: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx #: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx #: packages/ui/primitives/document-flow/send-document-action-dialog.tsx
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx #: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
msgid "Cancel" msgid "Cancel"
@@ -1517,7 +1552,7 @@ msgstr "Clear file"
msgid "Clear filters" msgid "Clear filters"
msgstr "Clear filters" msgstr "Clear filters"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
msgid "Clear Signature" msgid "Clear Signature"
msgstr "Clear Signature" msgstr "Clear Signature"
@@ -1692,6 +1727,7 @@ msgstr "Content"
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/document-flow/document-flow-root.tsx #: packages/ui/primitives/document-flow/document-flow-root.tsx
msgid "Continue" msgid "Continue"
msgstr "Continue" msgstr "Continue"
@@ -1732,14 +1768,18 @@ msgstr "Controls the default visibility of an uploaded document."
msgid "Controls the formatting of the message that will be sent when inviting a recipient to sign a document. If a custom message has been provided while configuring the document, it will be used instead." msgid "Controls the formatting of the message that will be sent when inviting a recipient to sign a document. If a custom message has been provided while configuring the document, it will be used instead."
msgstr "Controls the formatting of the message that will be sent when inviting a recipient to sign a document. If a custom message has been provided while configuring the document, it will be used instead." msgstr "Controls the formatting of the message that will be sent when inviting a recipient to sign a document. If a custom message has been provided while configuring the document, it will be used instead."
#: apps/remix/app/components/forms/team-document-preferences-form.tsx #: packages/ui/primitives/document-flow/add-settings.tsx
msgid "Controls whether the recipients can sign the documents using a typed signature. Enable or disable the typed signature globally." msgid "Controls the language for the document, including the language to be used for email notifications, and the final certificate that is generated and attached to the document."
msgstr "Controls whether the recipients can sign the documents using a typed signature. Enable or disable the typed signature globally." msgstr "Controls the language for the document, including the language to be used for email notifications, and the final certificate that is generated and attached to the document."
#: apps/remix/app/components/forms/team-document-preferences-form.tsx #: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "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." msgid "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."
msgstr "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." msgstr "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."
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "Controls which signatures are allowed to be used when signing a document."
msgstr "Controls which signatures are allowed to be used when signing a document."
#: apps/remix/app/components/general/document/document-recipient-link-copy-dialog.tsx #: apps/remix/app/components/general/document/document-recipient-link-copy-dialog.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
msgid "Copied" msgid "Copied"
@@ -1970,6 +2010,10 @@ msgstr "Default Document Language"
msgid "Default Document Visibility" msgid "Default Document Visibility"
msgstr "Default Document Visibility" msgstr "Default Document Visibility"
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "Default Signature Settings"
msgstr "Default Signature Settings"
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx
msgid "delete" msgid "delete"
msgstr "delete" msgstr "delete"
@@ -2196,6 +2240,11 @@ msgstr "Document \"{0}\" - Rejected by {1}"
msgid "Document \"{0}\" - Rejection Confirmed" msgid "Document \"{0}\" - Rejection Confirmed"
msgstr "Document \"{0}\" - Rejection Confirmed" msgstr "Document \"{0}\" - Rejection Confirmed"
#. placeholder {0}: document.title
#: packages/lib/jobs/definitions/emails/send-document-cancelled-emails.handler.ts
msgid "Document \"{0}\" Cancelled"
msgstr "Document \"{0}\" Cancelled"
#: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/template-flow/add-template-settings.tsx
#: packages/ui/primitives/document-flow/add-settings.tsx #: packages/ui/primitives/document-flow/add-settings.tsx
#: packages/ui/components/document/document-global-auth-access-select.tsx #: packages/ui/components/document/document-global-auth-access-select.tsx
@@ -2344,6 +2393,10 @@ msgstr "Document preferences updated"
msgid "Document re-sent" msgid "Document re-sent"
msgstr "Document re-sent" msgstr "Document re-sent"
#: apps/remix/app/components/general/document/document-status.tsx
msgid "Document rejected"
msgstr "Document rejected"
#: apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx #: apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx
#: apps/remix/app/components/embed/embed-document-rejected.tsx #: apps/remix/app/components/embed/embed-document-rejected.tsx
#: packages/email/template-components/template-document-rejected.tsx #: packages/email/template-components/template-document-rejected.tsx
@@ -2481,6 +2534,10 @@ msgstr "Drafted Documents"
msgid "Drag & drop your PDF here." msgid "Drag & drop your PDF here."
msgstr "Drag & drop your PDF here." msgstr "Drag & drop your PDF here."
#: packages/lib/constants/document.ts
msgid "Draw"
msgstr "Draw"
#: packages/ui/primitives/template-flow/add-template-fields.tsx #: packages/ui/primitives/template-flow/add-template-fields.tsx
#: packages/ui/primitives/document-flow/add-fields.tsx #: packages/ui/primitives/document-flow/add-fields.tsx
msgid "Dropdown" msgid "Dropdown"
@@ -2538,6 +2595,7 @@ msgstr "Electronic Signature Disclosure"
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx
#: apps/remix/app/components/general/document-signing/document-signing-email-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-email-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
#: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx #: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx
#: apps/remix/app/components/forms/signin.tsx #: apps/remix/app/components/forms/signin.tsx
#: apps/remix/app/components/forms/profile.tsx #: apps/remix/app/components/forms/profile.tsx
@@ -2548,6 +2606,7 @@ msgstr "Electronic Signature Disclosure"
#: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-add-dialog.tsx #: apps/remix/app/components/dialogs/team-email-add-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-fields.tsx #: packages/ui/primitives/template-flow/add-template-fields.tsx
@@ -2641,15 +2700,6 @@ msgstr "Enable Direct Link Signing"
msgid "Enable signing order" msgid "Enable signing order"
msgstr "Enable signing order" msgstr "Enable signing order"
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "Enable Typed Signature"
msgstr "Enable Typed Signature"
#: packages/ui/primitives/template-flow/add-template-fields.tsx
#: packages/ui/primitives/document-flow/add-fields.tsx
msgid "Enable Typed Signatures"
msgstr "Enable Typed Signatures"
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks._index.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks._index.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx
#: apps/remix/app/routes/_authenticated+/settings+/webhooks._index.tsx #: apps/remix/app/routes/_authenticated+/settings+/webhooks._index.tsx
@@ -2924,7 +2974,7 @@ msgstr "Go to owner"
msgid "Go to your <0>public profile settings</0> to add documents." msgid "Go to your <0>public profile settings</0> to add documents."
msgstr "Go to your <0>public profile settings</0> to add documents." msgstr "Go to your <0>public profile settings</0> to add documents."
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Green" msgid "Green"
msgstr "Green" msgstr "Green"
@@ -3478,10 +3528,12 @@ msgstr "My templates"
#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx
#: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/general/claim-account.tsx
#: apps/remix/app/components/general/document-signing/document-signing-name-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-name-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
#: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx
#: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-add-dialog.tsx #: apps/remix/app/components/dialogs/team-email-add-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-fields.tsx #: packages/ui/primitives/template-flow/add-template-fields.tsx
@@ -3532,6 +3584,7 @@ msgstr "New Template"
#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/embed/embed-document-signing-page.tsx #: apps/remix/app/components/embed/embed-document-signing-page.tsx
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx #: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
#: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx
msgid "Next" msgid "Next"
msgstr "Next" msgstr "Next"
@@ -3949,6 +4002,7 @@ msgstr "Please enter a meaningful name for your token. This will help you identi
#: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/general/claim-account.tsx
#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/forms/profile.tsx
msgid "Please enter a valid name." msgid "Please enter a valid name."
msgstr "Please enter a valid name." msgstr "Please enter a valid name."
@@ -3984,10 +4038,6 @@ msgstr "Please note that this action is irreversible. Once confirmed, your webho
msgid "Please note that you will lose access to all documents associated with this team & all the members will be removed and notified" msgid "Please note that you will lose access to all documents associated with this team & all the members will be removed and notified"
msgstr "Please note that you will lose access to all documents associated with this team & all the members will be removed and notified" msgstr "Please note that you will lose access to all documents associated with this team & all the members will be removed and notified"
#: apps/remix/app/components/general/document-signing/document-signing-reject-dialog.tsx
msgid "Please provide a reason"
msgstr "Please provide a reason"
#: apps/remix/app/components/forms/2fa/disable-authenticator-app-dialog.tsx #: apps/remix/app/components/forms/2fa/disable-authenticator-app-dialog.tsx
msgid "Please provide a token from the authenticator, or a backup code. If you do not have a backup code available, please contact support." msgid "Please provide a token from the authenticator, or a backup code. If you do not have a backup code available, please contact support."
msgstr "Please provide a token from the authenticator, or a backup code. If you do not have a backup code available, please contact support." msgstr "Please provide a token from the authenticator, or a backup code. If you do not have a backup code available, please contact support."
@@ -4058,6 +4108,10 @@ msgstr "Private"
msgid "Private templates can only be modified and viewed by you." msgid "Private templates can only be modified and viewed by you."
msgstr "Private templates can only be modified and viewed by you." msgstr "Private templates can only be modified and viewed by you."
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
msgid "Proceed"
msgstr "Proceed"
#: apps/remix/app/routes/_authenticated+/settings+/profile.tsx #: apps/remix/app/routes/_authenticated+/settings+/profile.tsx
#: apps/remix/app/components/general/settings-nav-mobile.tsx #: apps/remix/app/components/general/settings-nav-mobile.tsx
#: apps/remix/app/components/general/settings-nav-desktop.tsx #: apps/remix/app/components/general/settings-nav-desktop.tsx
@@ -4135,6 +4189,10 @@ msgstr "Ready"
msgid "Reason" msgid "Reason"
msgstr "Reason" msgstr "Reason"
#: packages/email/template-components/template-document-cancel.tsx
msgid "Reason for cancellation: {cancellationReason}"
msgstr "Reason for cancellation: {cancellationReason}"
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx #: apps/remix/app/components/general/document/document-page-view-recipients.tsx
msgid "Reason for rejection: " msgid "Reason for rejection: "
msgstr "Reason for rejection: " msgstr "Reason for rejection: "
@@ -4217,7 +4275,7 @@ msgstr "Recovery code copied"
msgid "Recovery codes" msgid "Recovery codes"
msgstr "Recovery codes" msgstr "Recovery codes"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Red" msgid "Red"
msgstr "Red" msgstr "Red"
@@ -4238,8 +4296,11 @@ msgstr "Registration Successful"
msgid "Reject Document" msgid "Reject Document"
msgstr "Reject Document" msgstr "Reject Document"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx #: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx
#: apps/remix/app/components/general/document/document-status.tsx
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx #: apps/remix/app/components/general/document/document-page-view-recipients.tsx
#: packages/lib/constants/document.ts
msgid "Rejected" msgid "Rejected"
msgstr "Rejected" msgstr "Rejected"
@@ -4424,8 +4485,6 @@ msgstr "Rows per page"
#: apps/remix/app/components/general/document-signing/document-signing-text-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-text-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-number-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-number-field.tsx
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
#: apps/remix/app/components/forms/team-branding-preferences-form.tsx
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx #: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
msgid "Save" msgid "Save"
@@ -4735,7 +4794,6 @@ msgstr "Sign Up with OIDC"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx #: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx
#: apps/remix/app/components/forms/profile.tsx #: apps/remix/app/components/forms/profile.tsx
@@ -4752,12 +4810,17 @@ msgstr "Signature"
msgid "Signature ID" msgid "Signature ID"
msgstr "Signature ID" msgstr "Signature ID"
#: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx #: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx msgid "Signature is too small"
#: apps/remix/app/components/embed/embed-document-signing-page.tsx msgstr "Signature is too small"
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
msgid "Signature is too small. Please provide a more complete signature." #: apps/remix/app/components/forms/profile.tsx
msgstr "Signature is too small. Please provide a more complete signature." msgid "Signature Pad cannot be empty."
msgstr "Signature Pad cannot be empty."
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "Signature types"
msgstr "Signature types"
#: apps/remix/app/routes/_authenticated+/admin+/stats.tsx #: apps/remix/app/routes/_authenticated+/admin+/stats.tsx
msgid "Signatures Collected" msgid "Signatures Collected"
@@ -4982,6 +5045,7 @@ msgid "Subject <0>(Optional)</0>"
msgstr "Subject <0>(Optional)</0>" msgstr "Subject <0>(Optional)</0>"
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
msgid "Submitting..." msgid "Submitting..."
msgstr "Submitting..." msgstr "Submitting..."
@@ -5498,6 +5562,10 @@ msgstr "The token you have used to reset your password is either expired or it n
msgid "The two-factor authentication code provided is incorrect" msgid "The two-factor authentication code provided is incorrect"
msgstr "The two-factor authentication code provided is incorrect" msgstr "The two-factor authentication code provided is incorrect"
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "The types of signatures that recipients are allowed to use when signing the document."
msgstr "The types of signatures that recipients are allowed to use when signing the document."
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx
#: apps/remix/app/routes/_authenticated+/settings+/webhooks.$id.tsx #: apps/remix/app/routes/_authenticated+/settings+/webhooks.$id.tsx
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx #: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
@@ -5579,6 +5647,10 @@ msgstr "This document has been cancelled by the owner and is no longer available
msgid "This document has been cancelled by the owner." msgid "This document has been cancelled by the owner."
msgstr "This document has been cancelled by the owner." msgstr "This document has been cancelled by the owner."
#: apps/remix/app/routes/_authenticated+/documents.$id._index.tsx
msgid "This document has been rejected by a recipient"
msgstr "This document has been rejected by a recipient"
#: apps/remix/app/routes/_authenticated+/documents.$id._index.tsx #: apps/remix/app/routes/_authenticated+/documents.$id._index.tsx
msgid "This document has been signed by all recipients" msgid "This document has been signed by all recipients"
msgstr "This document has been signed by all recipients" msgstr "This document has been signed by all recipients"
@@ -5727,6 +5799,10 @@ msgstr "Time Zone"
msgid "Title" msgid "Title"
msgstr "Title" msgstr "Title"
#: packages/ui/primitives/document-flow/add-settings.types.ts
msgid "Title cannot be empty"
msgstr "Title cannot be empty"
#: apps/remix/app/routes/_unauthenticated+/team.invite.$token.tsx #: apps/remix/app/routes/_unauthenticated+/team.invite.$token.tsx
msgid "To accept this invitation you must create an account." msgid "To accept this invitation you must create an account."
msgstr "To accept this invitation you must create an account." msgstr "To accept this invitation you must create an account."
@@ -5894,6 +5970,7 @@ msgstr "Two-Factor Re-Authentication"
#: apps/remix/app/components/tables/templates-table.tsx #: apps/remix/app/components/tables/templates-table.tsx
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: packages/lib/constants/document.ts
msgid "Type" msgid "Type"
msgstr "Type" msgstr "Type"
@@ -6001,6 +6078,7 @@ msgstr "Uncompleted"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.billing.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.billing.tsx
msgid "Unknown" msgid "Unknown"
msgstr "Unknown" msgstr "Unknown"
@@ -6011,11 +6089,14 @@ msgstr "Unpaid"
#: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
#: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx #: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
#: apps/remix/app/components/forms/team-branding-preferences-form.tsx
#: apps/remix/app/components/forms/public-profile-form.tsx #: apps/remix/app/components/forms/public-profile-form.tsx
#: apps/remix/app/components/dialogs/team-member-update-dialog.tsx #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
msgid "Update" msgid "Update"
msgstr "Update" msgstr "Update"
@@ -6036,6 +6117,8 @@ msgid "Update profile"
msgstr "Update profile" msgstr "Update profile"
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
msgid "Update Recipient" msgid "Update Recipient"
msgstr "Update Recipient" msgstr "Update Recipient"
@@ -6074,10 +6157,6 @@ msgstr "Update webhook"
msgid "Updating password..." msgid "Updating password..."
msgstr "Updating password..." msgstr "Updating password..."
#: apps/remix/app/components/forms/profile.tsx
msgid "Updating profile..."
msgstr "Updating profile..."
#: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx #: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx
msgid "Updating Your Information" msgid "Updating Your Information"
msgstr "Updating Your Information" msgstr "Updating Your Information"
@@ -6086,6 +6165,10 @@ msgstr "Updating Your Information"
msgid "Upgrade" msgid "Upgrade"
msgstr "Upgrade" msgstr "Upgrade"
#: packages/lib/constants/document.ts
msgid "Upload"
msgstr "Upload"
#: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx #: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx
msgid "Upload a CSV file to create multiple documents from this template. Each row represents one document with its recipient details." msgid "Upload a CSV file to create multiple documents from this template. Each row represents one document with its recipient details."
msgstr "Upload a CSV file to create multiple documents from this template. Each row represents one document with its recipient details." msgstr "Upload a CSV file to create multiple documents from this template. Each row represents one document with its recipient details."
@@ -6110,7 +6193,7 @@ msgstr "Upload CSV"
msgid "Upload custom document" msgid "Upload custom document"
msgstr "Upload custom document" msgstr "Upload custom document"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-upload.tsx
msgid "Upload Signature" msgid "Upload Signature"
msgstr "Upload Signature" msgstr "Upload Signature"
@@ -6284,6 +6367,7 @@ msgstr "View document"
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/email/template-components/template-document-rejected.tsx #: packages/email/template-components/template-document-rejected.tsx
#: packages/email/template-components/template-document-invite.tsx #: packages/email/template-components/template-document-invite.tsx
msgid "View Document" msgid "View Document"
@@ -6650,6 +6734,11 @@ msgstr "Welcome to Documenso!"
msgid "Were you trying to edit this document instead?" msgid "Were you trying to edit this document instead?"
msgstr "Were you trying to edit this document instead?" msgstr "Were you trying to edit this document instead?"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/document-flow/add-signers.tsx
msgid "When enabled, signers can choose who should sign next in the sequence instead of following the predefined order."
msgstr "When enabled, signers can choose who should sign next in the sequence instead of following the predefined order."
#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
msgid "When you click continue, you will be prompted to add the first available authenticator on your system." msgid "When you click continue, you will be prompted to add the first available authenticator on your system."
msgstr "When you click continue, you will be prompted to add the first available authenticator on your system." msgstr "When you click continue, you will be prompted to add the first available authenticator on your system."

View File

@@ -419,6 +419,10 @@ msgstr "<0>{teamName}</0> ha solicitado usar tu dirección de correo electrónic
msgid "<0>Click to upload</0> or drag and drop" msgid "<0>Click to upload</0> or drag and drop"
msgstr "<0>Haga clic para subir</0> o arrastre y suelte" msgstr "<0>Haga clic para subir</0> o arrastre y suelte"
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "<0>Drawn</0> - A signature that is drawn using a mouse or stylus."
msgstr ""
#: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/template-flow/add-template-settings.tsx
msgid "<0>Email</0> - The recipient will be emailed the document to sign, approve, etc." msgid "<0>Email</0> - The recipient will be emailed the document to sign, approve, etc."
msgstr "<0>Correo electrónico</0> - Al destinatario se le enviará el documento para firmar, aprobar, etc." msgstr "<0>Correo electrónico</0> - Al destinatario se le enviará el documento para firmar, aprobar, etc."
@@ -465,6 +469,14 @@ msgstr "<0>Requerir clave de acceso</0> - El destinatario debe tener una cuenta
msgid "<0>Sender:</0> All" msgid "<0>Sender:</0> All"
msgstr "<0>Remitente:</0> Todos" msgstr "<0>Remitente:</0> Todos"
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "<0>Typed</0> - A signature that is typed using a keyboard."
msgstr ""
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "<0>Uploaded</0> - A signature that is uploaded from a file."
msgstr ""
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
msgid "<0>You are about to complete approving <1>\"{documentTitle}\"</1>.</0><2/> Are you sure?" msgid "<0>You are about to complete approving <1>\"{documentTitle}\"</1>.</0><2/> Are you sure?"
msgstr "<0>Está a punto de completar la aprobación de <1>\"{documentTitle}\"</1>.</0><2/> ¿Está seguro?" msgstr "<0>Está a punto de completar la aprobación de <1>\"{documentTitle}\"</1>.</0><2/> ¿Está seguro?"
@@ -898,6 +910,16 @@ msgstr "Todo el Tiempo"
msgid "Allow document recipients to reply directly to this email address" msgid "Allow document recipients to reply directly to this email address"
msgstr "Permitir que los destinatarios del documento respondan directamente a esta dirección de correo electrónico" msgstr "Permitir que los destinatarios del documento respondan directamente a esta dirección de correo electrónico"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/document-flow/add-signers.tsx
msgid "Allow signers to dictate next signer"
msgstr ""
#: packages/ui/primitives/template-flow/add-template-settings.tsx
#: packages/ui/primitives/document-flow/add-settings.tsx
msgid "Allowed Signature Types"
msgstr ""
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx #: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "Allows authenticating using biometrics, password managers, hardware keys, etc." msgid "Allows authenticating using biometrics, password managers, hardware keys, etc."
msgstr "Permite autenticarse usando biometría, administradores de contraseñas, claves de hardware, etc." msgstr "Permite autenticarse usando biometría, administradores de contraseñas, claves de hardware, etc."
@@ -1237,6 +1259,13 @@ msgstr ""
msgid "Assisting" msgid "Assisting"
msgstr "" msgstr ""
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
#: packages/ui/primitives/template-flow/add-template-settings.types.tsx
#: packages/ui/primitives/document-flow/add-settings.types.ts
#: packages/lib/types/document-meta.ts
msgid "At least one signature type must be enabled"
msgstr ""
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
msgid "Attempts sealing the document again, useful for after a code change has occurred to resolve an erroneous document." msgid "Attempts sealing the document again, useful for after a code change has occurred to resolve an erroneous document."
msgstr "Intenta sellar el documento de nuevo, útil después de que se haya producido un cambio de código para resolver un documento erróneo." msgstr "Intenta sellar el documento de nuevo, útil después de que se haya producido un cambio de código para resolver un documento erróneo."
@@ -1309,11 +1338,11 @@ msgstr "Antes de comenzar, por favor confirma tu dirección de correo electróni
msgid "Billing" msgid "Billing"
msgstr "Facturación" msgstr "Facturación"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Black" msgid "Black"
msgstr "Negro" msgstr "Negro"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Blue" msgid "Blue"
msgstr "Azul" msgstr "Azul"
@@ -1384,6 +1413,10 @@ msgstr "Al continuar utilizando el servicio de firma electrónica proporcionado
msgid "By proceeding with your electronic signature, you acknowledge and consent that it will be used to sign the given document and holds the same legal validity as a handwritten signature. By completing the electronic signing process, you affirm your understanding and acceptance of these conditions." msgid "By proceeding with your electronic signature, you acknowledge and consent that it will be used to sign the given document and holds the same legal validity as a handwritten signature. By completing the electronic signing process, you affirm your understanding and acceptance of these conditions."
msgstr "Al continuar con su firma electrónica, usted reconoce y consiente que se utilizará para firmar el documento dado y tiene la misma validez legal que una firma manuscrita. Al completar el proceso de firma electrónica, usted afirma su comprensión y aceptación de estas condiciones." msgstr "Al continuar con su firma electrónica, usted reconoce y consiente que se utilizará para firmar el documento dado y tiene la misma validez legal que una firma manuscrita. Al completar el proceso de firma electrónica, usted afirma su comprensión y aceptación de estas condiciones."
#: apps/remix/app/components/forms/signup.tsx
msgid "By proceeding, you agree to our <0>Terms of Service</0> and <1>Privacy Policy</1>."
msgstr ""
#: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx #: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx
msgid "By using the electronic signature feature, you are consenting to conduct transactions and receive disclosures electronically. You acknowledge that your electronic signature on documents is binding and that you accept the terms outlined in the documents you are signing." msgid "By using the electronic signature feature, you are consenting to conduct transactions and receive disclosures electronically. You acknowledge that your electronic signature on documents is binding and that you accept the terms outlined in the documents you are signing."
msgstr "Al utilizar la función de firma electrónica, usted está consintiendo realizar transacciones y recibir divulgaciones electrónicamente. Reconoce que su firma electrónica en los documentos es vinculante y que acepta los términos esbozados en los documentos que está firmando." msgstr "Al utilizar la función de firma electrónica, usted está consintiendo realizar transacciones y recibir divulgaciones electrónicamente. Reconoce que su firma electrónica en los documentos es vinculante y que acepta los términos esbozados en los documentos que está firmando."
@@ -1436,6 +1469,8 @@ msgstr ""
#: apps/remix/app/components/dialogs/document-move-dialog.tsx #: apps/remix/app/components/dialogs/document-move-dialog.tsx
#: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx #: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx #: packages/ui/primitives/document-flow/send-document-action-dialog.tsx
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx #: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
msgid "Cancel" msgid "Cancel"
@@ -1522,7 +1557,7 @@ msgstr "Limpiar archivo"
msgid "Clear filters" msgid "Clear filters"
msgstr "Limpiar filtros" msgstr "Limpiar filtros"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
msgid "Clear Signature" msgid "Clear Signature"
msgstr "Limpiar firma" msgstr "Limpiar firma"
@@ -1697,6 +1732,7 @@ msgstr "Contenido"
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/document-flow/document-flow-root.tsx #: packages/ui/primitives/document-flow/document-flow-root.tsx
msgid "Continue" msgid "Continue"
msgstr "Continuar" msgstr "Continuar"
@@ -1737,14 +1773,18 @@ msgstr "Controla la visibilidad predeterminada de un documento cargado."
msgid "Controls the formatting of the message that will be sent when inviting a recipient to sign a document. If a custom message has been provided while configuring the document, it will be used instead." msgid "Controls the formatting of the message that will be sent when inviting a recipient to sign a document. If a custom message has been provided while configuring the document, it will be used instead."
msgstr "Controla el formato del mensaje que se enviará al invitar a un destinatario a firmar un documento. Si se ha proporcionado un mensaje personalizado al configurar el documento, se usará en su lugar." msgstr "Controla el formato del mensaje que se enviará al invitar a un destinatario a firmar un documento. Si se ha proporcionado un mensaje personalizado al configurar el documento, se usará en su lugar."
#: apps/remix/app/components/forms/team-document-preferences-form.tsx #: packages/ui/primitives/document-flow/add-settings.tsx
msgid "Controls whether the recipients can sign the documents using a typed signature. Enable or disable the typed signature globally." msgid "Controls the language for the document, including the language to be used for email notifications, and the final certificate that is generated and attached to the document."
msgstr "Controla si los destinatarios pueden firmar los documentos utilizando una firma mecanografiada. Habilitar o deshabilitar la firma mecanografiada globalmente." msgstr ""
#: apps/remix/app/components/forms/team-document-preferences-form.tsx #: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "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." msgid "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."
msgstr "Controla si el certificado de firma se incluirá en el documento cuando se descargue. El certificado de firma aún puede descargarse por separado desde la página de registros." msgstr "Controla si el certificado de firma se incluirá en el documento cuando se descargue. El certificado de firma aún puede descargarse por separado desde la página de registros."
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "Controls which signatures are allowed to be used when signing a document."
msgstr ""
#: apps/remix/app/components/general/document/document-recipient-link-copy-dialog.tsx #: apps/remix/app/components/general/document/document-recipient-link-copy-dialog.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
msgid "Copied" msgid "Copied"
@@ -1975,6 +2015,10 @@ msgstr "Idioma predeterminado del documento"
msgid "Default Document Visibility" msgid "Default Document Visibility"
msgstr "Visibilidad predeterminada del documento" msgstr "Visibilidad predeterminada del documento"
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "Default Signature Settings"
msgstr ""
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx
msgid "delete" msgid "delete"
msgstr "eliminar" msgstr "eliminar"
@@ -2201,6 +2245,11 @@ msgstr "Documento \"{0}\" - Rechazado por {1}"
msgid "Document \"{0}\" - Rejection Confirmed" msgid "Document \"{0}\" - Rejection Confirmed"
msgstr "Documento \"{0}\" - Rechazo confirmado" msgstr "Documento \"{0}\" - Rechazo confirmado"
#. placeholder {0}: document.title
#: packages/lib/jobs/definitions/emails/send-document-cancelled-emails.handler.ts
msgid "Document \"{0}\" Cancelled"
msgstr ""
#: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/template-flow/add-template-settings.tsx
#: packages/ui/primitives/document-flow/add-settings.tsx #: packages/ui/primitives/document-flow/add-settings.tsx
#: packages/ui/components/document/document-global-auth-access-select.tsx #: packages/ui/components/document/document-global-auth-access-select.tsx
@@ -2349,6 +2398,10 @@ msgstr "Preferencias del documento actualizadas"
msgid "Document re-sent" msgid "Document re-sent"
msgstr "Documento reenviado" msgstr "Documento reenviado"
#: apps/remix/app/components/general/document/document-status.tsx
msgid "Document rejected"
msgstr ""
#: apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx #: apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx
#: apps/remix/app/components/embed/embed-document-rejected.tsx #: apps/remix/app/components/embed/embed-document-rejected.tsx
#: packages/email/template-components/template-document-rejected.tsx #: packages/email/template-components/template-document-rejected.tsx
@@ -2486,6 +2539,10 @@ msgstr "Documentos redactados"
msgid "Drag & drop your PDF here." msgid "Drag & drop your PDF here."
msgstr "Arrastre y suelte su PDF aquí." msgstr "Arrastre y suelte su PDF aquí."
#: packages/lib/constants/document.ts
msgid "Draw"
msgstr ""
#: packages/ui/primitives/template-flow/add-template-fields.tsx #: packages/ui/primitives/template-flow/add-template-fields.tsx
#: packages/ui/primitives/document-flow/add-fields.tsx #: packages/ui/primitives/document-flow/add-fields.tsx
msgid "Dropdown" msgid "Dropdown"
@@ -2543,6 +2600,7 @@ msgstr "Divulgación de Firma Electrónica"
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx
#: apps/remix/app/components/general/document-signing/document-signing-email-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-email-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
#: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx #: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx
#: apps/remix/app/components/forms/signin.tsx #: apps/remix/app/components/forms/signin.tsx
#: apps/remix/app/components/forms/profile.tsx #: apps/remix/app/components/forms/profile.tsx
@@ -2553,6 +2611,7 @@ msgstr "Divulgación de Firma Electrónica"
#: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-add-dialog.tsx #: apps/remix/app/components/dialogs/team-email-add-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-fields.tsx #: packages/ui/primitives/template-flow/add-template-fields.tsx
@@ -2646,15 +2705,6 @@ msgstr "Habilitar firma de enlace directo"
msgid "Enable signing order" msgid "Enable signing order"
msgstr "Habilitar orden de firma" msgstr "Habilitar orden de firma"
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "Enable Typed Signature"
msgstr "Habilitar firma mecanografiada"
#: packages/ui/primitives/template-flow/add-template-fields.tsx
#: packages/ui/primitives/document-flow/add-fields.tsx
msgid "Enable Typed Signatures"
msgstr "Habilitar firmas escritas"
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks._index.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks._index.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx
#: apps/remix/app/routes/_authenticated+/settings+/webhooks._index.tsx #: apps/remix/app/routes/_authenticated+/settings+/webhooks._index.tsx
@@ -2929,7 +2979,7 @@ msgstr "Ir al propietario"
msgid "Go to your <0>public profile settings</0> to add documents." msgid "Go to your <0>public profile settings</0> to add documents."
msgstr "Ve a tu <0>configuración de perfil público</0> para agregar documentos." msgstr "Ve a tu <0>configuración de perfil público</0> para agregar documentos."
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Green" msgid "Green"
msgstr "Verde" msgstr "Verde"
@@ -3483,10 +3533,12 @@ msgstr "Mis plantillas"
#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx
#: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/general/claim-account.tsx
#: apps/remix/app/components/general/document-signing/document-signing-name-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-name-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
#: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx
#: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-add-dialog.tsx #: apps/remix/app/components/dialogs/team-email-add-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-fields.tsx #: packages/ui/primitives/template-flow/add-template-fields.tsx
@@ -3537,6 +3589,7 @@ msgstr "Nueva plantilla"
#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/embed/embed-document-signing-page.tsx #: apps/remix/app/components/embed/embed-document-signing-page.tsx
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx #: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
#: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx
msgid "Next" msgid "Next"
msgstr "Siguiente" msgstr "Siguiente"
@@ -3954,6 +4007,7 @@ msgstr "Por favor, ingresa un nombre significativo para tu token. Esto te ayudar
#: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/general/claim-account.tsx
#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/forms/profile.tsx
msgid "Please enter a valid name." msgid "Please enter a valid name."
msgstr "Por favor, introduce un nombre válido." msgstr "Por favor, introduce un nombre válido."
@@ -3989,10 +4043,6 @@ msgstr "Por favor, ten en cuenta que esta acción es irreversible. Una vez confi
msgid "Please note that you will lose access to all documents associated with this team & all the members will be removed and notified" msgid "Please note that you will lose access to all documents associated with this team & all the members will be removed and notified"
msgstr "Por favor, ten en cuenta que perderás acceso a todos los documentos asociados con este equipo y todos los miembros serán eliminados y notificados" msgstr "Por favor, ten en cuenta que perderás acceso a todos los documentos asociados con este equipo y todos los miembros serán eliminados y notificados"
#: apps/remix/app/components/general/document-signing/document-signing-reject-dialog.tsx
msgid "Please provide a reason"
msgstr "Please provide a reason"
#: apps/remix/app/components/forms/2fa/disable-authenticator-app-dialog.tsx #: apps/remix/app/components/forms/2fa/disable-authenticator-app-dialog.tsx
msgid "Please provide a token from the authenticator, or a backup code. If you do not have a backup code available, please contact support." msgid "Please provide a token from the authenticator, or a backup code. If you do not have a backup code available, please contact support."
msgstr "Por favor, proporciona un token del autenticador o un código de respaldo. Si no tienes un código de respaldo disponible, contacta al soporte." msgstr "Por favor, proporciona un token del autenticador o un código de respaldo. Si no tienes un código de respaldo disponible, contacta al soporte."
@@ -4063,6 +4113,10 @@ msgstr "Privado"
msgid "Private templates can only be modified and viewed by you." msgid "Private templates can only be modified and viewed by you."
msgstr "Las plantillas privadas solo pueden ser modificadas y vistas por ti." msgstr "Las plantillas privadas solo pueden ser modificadas y vistas por ti."
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
msgid "Proceed"
msgstr ""
#: apps/remix/app/routes/_authenticated+/settings+/profile.tsx #: apps/remix/app/routes/_authenticated+/settings+/profile.tsx
#: apps/remix/app/components/general/settings-nav-mobile.tsx #: apps/remix/app/components/general/settings-nav-mobile.tsx
#: apps/remix/app/components/general/settings-nav-desktop.tsx #: apps/remix/app/components/general/settings-nav-desktop.tsx
@@ -4140,6 +4194,10 @@ msgstr "Listo"
msgid "Reason" msgid "Reason"
msgstr "Razón" msgstr "Razón"
#: packages/email/template-components/template-document-cancel.tsx
msgid "Reason for cancellation: {cancellationReason}"
msgstr ""
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx #: apps/remix/app/components/general/document/document-page-view-recipients.tsx
msgid "Reason for rejection: " msgid "Reason for rejection: "
msgstr "" msgstr ""
@@ -4222,7 +4280,7 @@ msgstr "Código de recuperación copiado"
msgid "Recovery codes" msgid "Recovery codes"
msgstr "Códigos de recuperación" msgstr "Códigos de recuperación"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Red" msgid "Red"
msgstr "Rojo" msgstr "Rojo"
@@ -4243,8 +4301,11 @@ msgstr "Registro exitoso"
msgid "Reject Document" msgid "Reject Document"
msgstr "Rechazar Documento" msgstr "Rechazar Documento"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx #: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx
#: apps/remix/app/components/general/document/document-status.tsx
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx #: apps/remix/app/components/general/document/document-page-view-recipients.tsx
#: packages/lib/constants/document.ts
msgid "Rejected" msgid "Rejected"
msgstr "Rejected" msgstr "Rejected"
@@ -4429,8 +4490,6 @@ msgstr "Filas por página"
#: apps/remix/app/components/general/document-signing/document-signing-text-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-text-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-number-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-number-field.tsx
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
#: apps/remix/app/components/forms/team-branding-preferences-form.tsx
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx #: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
msgid "Save" msgid "Save"
@@ -4740,7 +4799,6 @@ msgstr "Regístrate con OIDC"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx #: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx
#: apps/remix/app/components/forms/profile.tsx #: apps/remix/app/components/forms/profile.tsx
@@ -4757,12 +4815,17 @@ msgstr "Firma"
msgid "Signature ID" msgid "Signature ID"
msgstr "ID de Firma" msgstr "ID de Firma"
#: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx #: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx msgid "Signature is too small"
#: apps/remix/app/components/embed/embed-document-signing-page.tsx msgstr ""
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
msgid "Signature is too small. Please provide a more complete signature." #: apps/remix/app/components/forms/profile.tsx
msgstr "La firma es demasiado pequeña. Proporcione una firma más completa." msgid "Signature Pad cannot be empty."
msgstr ""
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "Signature types"
msgstr ""
#: apps/remix/app/routes/_authenticated+/admin+/stats.tsx #: apps/remix/app/routes/_authenticated+/admin+/stats.tsx
msgid "Signatures Collected" msgid "Signatures Collected"
@@ -4987,6 +5050,7 @@ msgid "Subject <0>(Optional)</0>"
msgstr "Asunto <0>(Opcional)</0>" msgstr "Asunto <0>(Opcional)</0>"
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
msgid "Submitting..." msgid "Submitting..."
msgstr "" msgstr ""
@@ -5501,6 +5565,10 @@ msgstr "El token que has utilizado para restablecer tu contraseña ha expirado o
msgid "The two-factor authentication code provided is incorrect" msgid "The two-factor authentication code provided is incorrect"
msgstr "" msgstr ""
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "The types of signatures that recipients are allowed to use when signing the document."
msgstr ""
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx
#: apps/remix/app/routes/_authenticated+/settings+/webhooks.$id.tsx #: apps/remix/app/routes/_authenticated+/settings+/webhooks.$id.tsx
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx #: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
@@ -5582,6 +5650,10 @@ msgstr "Este documento ha sido cancelado por el propietario y ya no está dispon
msgid "This document has been cancelled by the owner." msgid "This document has been cancelled by the owner."
msgstr "Este documento ha sido cancelado por el propietario." msgstr "Este documento ha sido cancelado por el propietario."
#: apps/remix/app/routes/_authenticated+/documents.$id._index.tsx
msgid "This document has been rejected by a recipient"
msgstr ""
#: apps/remix/app/routes/_authenticated+/documents.$id._index.tsx #: apps/remix/app/routes/_authenticated+/documents.$id._index.tsx
msgid "This document has been signed by all recipients" msgid "This document has been signed by all recipients"
msgstr "Este documento ha sido firmado por todos los destinatarios" msgstr "Este documento ha sido firmado por todos los destinatarios"
@@ -5730,6 +5802,10 @@ msgstr "Zona horaria"
msgid "Title" msgid "Title"
msgstr "Título" msgstr "Título"
#: packages/ui/primitives/document-flow/add-settings.types.ts
msgid "Title cannot be empty"
msgstr ""
#: apps/remix/app/routes/_unauthenticated+/team.invite.$token.tsx #: apps/remix/app/routes/_unauthenticated+/team.invite.$token.tsx
msgid "To accept this invitation you must create an account." msgid "To accept this invitation you must create an account."
msgstr "Para aceptar esta invitación debes crear una cuenta." msgstr "Para aceptar esta invitación debes crear una cuenta."
@@ -5897,6 +5973,7 @@ msgstr "Re-autenticación de Doble Factor"
#: apps/remix/app/components/tables/templates-table.tsx #: apps/remix/app/components/tables/templates-table.tsx
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: packages/lib/constants/document.ts
msgid "Type" msgid "Type"
msgstr "Tipo" msgstr "Tipo"
@@ -6004,6 +6081,7 @@ msgstr "Incompleto"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.billing.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.billing.tsx
msgid "Unknown" msgid "Unknown"
msgstr "Desconocido" msgstr "Desconocido"
@@ -6014,11 +6092,14 @@ msgstr "No pagado"
#: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
#: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx #: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
#: apps/remix/app/components/forms/team-branding-preferences-form.tsx
#: apps/remix/app/components/forms/public-profile-form.tsx #: apps/remix/app/components/forms/public-profile-form.tsx
#: apps/remix/app/components/dialogs/team-member-update-dialog.tsx #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
msgid "Update" msgid "Update"
msgstr "Actualizar" msgstr "Actualizar"
@@ -6039,6 +6120,8 @@ msgid "Update profile"
msgstr "Actualizar perfil" msgstr "Actualizar perfil"
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
msgid "Update Recipient" msgid "Update Recipient"
msgstr "Actualizar destinatario" msgstr "Actualizar destinatario"
@@ -6077,10 +6160,6 @@ msgstr "Actualizar webhook"
msgid "Updating password..." msgid "Updating password..."
msgstr "Actualizando contraseña..." msgstr "Actualizando contraseña..."
#: apps/remix/app/components/forms/profile.tsx
msgid "Updating profile..."
msgstr "Actualizando perfil..."
#: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx #: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx
msgid "Updating Your Information" msgid "Updating Your Information"
msgstr "Actualizando Su Información" msgstr "Actualizando Su Información"
@@ -6089,6 +6168,10 @@ msgstr "Actualizando Su Información"
msgid "Upgrade" msgid "Upgrade"
msgstr "Actualizar" msgstr "Actualizar"
#: packages/lib/constants/document.ts
msgid "Upload"
msgstr ""
#: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx #: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx
msgid "Upload a CSV file to create multiple documents from this template. Each row represents one document with its recipient details." msgid "Upload a CSV file to create multiple documents from this template. Each row represents one document with its recipient details."
msgstr "Sube un archivo CSV para crear múltiples documentos a partir de esta plantilla. Cada fila representa un documento con los detalles del destinatario." msgstr "Sube un archivo CSV para crear múltiples documentos a partir de esta plantilla. Cada fila representa un documento con los detalles del destinatario."
@@ -6113,7 +6196,7 @@ msgstr "Subir CSV"
msgid "Upload custom document" msgid "Upload custom document"
msgstr "Subir documento personalizado" msgstr "Subir documento personalizado"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-upload.tsx
msgid "Upload Signature" msgid "Upload Signature"
msgstr "Subir firma" msgstr "Subir firma"
@@ -6287,6 +6370,7 @@ msgstr "Ver documento"
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/email/template-components/template-document-rejected.tsx #: packages/email/template-components/template-document-rejected.tsx
#: packages/email/template-components/template-document-invite.tsx #: packages/email/template-components/template-document-invite.tsx
msgid "View Document" msgid "View Document"
@@ -6653,6 +6737,11 @@ msgstr "¡Bienvenido a Documenso!"
msgid "Were you trying to edit this document instead?" msgid "Were you trying to edit this document instead?"
msgstr "¿Estabas intentando editar este documento en su lugar?" msgstr "¿Estabas intentando editar este documento en su lugar?"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/document-flow/add-signers.tsx
msgid "When enabled, signers can choose who should sign next in the sequence instead of following the predefined order."
msgstr ""
#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
msgid "When you click continue, you will be prompted to add the first available authenticator on your system." msgid "When you click continue, you will be prompted to add the first available authenticator on your system."
msgstr "Cuando haces clic en continuar, se te pedirá que añadas el primer autenticador disponible en tu sistema." msgstr "Cuando haces clic en continuar, se te pedirá que añadas el primer autenticador disponible en tu sistema."

View File

@@ -419,6 +419,10 @@ msgstr "<0>{teamName}</0> a demandé à utiliser votre adresse e-mail pour leur
msgid "<0>Click to upload</0> or drag and drop" msgid "<0>Click to upload</0> or drag and drop"
msgstr "<0>Cliquez pour importer</0> ou faites glisser et déposez" msgstr "<0>Cliquez pour importer</0> ou faites glisser et déposez"
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "<0>Drawn</0> - A signature that is drawn using a mouse or stylus."
msgstr ""
#: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/template-flow/add-template-settings.tsx
msgid "<0>Email</0> - The recipient will be emailed the document to sign, approve, etc." msgid "<0>Email</0> - The recipient will be emailed the document to sign, approve, etc."
msgstr "<0>Email</0> - Le destinataire recevra le document par e-mail pour signer, approuver, etc." msgstr "<0>Email</0> - Le destinataire recevra le document par e-mail pour signer, approuver, etc."
@@ -465,6 +469,14 @@ msgstr "<0>Clé d'accès requise</0> - Le destinataire doit avoir un compte et u
msgid "<0>Sender:</0> All" msgid "<0>Sender:</0> All"
msgstr "<0>Expéditeur :</0> Tous" msgstr "<0>Expéditeur :</0> Tous"
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "<0>Typed</0> - A signature that is typed using a keyboard."
msgstr ""
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "<0>Uploaded</0> - A signature that is uploaded from a file."
msgstr ""
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
msgid "<0>You are about to complete approving <1>\"{documentTitle}\"</1>.</0><2/> Are you sure?" msgid "<0>You are about to complete approving <1>\"{documentTitle}\"</1>.</0><2/> Are you sure?"
msgstr "<0>Vous êtes sur le point de terminer l'approbation de <1>\"{documentTitle}\"</1>.</0><2/> Êtes-vous sûr ?" msgstr "<0>Vous êtes sur le point de terminer l'approbation de <1>\"{documentTitle}\"</1>.</0><2/> Êtes-vous sûr ?"
@@ -898,6 +910,16 @@ msgstr "Depuis toujours"
msgid "Allow document recipients to reply directly to this email address" msgid "Allow document recipients to reply directly to this email address"
msgstr "Autoriser les destinataires du document à répondre directement à cette adresse e-mail" msgstr "Autoriser les destinataires du document à répondre directement à cette adresse e-mail"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/document-flow/add-signers.tsx
msgid "Allow signers to dictate next signer"
msgstr ""
#: packages/ui/primitives/template-flow/add-template-settings.tsx
#: packages/ui/primitives/document-flow/add-settings.tsx
msgid "Allowed Signature Types"
msgstr ""
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx #: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "Allows authenticating using biometrics, password managers, hardware keys, etc." msgid "Allows authenticating using biometrics, password managers, hardware keys, etc."
msgstr "Permet d'authentifier en utilisant des biométries, des gestionnaires de mots de passe, des clés matérielles, etc." msgstr "Permet d'authentifier en utilisant des biométries, des gestionnaires de mots de passe, des clés matérielles, etc."
@@ -1237,6 +1259,13 @@ msgstr ""
msgid "Assisting" msgid "Assisting"
msgstr "" msgstr ""
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
#: packages/ui/primitives/template-flow/add-template-settings.types.tsx
#: packages/ui/primitives/document-flow/add-settings.types.ts
#: packages/lib/types/document-meta.ts
msgid "At least one signature type must be enabled"
msgstr ""
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
msgid "Attempts sealing the document again, useful for after a code change has occurred to resolve an erroneous document." msgid "Attempts sealing the document again, useful for after a code change has occurred to resolve an erroneous document."
msgstr "Essaye de sceller le document à nouveau, utile après qu'un changement de code ait eu lieu pour résoudre un document erroné." msgstr "Essaye de sceller le document à nouveau, utile après qu'un changement de code ait eu lieu pour résoudre un document erroné."
@@ -1309,11 +1338,11 @@ msgstr "Avant de commencer, veuillez confirmer votre adresse e-mail en cliquant
msgid "Billing" msgid "Billing"
msgstr "Facturation" msgstr "Facturation"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Black" msgid "Black"
msgstr "Noir" msgstr "Noir"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Blue" msgid "Blue"
msgstr "Bleu" msgstr "Bleu"
@@ -1384,6 +1413,10 @@ msgstr "En procédant à l'utilisation du service de signature électronique fou
msgid "By proceeding with your electronic signature, you acknowledge and consent that it will be used to sign the given document and holds the same legal validity as a handwritten signature. By completing the electronic signing process, you affirm your understanding and acceptance of these conditions." msgid "By proceeding with your electronic signature, you acknowledge and consent that it will be used to sign the given document and holds the same legal validity as a handwritten signature. By completing the electronic signing process, you affirm your understanding and acceptance of these conditions."
msgstr "En procédant avec votre signature électronique, vous reconnaissez et consentez à ce qu'elle soit utilisée pour signer le document donné et a la même validité légale qu'une signature manuscrite. En complétant le processus de signature électronique, vous affirmez votre compréhension et votre acceptation de ces conditions." msgstr "En procédant avec votre signature électronique, vous reconnaissez et consentez à ce qu'elle soit utilisée pour signer le document donné et a la même validité légale qu'une signature manuscrite. En complétant le processus de signature électronique, vous affirmez votre compréhension et votre acceptation de ces conditions."
#: apps/remix/app/components/forms/signup.tsx
msgid "By proceeding, you agree to our <0>Terms of Service</0> and <1>Privacy Policy</1>."
msgstr ""
#: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx #: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx
msgid "By using the electronic signature feature, you are consenting to conduct transactions and receive disclosures electronically. You acknowledge that your electronic signature on documents is binding and that you accept the terms outlined in the documents you are signing." msgid "By using the electronic signature feature, you are consenting to conduct transactions and receive disclosures electronically. You acknowledge that your electronic signature on documents is binding and that you accept the terms outlined in the documents you are signing."
msgstr "En utilisant la fonctionnalité de signature électronique, vous consentez à effectuer des transactions et à recevoir des divulgations électroniquement. Vous reconnaissez que votre signature électronique sur les documents est contraignante et que vous acceptez les termes énoncés dans les documents que vous signez." msgstr "En utilisant la fonctionnalité de signature électronique, vous consentez à effectuer des transactions et à recevoir des divulgations électroniquement. Vous reconnaissez que votre signature électronique sur les documents est contraignante et que vous acceptez les termes énoncés dans les documents que vous signez."
@@ -1436,6 +1469,8 @@ msgstr ""
#: apps/remix/app/components/dialogs/document-move-dialog.tsx #: apps/remix/app/components/dialogs/document-move-dialog.tsx
#: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx #: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx #: packages/ui/primitives/document-flow/send-document-action-dialog.tsx
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx #: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
msgid "Cancel" msgid "Cancel"
@@ -1522,7 +1557,7 @@ msgstr "Effacer le fichier"
msgid "Clear filters" msgid "Clear filters"
msgstr "Effacer les filtres" msgstr "Effacer les filtres"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
msgid "Clear Signature" msgid "Clear Signature"
msgstr "Effacer la signature" msgstr "Effacer la signature"
@@ -1697,6 +1732,7 @@ msgstr "Contenu"
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/document-flow/document-flow-root.tsx #: packages/ui/primitives/document-flow/document-flow-root.tsx
msgid "Continue" msgid "Continue"
msgstr "Continuer" msgstr "Continuer"
@@ -1737,14 +1773,18 @@ msgstr "Contrôle la visibilité par défaut d'un document importé."
msgid "Controls the formatting of the message that will be sent when inviting a recipient to sign a document. If a custom message has been provided while configuring the document, it will be used instead." msgid "Controls the formatting of the message that will be sent when inviting a recipient to sign a document. If a custom message has been provided while configuring the document, it will be used instead."
msgstr "Contrôle le formatage du message qui sera envoyé lors de l'invitation d'un destinataire à signer un document. Si un message personnalisé a été fourni lors de la configuration du document, il sera utilisé à la place." msgstr "Contrôle le formatage du message qui sera envoyé lors de l'invitation d'un destinataire à signer un document. Si un message personnalisé a été fourni lors de la configuration du document, il sera utilisé à la place."
#: apps/remix/app/components/forms/team-document-preferences-form.tsx #: packages/ui/primitives/document-flow/add-settings.tsx
msgid "Controls whether the recipients can sign the documents using a typed signature. Enable or disable the typed signature globally." msgid "Controls the language for the document, including the language to be used for email notifications, and the final certificate that is generated and attached to the document."
msgstr "Contrôle si les destinataires peuvent signer les documents à l'aide d'une signature dactylographiée. Active ou désactive globalement la signature dactylographiée." msgstr ""
#: apps/remix/app/components/forms/team-document-preferences-form.tsx #: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "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." msgid "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."
msgstr "Contrôle si le certificat de signature sera inclus dans le document lorsqu'il sera téléchargé. Le certificat de signature peut toujours être téléchargé séparément à partir de la page d'historique'." msgstr "Contrôle si le certificat de signature sera inclus dans le document lorsqu'il sera téléchargé. Le certificat de signature peut toujours être téléchargé séparément à partir de la page d'historique'."
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "Controls which signatures are allowed to be used when signing a document."
msgstr ""
#: apps/remix/app/components/general/document/document-recipient-link-copy-dialog.tsx #: apps/remix/app/components/general/document/document-recipient-link-copy-dialog.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
msgid "Copied" msgid "Copied"
@@ -1975,6 +2015,10 @@ msgstr "Langue par défaut du document"
msgid "Default Document Visibility" msgid "Default Document Visibility"
msgstr "Visibilité par défaut du document" msgstr "Visibilité par défaut du document"
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "Default Signature Settings"
msgstr ""
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx
msgid "delete" msgid "delete"
msgstr "supprimer" msgstr "supprimer"
@@ -2201,6 +2245,11 @@ msgstr "Document \"{0}\" - Rejeté par {1}"
msgid "Document \"{0}\" - Rejection Confirmed" msgid "Document \"{0}\" - Rejection Confirmed"
msgstr "Document \"{0}\" - Rejet Confirmé" msgstr "Document \"{0}\" - Rejet Confirmé"
#. placeholder {0}: document.title
#: packages/lib/jobs/definitions/emails/send-document-cancelled-emails.handler.ts
msgid "Document \"{0}\" Cancelled"
msgstr ""
#: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/template-flow/add-template-settings.tsx
#: packages/ui/primitives/document-flow/add-settings.tsx #: packages/ui/primitives/document-flow/add-settings.tsx
#: packages/ui/components/document/document-global-auth-access-select.tsx #: packages/ui/components/document/document-global-auth-access-select.tsx
@@ -2349,6 +2398,10 @@ msgstr "Préférences de document mises à jour"
msgid "Document re-sent" msgid "Document re-sent"
msgstr "Document renvoyé" msgstr "Document renvoyé"
#: apps/remix/app/components/general/document/document-status.tsx
msgid "Document rejected"
msgstr ""
#: apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx #: apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx
#: apps/remix/app/components/embed/embed-document-rejected.tsx #: apps/remix/app/components/embed/embed-document-rejected.tsx
#: packages/email/template-components/template-document-rejected.tsx #: packages/email/template-components/template-document-rejected.tsx
@@ -2486,6 +2539,10 @@ msgstr "Documents brouillon"
msgid "Drag & drop your PDF here." msgid "Drag & drop your PDF here."
msgstr "Faites glisser et déposez votre PDF ici." msgstr "Faites glisser et déposez votre PDF ici."
#: packages/lib/constants/document.ts
msgid "Draw"
msgstr ""
#: packages/ui/primitives/template-flow/add-template-fields.tsx #: packages/ui/primitives/template-flow/add-template-fields.tsx
#: packages/ui/primitives/document-flow/add-fields.tsx #: packages/ui/primitives/document-flow/add-fields.tsx
msgid "Dropdown" msgid "Dropdown"
@@ -2543,6 +2600,7 @@ msgstr "Divulgation de signature électronique"
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx
#: apps/remix/app/components/general/document-signing/document-signing-email-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-email-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
#: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx #: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx
#: apps/remix/app/components/forms/signin.tsx #: apps/remix/app/components/forms/signin.tsx
#: apps/remix/app/components/forms/profile.tsx #: apps/remix/app/components/forms/profile.tsx
@@ -2553,6 +2611,7 @@ msgstr "Divulgation de signature électronique"
#: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-add-dialog.tsx #: apps/remix/app/components/dialogs/team-email-add-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-fields.tsx #: packages/ui/primitives/template-flow/add-template-fields.tsx
@@ -2646,15 +2705,6 @@ msgstr "Activer la signature de lien direct"
msgid "Enable signing order" msgid "Enable signing order"
msgstr "Activer l'ordre de signature" msgstr "Activer l'ordre de signature"
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "Enable Typed Signature"
msgstr "Activer la signature dactylographiée"
#: packages/ui/primitives/template-flow/add-template-fields.tsx
#: packages/ui/primitives/document-flow/add-fields.tsx
msgid "Enable Typed Signatures"
msgstr "Activer les signatures tapées"
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks._index.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks._index.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx
#: apps/remix/app/routes/_authenticated+/settings+/webhooks._index.tsx #: apps/remix/app/routes/_authenticated+/settings+/webhooks._index.tsx
@@ -2929,7 +2979,7 @@ msgstr "Aller au propriétaire"
msgid "Go to your <0>public profile settings</0> to add documents." msgid "Go to your <0>public profile settings</0> to add documents."
msgstr "Allez à vos <0>paramètres de profil public</0> pour ajouter des documents." msgstr "Allez à vos <0>paramètres de profil public</0> pour ajouter des documents."
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Green" msgid "Green"
msgstr "Vert" msgstr "Vert"
@@ -3483,10 +3533,12 @@ msgstr "Mes modèles"
#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx
#: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/general/claim-account.tsx
#: apps/remix/app/components/general/document-signing/document-signing-name-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-name-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
#: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx
#: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-add-dialog.tsx #: apps/remix/app/components/dialogs/team-email-add-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-fields.tsx #: packages/ui/primitives/template-flow/add-template-fields.tsx
@@ -3537,6 +3589,7 @@ msgstr "Nouveau modèle"
#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/embed/embed-document-signing-page.tsx #: apps/remix/app/components/embed/embed-document-signing-page.tsx
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx #: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
#: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx
msgid "Next" msgid "Next"
msgstr "Suivant" msgstr "Suivant"
@@ -3954,6 +4007,7 @@ msgstr "Veuillez entrer un nom significatif pour votre token. Cela vous aidera
#: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/general/claim-account.tsx
#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/forms/profile.tsx
msgid "Please enter a valid name." msgid "Please enter a valid name."
msgstr "Veuiillez entrer un nom valide." msgstr "Veuiillez entrer un nom valide."
@@ -3989,10 +4043,6 @@ msgstr "Veuillez noter que cette action est irréversible. Une fois confirmée,
msgid "Please note that you will lose access to all documents associated with this team & all the members will be removed and notified" msgid "Please note that you will lose access to all documents associated with this team & all the members will be removed and notified"
msgstr "Veuillez noter que vous perdrez l'accès à tous les documents associés à cette équipe et que tous les membres seront supprimés et notifiés" msgstr "Veuillez noter que vous perdrez l'accès à tous les documents associés à cette équipe et que tous les membres seront supprimés et notifiés"
#: apps/remix/app/components/general/document-signing/document-signing-reject-dialog.tsx
msgid "Please provide a reason"
msgstr "Veuillez fournir une raison"
#: apps/remix/app/components/forms/2fa/disable-authenticator-app-dialog.tsx #: apps/remix/app/components/forms/2fa/disable-authenticator-app-dialog.tsx
msgid "Please provide a token from the authenticator, or a backup code. If you do not have a backup code available, please contact support." msgid "Please provide a token from the authenticator, or a backup code. If you do not have a backup code available, please contact support."
msgstr "Veuillez fournir un token de l'authentificateur, ou un code de secours. Si vous n'avez pas de code de secours disponible, veuillez contacter le support." msgstr "Veuillez fournir un token de l'authentificateur, ou un code de secours. Si vous n'avez pas de code de secours disponible, veuillez contacter le support."
@@ -4063,6 +4113,10 @@ msgstr "Privé"
msgid "Private templates can only be modified and viewed by you." msgid "Private templates can only be modified and viewed by you."
msgstr "Les modèles privés ne peuvent être modifiés et consultés que par vous." msgstr "Les modèles privés ne peuvent être modifiés et consultés que par vous."
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
msgid "Proceed"
msgstr ""
#: apps/remix/app/routes/_authenticated+/settings+/profile.tsx #: apps/remix/app/routes/_authenticated+/settings+/profile.tsx
#: apps/remix/app/components/general/settings-nav-mobile.tsx #: apps/remix/app/components/general/settings-nav-mobile.tsx
#: apps/remix/app/components/general/settings-nav-desktop.tsx #: apps/remix/app/components/general/settings-nav-desktop.tsx
@@ -4140,6 +4194,10 @@ msgstr "Prêt"
msgid "Reason" msgid "Reason"
msgstr "Raison" msgstr "Raison"
#: packages/email/template-components/template-document-cancel.tsx
msgid "Reason for cancellation: {cancellationReason}"
msgstr ""
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx #: apps/remix/app/components/general/document/document-page-view-recipients.tsx
msgid "Reason for rejection: " msgid "Reason for rejection: "
msgstr "" msgstr ""
@@ -4222,7 +4280,7 @@ msgstr "Code de récupération copié"
msgid "Recovery codes" msgid "Recovery codes"
msgstr "Codes de récupération" msgstr "Codes de récupération"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Red" msgid "Red"
msgstr "Rouge" msgstr "Rouge"
@@ -4243,8 +4301,11 @@ msgstr "Inscription réussie"
msgid "Reject Document" msgid "Reject Document"
msgstr "Rejeter le Document" msgstr "Rejeter le Document"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx #: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx
#: apps/remix/app/components/general/document/document-status.tsx
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx #: apps/remix/app/components/general/document/document-page-view-recipients.tsx
#: packages/lib/constants/document.ts
msgid "Rejected" msgid "Rejected"
msgstr "Rejeté" msgstr "Rejeté"
@@ -4429,8 +4490,6 @@ msgstr "Lignes par page"
#: apps/remix/app/components/general/document-signing/document-signing-text-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-text-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-number-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-number-field.tsx
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
#: apps/remix/app/components/forms/team-branding-preferences-form.tsx
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx #: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
msgid "Save" msgid "Save"
@@ -4740,7 +4799,6 @@ msgstr "S'inscrire avec OIDC"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx #: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx
#: apps/remix/app/components/forms/profile.tsx #: apps/remix/app/components/forms/profile.tsx
@@ -4757,12 +4815,17 @@ msgstr "Signature"
msgid "Signature ID" msgid "Signature ID"
msgstr "ID de signature" msgstr "ID de signature"
#: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx #: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx msgid "Signature is too small"
#: apps/remix/app/components/embed/embed-document-signing-page.tsx msgstr ""
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
msgid "Signature is too small. Please provide a more complete signature." #: apps/remix/app/components/forms/profile.tsx
msgstr "La signature est trop petite. Veuillez fournir une signature plus grande." msgid "Signature Pad cannot be empty."
msgstr ""
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "Signature types"
msgstr ""
#: apps/remix/app/routes/_authenticated+/admin+/stats.tsx #: apps/remix/app/routes/_authenticated+/admin+/stats.tsx
msgid "Signatures Collected" msgid "Signatures Collected"
@@ -4987,6 +5050,7 @@ msgid "Subject <0>(Optional)</0>"
msgstr "Objet <0>(Optionnel)</0>" msgstr "Objet <0>(Optionnel)</0>"
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
msgid "Submitting..." msgid "Submitting..."
msgstr "" msgstr ""
@@ -5501,6 +5565,10 @@ msgstr "Le token que vous avez utilisé pour réinitialiser votre mot de passe a
msgid "The two-factor authentication code provided is incorrect" msgid "The two-factor authentication code provided is incorrect"
msgstr "" msgstr ""
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "The types of signatures that recipients are allowed to use when signing the document."
msgstr ""
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx
#: apps/remix/app/routes/_authenticated+/settings+/webhooks.$id.tsx #: apps/remix/app/routes/_authenticated+/settings+/webhooks.$id.tsx
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx #: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
@@ -5582,6 +5650,10 @@ msgstr "Ce document a été annulé par le propriétaire et n'est plus disponibl
msgid "This document has been cancelled by the owner." msgid "This document has been cancelled by the owner."
msgstr "Ce document a été annulé par le propriétaire." msgstr "Ce document a été annulé par le propriétaire."
#: apps/remix/app/routes/_authenticated+/documents.$id._index.tsx
msgid "This document has been rejected by a recipient"
msgstr ""
#: apps/remix/app/routes/_authenticated+/documents.$id._index.tsx #: apps/remix/app/routes/_authenticated+/documents.$id._index.tsx
msgid "This document has been signed by all recipients" msgid "This document has been signed by all recipients"
msgstr "Ce document a été signé par tous les destinataires" msgstr "Ce document a été signé par tous les destinataires"
@@ -5730,6 +5802,10 @@ msgstr "Fuseau horaire"
msgid "Title" msgid "Title"
msgstr "Titre" msgstr "Titre"
#: packages/ui/primitives/document-flow/add-settings.types.ts
msgid "Title cannot be empty"
msgstr ""
#: apps/remix/app/routes/_unauthenticated+/team.invite.$token.tsx #: apps/remix/app/routes/_unauthenticated+/team.invite.$token.tsx
msgid "To accept this invitation you must create an account." msgid "To accept this invitation you must create an account."
msgstr "Pour accepter cette invitation, vous devez créer un compte." msgstr "Pour accepter cette invitation, vous devez créer un compte."
@@ -5897,6 +5973,7 @@ msgstr "Ré-authentification à deux facteurs"
#: apps/remix/app/components/tables/templates-table.tsx #: apps/remix/app/components/tables/templates-table.tsx
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: packages/lib/constants/document.ts
msgid "Type" msgid "Type"
msgstr "Type" msgstr "Type"
@@ -6004,6 +6081,7 @@ msgstr "Non complet"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.billing.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.billing.tsx
msgid "Unknown" msgid "Unknown"
msgstr "Inconnu" msgstr "Inconnu"
@@ -6014,11 +6092,14 @@ msgstr "Non payé"
#: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
#: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx #: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
#: apps/remix/app/components/forms/team-branding-preferences-form.tsx
#: apps/remix/app/components/forms/public-profile-form.tsx #: apps/remix/app/components/forms/public-profile-form.tsx
#: apps/remix/app/components/dialogs/team-member-update-dialog.tsx #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
msgid "Update" msgid "Update"
msgstr "Mettre à jour" msgstr "Mettre à jour"
@@ -6039,6 +6120,8 @@ msgid "Update profile"
msgstr "Mettre à jour le profil" msgstr "Mettre à jour le profil"
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
msgid "Update Recipient" msgid "Update Recipient"
msgstr "Mettre à jour le destinataire" msgstr "Mettre à jour le destinataire"
@@ -6077,10 +6160,6 @@ msgstr "Mettre à jour le webhook"
msgid "Updating password..." msgid "Updating password..."
msgstr "Mise à jour du mot de passe..." msgstr "Mise à jour du mot de passe..."
#: apps/remix/app/components/forms/profile.tsx
msgid "Updating profile..."
msgstr "Mise à jour du profil..."
#: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx #: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx
msgid "Updating Your Information" msgid "Updating Your Information"
msgstr "Mise à jour de vos informations" msgstr "Mise à jour de vos informations"
@@ -6089,6 +6168,10 @@ msgstr "Mise à jour de vos informations"
msgid "Upgrade" msgid "Upgrade"
msgstr "Améliorer" msgstr "Améliorer"
#: packages/lib/constants/document.ts
msgid "Upload"
msgstr ""
#: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx #: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx
msgid "Upload a CSV file to create multiple documents from this template. Each row represents one document with its recipient details." msgid "Upload a CSV file to create multiple documents from this template. Each row represents one document with its recipient details."
msgstr "Importer un fichier CSV pour créer plusieurs documents à partir de ce modèle. Chaque ligne représente un document avec les coordonnées de son destinataire." msgstr "Importer un fichier CSV pour créer plusieurs documents à partir de ce modèle. Chaque ligne représente un document avec les coordonnées de son destinataire."
@@ -6113,7 +6196,7 @@ msgstr "Importer le CSV"
msgid "Upload custom document" msgid "Upload custom document"
msgstr "Importer un document personnalisé" msgstr "Importer un document personnalisé"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-upload.tsx
msgid "Upload Signature" msgid "Upload Signature"
msgstr "Importer une signature" msgstr "Importer une signature"
@@ -6287,6 +6370,7 @@ msgstr "Voir le document"
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/email/template-components/template-document-rejected.tsx #: packages/email/template-components/template-document-rejected.tsx
#: packages/email/template-components/template-document-invite.tsx #: packages/email/template-components/template-document-invite.tsx
msgid "View Document" msgid "View Document"
@@ -6653,6 +6737,11 @@ msgstr "Bienvenue sur Documenso !"
msgid "Were you trying to edit this document instead?" msgid "Were you trying to edit this document instead?"
msgstr "Essayiez-vous d'éditer ce document à la place ?" msgstr "Essayiez-vous d'éditer ce document à la place ?"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/document-flow/add-signers.tsx
msgid "When enabled, signers can choose who should sign next in the sequence instead of following the predefined order."
msgstr ""
#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
msgid "When you click continue, you will be prompted to add the first available authenticator on your system." msgid "When you click continue, you will be prompted to add the first available authenticator on your system."
msgstr "Lorsque vous cliquez sur continuer, vous serez invité à ajouter le premier authentificateur disponible sur votre système." msgstr "Lorsque vous cliquez sur continuer, vous serez invité à ajouter le premier authentificateur disponible sur votre système."

View File

@@ -419,6 +419,10 @@ msgstr "<0>{teamName}</0> ha richiesto di utilizzare il tuo indirizzo email per
msgid "<0>Click to upload</0> or drag and drop" msgid "<0>Click to upload</0> or drag and drop"
msgstr "<0>Fai clic per caricare</0> o trascina e rilascia" msgstr "<0>Fai clic per caricare</0> o trascina e rilascia"
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "<0>Drawn</0> - A signature that is drawn using a mouse or stylus."
msgstr ""
#: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/template-flow/add-template-settings.tsx
msgid "<0>Email</0> - The recipient will be emailed the document to sign, approve, etc." msgid "<0>Email</0> - The recipient will be emailed the document to sign, approve, etc."
msgstr "<0>Email</0> - Al destinatario verrà inviato il documento tramite email per firmare, approvare, ecc." msgstr "<0>Email</0> - Al destinatario verrà inviato il documento tramite email per firmare, approvare, ecc."
@@ -465,6 +469,14 @@ msgstr "<0>Richiede passkey</0> - Il destinatario deve avere un account e una pa
msgid "<0>Sender:</0> All" msgid "<0>Sender:</0> All"
msgstr "<0>Mittente:</0> Tutti" msgstr "<0>Mittente:</0> Tutti"
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "<0>Typed</0> - A signature that is typed using a keyboard."
msgstr ""
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "<0>Uploaded</0> - A signature that is uploaded from a file."
msgstr ""
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
msgid "<0>You are about to complete approving <1>\"{documentTitle}\"</1>.</0><2/> Are you sure?" msgid "<0>You are about to complete approving <1>\"{documentTitle}\"</1>.</0><2/> Are you sure?"
msgstr "<0>Stai per completare l'approvazione di <1>\"{documentTitle}\"</1>.</0><2/> Sei sicuro?" msgstr "<0>Stai per completare l'approvazione di <1>\"{documentTitle}\"</1>.</0><2/> Sei sicuro?"
@@ -898,6 +910,16 @@ msgstr "Tutto il tempo"
msgid "Allow document recipients to reply directly to this email address" msgid "Allow document recipients to reply directly to this email address"
msgstr "Consenti ai destinatari del documento di rispondere direttamente a questo indirizzo email" msgstr "Consenti ai destinatari del documento di rispondere direttamente a questo indirizzo email"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/document-flow/add-signers.tsx
msgid "Allow signers to dictate next signer"
msgstr ""
#: packages/ui/primitives/template-flow/add-template-settings.tsx
#: packages/ui/primitives/document-flow/add-settings.tsx
msgid "Allowed Signature Types"
msgstr ""
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx #: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "Allows authenticating using biometrics, password managers, hardware keys, etc." msgid "Allows authenticating using biometrics, password managers, hardware keys, etc."
msgstr "Consente di autenticare utilizzando biometria, gestori di password, chiavi hardware, ecc." msgstr "Consente di autenticare utilizzando biometria, gestori di password, chiavi hardware, ecc."
@@ -1237,6 +1259,13 @@ msgstr ""
msgid "Assisting" msgid "Assisting"
msgstr "" msgstr ""
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
#: packages/ui/primitives/template-flow/add-template-settings.types.tsx
#: packages/ui/primitives/document-flow/add-settings.types.ts
#: packages/lib/types/document-meta.ts
msgid "At least one signature type must be enabled"
msgstr ""
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
msgid "Attempts sealing the document again, useful for after a code change has occurred to resolve an erroneous document." msgid "Attempts sealing the document again, useful for after a code change has occurred to resolve an erroneous document."
msgstr "Tenta nuovamente di sigillare il documento, utile dopo una modifica al codice per risolvere un documento errato." msgstr "Tenta nuovamente di sigillare il documento, utile dopo una modifica al codice per risolvere un documento errato."
@@ -1309,11 +1338,11 @@ msgstr "Prima di iniziare, conferma il tuo indirizzo email facendo clic sul puls
msgid "Billing" msgid "Billing"
msgstr "Fatturazione" msgstr "Fatturazione"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Black" msgid "Black"
msgstr "Nero" msgstr "Nero"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Blue" msgid "Blue"
msgstr "Blu" msgstr "Blu"
@@ -1384,6 +1413,10 @@ msgstr "Procedendo con l'utilizzo del servizio di firma elettronica fornito da D
msgid "By proceeding with your electronic signature, you acknowledge and consent that it will be used to sign the given document and holds the same legal validity as a handwritten signature. By completing the electronic signing process, you affirm your understanding and acceptance of these conditions." msgid "By proceeding with your electronic signature, you acknowledge and consent that it will be used to sign the given document and holds the same legal validity as a handwritten signature. By completing the electronic signing process, you affirm your understanding and acceptance of these conditions."
msgstr "Procedendo con la tua firma elettronica, riconosci e acconsenti che sarà utilizzata per firmare il documento dato e ha la stessa validità legale di una firma autografa. Completando il processo di firma elettronica, affermi la tua comprensione e accettazione di queste condizioni." msgstr "Procedendo con la tua firma elettronica, riconosci e acconsenti che sarà utilizzata per firmare il documento dato e ha la stessa validità legale di una firma autografa. Completando il processo di firma elettronica, affermi la tua comprensione e accettazione di queste condizioni."
#: apps/remix/app/components/forms/signup.tsx
msgid "By proceeding, you agree to our <0>Terms of Service</0> and <1>Privacy Policy</1>."
msgstr ""
#: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx #: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx
msgid "By using the electronic signature feature, you are consenting to conduct transactions and receive disclosures electronically. You acknowledge that your electronic signature on documents is binding and that you accept the terms outlined in the documents you are signing." msgid "By using the electronic signature feature, you are consenting to conduct transactions and receive disclosures electronically. You acknowledge that your electronic signature on documents is binding and that you accept the terms outlined in the documents you are signing."
msgstr "Utilizzando la funzione di firma elettronica, acconsenti a effettuare transazioni e ricevere divulgazioni elettronicamente. Riconosci che la tua firma elettronica sui documenti è vincolante e accetti i termini delineati nei documenti che stai firmando." msgstr "Utilizzando la funzione di firma elettronica, acconsenti a effettuare transazioni e ricevere divulgazioni elettronicamente. Riconosci che la tua firma elettronica sui documenti è vincolante e accetti i termini delineati nei documenti che stai firmando."
@@ -1436,6 +1469,8 @@ msgstr ""
#: apps/remix/app/components/dialogs/document-move-dialog.tsx #: apps/remix/app/components/dialogs/document-move-dialog.tsx
#: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx #: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx #: packages/ui/primitives/document-flow/send-document-action-dialog.tsx
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx #: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
msgid "Cancel" msgid "Cancel"
@@ -1522,7 +1557,7 @@ msgstr "Rimuovi file"
msgid "Clear filters" msgid "Clear filters"
msgstr "Cancella filtri" msgstr "Cancella filtri"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
msgid "Clear Signature" msgid "Clear Signature"
msgstr "Cancella firma" msgstr "Cancella firma"
@@ -1697,6 +1732,7 @@ msgstr "Contenuto"
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/document-flow/document-flow-root.tsx #: packages/ui/primitives/document-flow/document-flow-root.tsx
msgid "Continue" msgid "Continue"
msgstr "Continua" msgstr "Continua"
@@ -1737,14 +1773,18 @@ msgstr "Controlla la visibilità predefinita di un documento caricato."
msgid "Controls the formatting of the message that will be sent when inviting a recipient to sign a document. If a custom message has been provided while configuring the document, it will be used instead." msgid "Controls the formatting of the message that will be sent when inviting a recipient to sign a document. If a custom message has been provided while configuring the document, it will be used instead."
msgstr "Controlla la formattazione del messaggio che verrà inviato quando si invita un destinatario a firmare un documento. Se è stato fornito un messaggio personalizzato durante la configurazione del documento, verrà utilizzato invece." msgstr "Controlla la formattazione del messaggio che verrà inviato quando si invita un destinatario a firmare un documento. Se è stato fornito un messaggio personalizzato durante la configurazione del documento, verrà utilizzato invece."
#: apps/remix/app/components/forms/team-document-preferences-form.tsx #: packages/ui/primitives/document-flow/add-settings.tsx
msgid "Controls whether the recipients can sign the documents using a typed signature. Enable or disable the typed signature globally." msgid "Controls the language for the document, including the language to be used for email notifications, and the final certificate that is generated and attached to the document."
msgstr "Controlla se i destinatari possono firmare i documenti utilizzando una firma digitata. Abilita o disabilita la firma digitata a livello globale." msgstr ""
#: apps/remix/app/components/forms/team-document-preferences-form.tsx #: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "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." msgid "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."
msgstr "Controlla se il certificato di firma sarà incluso nel documento quando viene scaricato. Il certificato di firma può comunque essere scaricato separatamente dalla pagina dei log." msgstr "Controlla se il certificato di firma sarà incluso nel documento quando viene scaricato. Il certificato di firma può comunque essere scaricato separatamente dalla pagina dei log."
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "Controls which signatures are allowed to be used when signing a document."
msgstr ""
#: apps/remix/app/components/general/document/document-recipient-link-copy-dialog.tsx #: apps/remix/app/components/general/document/document-recipient-link-copy-dialog.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
msgid "Copied" msgid "Copied"
@@ -1975,6 +2015,10 @@ msgstr "Lingua predefinita del documento"
msgid "Default Document Visibility" msgid "Default Document Visibility"
msgstr "Visibilità predefinita del documento" msgstr "Visibilità predefinita del documento"
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "Default Signature Settings"
msgstr ""
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx
msgid "delete" msgid "delete"
msgstr "elimina" msgstr "elimina"
@@ -2201,6 +2245,11 @@ msgstr "Documento \"{0}\" - Rifiutato da {1}"
msgid "Document \"{0}\" - Rejection Confirmed" msgid "Document \"{0}\" - Rejection Confirmed"
msgstr "Documento \"{0}\" - Rifiuto Confermato" msgstr "Documento \"{0}\" - Rifiuto Confermato"
#. placeholder {0}: document.title
#: packages/lib/jobs/definitions/emails/send-document-cancelled-emails.handler.ts
msgid "Document \"{0}\" Cancelled"
msgstr ""
#: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/template-flow/add-template-settings.tsx
#: packages/ui/primitives/document-flow/add-settings.tsx #: packages/ui/primitives/document-flow/add-settings.tsx
#: packages/ui/components/document/document-global-auth-access-select.tsx #: packages/ui/components/document/document-global-auth-access-select.tsx
@@ -2349,6 +2398,10 @@ msgstr "Preferenze del documento aggiornate"
msgid "Document re-sent" msgid "Document re-sent"
msgstr "Documento rinviato" msgstr "Documento rinviato"
#: apps/remix/app/components/general/document/document-status.tsx
msgid "Document rejected"
msgstr ""
#: apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx #: apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx
#: apps/remix/app/components/embed/embed-document-rejected.tsx #: apps/remix/app/components/embed/embed-document-rejected.tsx
#: packages/email/template-components/template-document-rejected.tsx #: packages/email/template-components/template-document-rejected.tsx
@@ -2486,6 +2539,10 @@ msgstr "Documenti redatti"
msgid "Drag & drop your PDF here." msgid "Drag & drop your PDF here."
msgstr "Trascina e rilascia il tuo PDF qui." msgstr "Trascina e rilascia il tuo PDF qui."
#: packages/lib/constants/document.ts
msgid "Draw"
msgstr ""
#: packages/ui/primitives/template-flow/add-template-fields.tsx #: packages/ui/primitives/template-flow/add-template-fields.tsx
#: packages/ui/primitives/document-flow/add-fields.tsx #: packages/ui/primitives/document-flow/add-fields.tsx
msgid "Dropdown" msgid "Dropdown"
@@ -2543,6 +2600,7 @@ msgstr "Divulgazione della firma elettronica"
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx
#: apps/remix/app/components/general/document-signing/document-signing-email-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-email-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
#: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx #: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx
#: apps/remix/app/components/forms/signin.tsx #: apps/remix/app/components/forms/signin.tsx
#: apps/remix/app/components/forms/profile.tsx #: apps/remix/app/components/forms/profile.tsx
@@ -2553,6 +2611,7 @@ msgstr "Divulgazione della firma elettronica"
#: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-add-dialog.tsx #: apps/remix/app/components/dialogs/team-email-add-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-fields.tsx #: packages/ui/primitives/template-flow/add-template-fields.tsx
@@ -2646,15 +2705,6 @@ msgstr "Abilita la firma di link diretto"
msgid "Enable signing order" msgid "Enable signing order"
msgstr "Abilita ordine di firma" msgstr "Abilita ordine di firma"
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "Enable Typed Signature"
msgstr "Abilita firma digitata"
#: packages/ui/primitives/template-flow/add-template-fields.tsx
#: packages/ui/primitives/document-flow/add-fields.tsx
msgid "Enable Typed Signatures"
msgstr "Abilita firme digitate"
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks._index.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks._index.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx
#: apps/remix/app/routes/_authenticated+/settings+/webhooks._index.tsx #: apps/remix/app/routes/_authenticated+/settings+/webhooks._index.tsx
@@ -2929,7 +2979,7 @@ msgstr "Vai al proprietario"
msgid "Go to your <0>public profile settings</0> to add documents." msgid "Go to your <0>public profile settings</0> to add documents."
msgstr "Vai alle tue <0>impostazioni del profilo pubblico</0> per aggiungere documenti." msgstr "Vai alle tue <0>impostazioni del profilo pubblico</0> per aggiungere documenti."
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Green" msgid "Green"
msgstr "Verde" msgstr "Verde"
@@ -3483,10 +3533,12 @@ msgstr "I miei modelli"
#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx
#: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/general/claim-account.tsx
#: apps/remix/app/components/general/document-signing/document-signing-name-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-name-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
#: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx
#: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-add-dialog.tsx #: apps/remix/app/components/dialogs/team-email-add-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-fields.tsx #: packages/ui/primitives/template-flow/add-template-fields.tsx
@@ -3537,6 +3589,7 @@ msgstr "Nuovo modello"
#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/embed/embed-document-signing-page.tsx #: apps/remix/app/components/embed/embed-document-signing-page.tsx
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx #: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
#: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx
msgid "Next" msgid "Next"
msgstr "Successivo" msgstr "Successivo"
@@ -3954,6 +4007,7 @@ msgstr "Si prega di inserire un nome significativo per il proprio token. Questo
#: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/general/claim-account.tsx
#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/forms/profile.tsx
msgid "Please enter a valid name." msgid "Please enter a valid name."
msgstr "Per favore inserisci un nome valido." msgstr "Per favore inserisci un nome valido."
@@ -3989,10 +4043,6 @@ msgstr "Si prega di notare che questa azione è irreversibile. Una volta conferm
msgid "Please note that you will lose access to all documents associated with this team & all the members will be removed and notified" msgid "Please note that you will lose access to all documents associated with this team & all the members will be removed and notified"
msgstr "Si prega di notare che perderai l'accesso a tutti i documenti associati a questo team e tutti i membri saranno rimossi e notificati" msgstr "Si prega di notare che perderai l'accesso a tutti i documenti associati a questo team e tutti i membri saranno rimossi e notificati"
#: apps/remix/app/components/general/document-signing/document-signing-reject-dialog.tsx
msgid "Please provide a reason"
msgstr "Per favore, fornire una ragione"
#: apps/remix/app/components/forms/2fa/disable-authenticator-app-dialog.tsx #: apps/remix/app/components/forms/2fa/disable-authenticator-app-dialog.tsx
msgid "Please provide a token from the authenticator, or a backup code. If you do not have a backup code available, please contact support." msgid "Please provide a token from the authenticator, or a backup code. If you do not have a backup code available, please contact support."
msgstr "Si prega di fornire un token dal tuo autenticatore, o un codice di backup. Se non hai un codice di backup disponibile, contatta il supporto." msgstr "Si prega di fornire un token dal tuo autenticatore, o un codice di backup. Se non hai un codice di backup disponibile, contatta il supporto."
@@ -4063,6 +4113,10 @@ msgstr "Privato"
msgid "Private templates can only be modified and viewed by you." msgid "Private templates can only be modified and viewed by you."
msgstr "I modelli privati possono essere modificati e visualizzati solo da te." msgstr "I modelli privati possono essere modificati e visualizzati solo da te."
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
msgid "Proceed"
msgstr ""
#: apps/remix/app/routes/_authenticated+/settings+/profile.tsx #: apps/remix/app/routes/_authenticated+/settings+/profile.tsx
#: apps/remix/app/components/general/settings-nav-mobile.tsx #: apps/remix/app/components/general/settings-nav-mobile.tsx
#: apps/remix/app/components/general/settings-nav-desktop.tsx #: apps/remix/app/components/general/settings-nav-desktop.tsx
@@ -4140,6 +4194,10 @@ msgstr "Pronto"
msgid "Reason" msgid "Reason"
msgstr "Motivo" msgstr "Motivo"
#: packages/email/template-components/template-document-cancel.tsx
msgid "Reason for cancellation: {cancellationReason}"
msgstr ""
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx #: apps/remix/app/components/general/document/document-page-view-recipients.tsx
msgid "Reason for rejection: " msgid "Reason for rejection: "
msgstr "" msgstr ""
@@ -4222,7 +4280,7 @@ msgstr "Codice di recupero copiato"
msgid "Recovery codes" msgid "Recovery codes"
msgstr "Codici di recupero" msgstr "Codici di recupero"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Red" msgid "Red"
msgstr "Rosso" msgstr "Rosso"
@@ -4243,8 +4301,11 @@ msgstr "Registrazione avvenuta con successo"
msgid "Reject Document" msgid "Reject Document"
msgstr "Rifiuta Documento" msgstr "Rifiuta Documento"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx #: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx
#: apps/remix/app/components/general/document/document-status.tsx
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx #: apps/remix/app/components/general/document/document-page-view-recipients.tsx
#: packages/lib/constants/document.ts
msgid "Rejected" msgid "Rejected"
msgstr "Rifiutato" msgstr "Rifiutato"
@@ -4429,8 +4490,6 @@ msgstr "Righe per pagina"
#: apps/remix/app/components/general/document-signing/document-signing-text-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-text-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-number-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-number-field.tsx
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
#: apps/remix/app/components/forms/team-branding-preferences-form.tsx
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx #: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
msgid "Save" msgid "Save"
@@ -4740,7 +4799,6 @@ msgstr "Iscriviti con OIDC"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx #: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx
#: apps/remix/app/components/forms/profile.tsx #: apps/remix/app/components/forms/profile.tsx
@@ -4757,12 +4815,17 @@ msgstr "Firma"
msgid "Signature ID" msgid "Signature ID"
msgstr "ID Firma" msgstr "ID Firma"
#: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx #: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx msgid "Signature is too small"
#: apps/remix/app/components/embed/embed-document-signing-page.tsx msgstr ""
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
msgid "Signature is too small. Please provide a more complete signature." #: apps/remix/app/components/forms/profile.tsx
msgstr "La firma è troppo piccola. Si prega di fornire una firma più completa." msgid "Signature Pad cannot be empty."
msgstr ""
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "Signature types"
msgstr ""
#: apps/remix/app/routes/_authenticated+/admin+/stats.tsx #: apps/remix/app/routes/_authenticated+/admin+/stats.tsx
msgid "Signatures Collected" msgid "Signatures Collected"
@@ -4987,6 +5050,7 @@ msgid "Subject <0>(Optional)</0>"
msgstr "Oggetto <0>(Opzionale)</0>" msgstr "Oggetto <0>(Opzionale)</0>"
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
msgid "Submitting..." msgid "Submitting..."
msgstr "" msgstr ""
@@ -5501,6 +5565,10 @@ msgstr "Il token che hai usato per reimpostare la tua password è scaduto o non
msgid "The two-factor authentication code provided is incorrect" msgid "The two-factor authentication code provided is incorrect"
msgstr "" msgstr ""
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "The types of signatures that recipients are allowed to use when signing the document."
msgstr ""
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx
#: apps/remix/app/routes/_authenticated+/settings+/webhooks.$id.tsx #: apps/remix/app/routes/_authenticated+/settings+/webhooks.$id.tsx
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx #: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
@@ -5582,6 +5650,10 @@ msgstr "Questo documento è stato annullato dal proprietario e non è più dispo
msgid "This document has been cancelled by the owner." msgid "This document has been cancelled by the owner."
msgstr "Questo documento è stato annullato dal proprietario." msgstr "Questo documento è stato annullato dal proprietario."
#: apps/remix/app/routes/_authenticated+/documents.$id._index.tsx
msgid "This document has been rejected by a recipient"
msgstr ""
#: apps/remix/app/routes/_authenticated+/documents.$id._index.tsx #: apps/remix/app/routes/_authenticated+/documents.$id._index.tsx
msgid "This document has been signed by all recipients" msgid "This document has been signed by all recipients"
msgstr "Questo documento è stato firmato da tutti i destinatari" msgstr "Questo documento è stato firmato da tutti i destinatari"
@@ -5730,6 +5802,10 @@ msgstr "Fuso orario"
msgid "Title" msgid "Title"
msgstr "Titolo" msgstr "Titolo"
#: packages/ui/primitives/document-flow/add-settings.types.ts
msgid "Title cannot be empty"
msgstr ""
#: apps/remix/app/routes/_unauthenticated+/team.invite.$token.tsx #: apps/remix/app/routes/_unauthenticated+/team.invite.$token.tsx
msgid "To accept this invitation you must create an account." msgid "To accept this invitation you must create an account."
msgstr "Per accettare questo invito devi creare un account." msgstr "Per accettare questo invito devi creare un account."
@@ -5897,6 +5973,7 @@ msgstr "Ri-autenticazione a due fattori"
#: apps/remix/app/components/tables/templates-table.tsx #: apps/remix/app/components/tables/templates-table.tsx
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: packages/lib/constants/document.ts
msgid "Type" msgid "Type"
msgstr "Tipo" msgstr "Tipo"
@@ -6004,6 +6081,7 @@ msgstr "Incompleto"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.billing.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.billing.tsx
msgid "Unknown" msgid "Unknown"
msgstr "Sconosciuto" msgstr "Sconosciuto"
@@ -6014,11 +6092,14 @@ msgstr "Non pagato"
#: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
#: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx #: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
#: apps/remix/app/components/forms/team-branding-preferences-form.tsx
#: apps/remix/app/components/forms/public-profile-form.tsx #: apps/remix/app/components/forms/public-profile-form.tsx
#: apps/remix/app/components/dialogs/team-member-update-dialog.tsx #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
msgid "Update" msgid "Update"
msgstr "Aggiorna" msgstr "Aggiorna"
@@ -6039,6 +6120,8 @@ msgid "Update profile"
msgstr "Aggiorna profilo" msgstr "Aggiorna profilo"
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
msgid "Update Recipient" msgid "Update Recipient"
msgstr "Aggiorna destinatario" msgstr "Aggiorna destinatario"
@@ -6077,10 +6160,6 @@ msgstr "Aggiorna webhook"
msgid "Updating password..." msgid "Updating password..."
msgstr "Aggiornamento della password..." msgstr "Aggiornamento della password..."
#: apps/remix/app/components/forms/profile.tsx
msgid "Updating profile..."
msgstr "Aggiornamento del profilo..."
#: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx #: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx
msgid "Updating Your Information" msgid "Updating Your Information"
msgstr "Aggiornamento delle tue informazioni" msgstr "Aggiornamento delle tue informazioni"
@@ -6089,6 +6168,10 @@ msgstr "Aggiornamento delle tue informazioni"
msgid "Upgrade" msgid "Upgrade"
msgstr "Aggiorna" msgstr "Aggiorna"
#: packages/lib/constants/document.ts
msgid "Upload"
msgstr ""
#: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx #: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx
msgid "Upload a CSV file to create multiple documents from this template. Each row represents one document with its recipient details." msgid "Upload a CSV file to create multiple documents from this template. Each row represents one document with its recipient details."
msgstr "Carica un file CSV per creare più documenti da questo modello. Ogni riga rappresenta un documento con i dettagli del destinatario." msgstr "Carica un file CSV per creare più documenti da questo modello. Ogni riga rappresenta un documento con i dettagli del destinatario."
@@ -6113,7 +6196,7 @@ msgstr "Carica CSV"
msgid "Upload custom document" msgid "Upload custom document"
msgstr "Carica documento personalizzato" msgstr "Carica documento personalizzato"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-upload.tsx
msgid "Upload Signature" msgid "Upload Signature"
msgstr "Carica Firma" msgstr "Carica Firma"
@@ -6287,6 +6370,7 @@ msgstr "Visualizza documento"
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/email/template-components/template-document-rejected.tsx #: packages/email/template-components/template-document-rejected.tsx
#: packages/email/template-components/template-document-invite.tsx #: packages/email/template-components/template-document-invite.tsx
msgid "View Document" msgid "View Document"
@@ -6653,6 +6737,11 @@ msgstr "Benvenuto su Documenso!"
msgid "Were you trying to edit this document instead?" msgid "Were you trying to edit this document instead?"
msgstr "Stavi provando a modificare questo documento invece?" msgstr "Stavi provando a modificare questo documento invece?"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/document-flow/add-signers.tsx
msgid "When enabled, signers can choose who should sign next in the sequence instead of following the predefined order."
msgstr ""
#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
msgid "When you click continue, you will be prompted to add the first available authenticator on your system." msgid "When you click continue, you will be prompted to add the first available authenticator on your system."
msgstr "Quando fai clic su continua, ti verrà chiesto di aggiungere il primo autenticatore disponibile sul tuo sistema." msgstr "Quando fai clic su continua, ti verrà chiesto di aggiungere il primo autenticatore disponibile sul tuo sistema."

View File

@@ -419,6 +419,10 @@ msgstr "<0>{teamName}</0> poprosił o używanie twojego adresu e-mail dla swojeg
msgid "<0>Click to upload</0> or drag and drop" msgid "<0>Click to upload</0> or drag and drop"
msgstr "<0>Kliknij, aby przesłać</0> lub przeciągnij i upuść" msgstr "<0>Kliknij, aby przesłać</0> lub przeciągnij i upuść"
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "<0>Drawn</0> - A signature that is drawn using a mouse or stylus."
msgstr ""
#: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/template-flow/add-template-settings.tsx
msgid "<0>Email</0> - The recipient will be emailed the document to sign, approve, etc." msgid "<0>Email</0> - The recipient will be emailed the document to sign, approve, etc."
msgstr "<0>E-mail</0> - Odbiorca otrzyma e-mail z dokumentem do podpisania, zatwierdzenia itp." msgstr "<0>E-mail</0> - Odbiorca otrzyma e-mail z dokumentem do podpisania, zatwierdzenia itp."
@@ -465,6 +469,14 @@ msgstr "<0>Wymagana passkey</0> - Odbiorca musi mieć konto i skonfigurowaną pa
msgid "<0>Sender:</0> All" msgid "<0>Sender:</0> All"
msgstr "<0>Rządzący:</0> Wszyscy" msgstr "<0>Rządzący:</0> Wszyscy"
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "<0>Typed</0> - A signature that is typed using a keyboard."
msgstr ""
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "<0>Uploaded</0> - A signature that is uploaded from a file."
msgstr ""
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
msgid "<0>You are about to complete approving <1>\"{documentTitle}\"</1>.</0><2/> Are you sure?" msgid "<0>You are about to complete approving <1>\"{documentTitle}\"</1>.</0><2/> Are you sure?"
msgstr "<0>Jesteś na drodze do zatwierdzenia <1>\"{documentTitle}\"</1>.</0><2/> Czy jesteś pewien?" msgstr "<0>Jesteś na drodze do zatwierdzenia <1>\"{documentTitle}\"</1>.</0><2/> Czy jesteś pewien?"
@@ -898,6 +910,16 @@ msgstr "Cały czas"
msgid "Allow document recipients to reply directly to this email address" msgid "Allow document recipients to reply directly to this email address"
msgstr "Zezwól odbiorcom dokumentów na bezpośrednią odpowiedź na ten adres e-mail" msgstr "Zezwól odbiorcom dokumentów na bezpośrednią odpowiedź na ten adres e-mail"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/document-flow/add-signers.tsx
msgid "Allow signers to dictate next signer"
msgstr ""
#: packages/ui/primitives/template-flow/add-template-settings.tsx
#: packages/ui/primitives/document-flow/add-settings.tsx
msgid "Allowed Signature Types"
msgstr ""
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx #: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "Allows authenticating using biometrics, password managers, hardware keys, etc." msgid "Allows authenticating using biometrics, password managers, hardware keys, etc."
msgstr "Pozwala na uwierzytelnianie za pomocą biometrii, menedżerów haseł, kluczy sprzętowych itp." msgstr "Pozwala na uwierzytelnianie za pomocą biometrii, menedżerów haseł, kluczy sprzętowych itp."
@@ -1237,6 +1259,13 @@ msgstr ""
msgid "Assisting" msgid "Assisting"
msgstr "" msgstr ""
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
#: packages/ui/primitives/template-flow/add-template-settings.types.tsx
#: packages/ui/primitives/document-flow/add-settings.types.ts
#: packages/lib/types/document-meta.ts
msgid "At least one signature type must be enabled"
msgstr ""
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
msgid "Attempts sealing the document again, useful for after a code change has occurred to resolve an erroneous document." msgid "Attempts sealing the document again, useful for after a code change has occurred to resolve an erroneous document."
msgstr "Ponowne próby zapieczętowania dokumentu, przydatne po zmianie kodu w celu rozwiązania błędnego dokumentu." msgstr "Ponowne próby zapieczętowania dokumentu, przydatne po zmianie kodu w celu rozwiązania błędnego dokumentu."
@@ -1309,11 +1338,11 @@ msgstr "Zanim zaczniesz, proszę potwierdź swój adres e-mail, klikając przyci
msgid "Billing" msgid "Billing"
msgstr "Fakturowanie" msgstr "Fakturowanie"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Black" msgid "Black"
msgstr "Czarny" msgstr "Czarny"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Blue" msgid "Blue"
msgstr "Niebieski" msgstr "Niebieski"
@@ -1384,6 +1413,10 @@ msgstr "Kontynuując korzystanie z usługi podpisu elektronicznego oferowanej pr
msgid "By proceeding with your electronic signature, you acknowledge and consent that it will be used to sign the given document and holds the same legal validity as a handwritten signature. By completing the electronic signing process, you affirm your understanding and acceptance of these conditions." msgid "By proceeding with your electronic signature, you acknowledge and consent that it will be used to sign the given document and holds the same legal validity as a handwritten signature. By completing the electronic signing process, you affirm your understanding and acceptance of these conditions."
msgstr "Kontynuując z Twoim podpisem elektronicznym, przyjmujesz i zgadzasz się, że będzie on użyty do podpisania danego dokumentu i ma tę samą ważność prawną jak odręczny podpis. Dokonując procesu podpisu elektronicznego, potwierdzasz swoje zrozumienie i akceptację tych warunków." msgstr "Kontynuując z Twoim podpisem elektronicznym, przyjmujesz i zgadzasz się, że będzie on użyty do podpisania danego dokumentu i ma tę samą ważność prawną jak odręczny podpis. Dokonując procesu podpisu elektronicznego, potwierdzasz swoje zrozumienie i akceptację tych warunków."
#: apps/remix/app/components/forms/signup.tsx
msgid "By proceeding, you agree to our <0>Terms of Service</0> and <1>Privacy Policy</1>."
msgstr ""
#: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx #: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx
msgid "By using the electronic signature feature, you are consenting to conduct transactions and receive disclosures electronically. You acknowledge that your electronic signature on documents is binding and that you accept the terms outlined in the documents you are signing." msgid "By using the electronic signature feature, you are consenting to conduct transactions and receive disclosures electronically. You acknowledge that your electronic signature on documents is binding and that you accept the terms outlined in the documents you are signing."
msgstr "Korzystając z funkcji podpisu elektronicznego, wyrażasz zgodę na przeprowadzanie transakcji i otrzymywanie ujawnień elektronicznie. Przyjmujesz do wiadomości, że Twój podpis elektroniczny na dokumentach jest wiążący i akceptujesz warunki przedstawione w dokumentach, które podpisujesz." msgstr "Korzystając z funkcji podpisu elektronicznego, wyrażasz zgodę na przeprowadzanie transakcji i otrzymywanie ujawnień elektronicznie. Przyjmujesz do wiadomości, że Twój podpis elektroniczny na dokumentach jest wiążący i akceptujesz warunki przedstawione w dokumentach, które podpisujesz."
@@ -1436,6 +1469,8 @@ msgstr ""
#: apps/remix/app/components/dialogs/document-move-dialog.tsx #: apps/remix/app/components/dialogs/document-move-dialog.tsx
#: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx #: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx #: packages/ui/primitives/document-flow/send-document-action-dialog.tsx
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx #: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
msgid "Cancel" msgid "Cancel"
@@ -1522,7 +1557,7 @@ msgstr "Wyczyść plik"
msgid "Clear filters" msgid "Clear filters"
msgstr "Wyczyść filtry" msgstr "Wyczyść filtry"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
msgid "Clear Signature" msgid "Clear Signature"
msgstr "Wyczyść podpis" msgstr "Wyczyść podpis"
@@ -1697,6 +1732,7 @@ msgstr "Treść"
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/document-flow/document-flow-root.tsx #: packages/ui/primitives/document-flow/document-flow-root.tsx
msgid "Continue" msgid "Continue"
msgstr "Kontynuuj" msgstr "Kontynuuj"
@@ -1737,14 +1773,18 @@ msgstr "Kontroluje domyślną widoczność przesłanego dokumentu."
msgid "Controls the formatting of the message that will be sent when inviting a recipient to sign a document. If a custom message has been provided while configuring the document, it will be used instead." msgid "Controls the formatting of the message that will be sent when inviting a recipient to sign a document. If a custom message has been provided while configuring the document, it will be used instead."
msgstr "Kontroluje formatowanie wiadomości, która zostanie wysłana podczas zapraszania odbiorcy do podpisania dokumentu. Jeśli w konfiguracji dokumentu podano niestandardową wiadomość, zostanie użyta zamiast tego." msgstr "Kontroluje formatowanie wiadomości, która zostanie wysłana podczas zapraszania odbiorcy do podpisania dokumentu. Jeśli w konfiguracji dokumentu podano niestandardową wiadomość, zostanie użyta zamiast tego."
#: apps/remix/app/components/forms/team-document-preferences-form.tsx #: packages/ui/primitives/document-flow/add-settings.tsx
msgid "Controls whether the recipients can sign the documents using a typed signature. Enable or disable the typed signature globally." msgid "Controls the language for the document, including the language to be used for email notifications, and the final certificate that is generated and attached to the document."
msgstr "Kontroluje, czy odbiorcy mogą podpisywać dokumenty za pomocą pisanych podpisów. Włącz lub wyłącz podpis pisany globalnie." msgstr ""
#: apps/remix/app/components/forms/team-document-preferences-form.tsx #: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "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." msgid "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."
msgstr "Kontroluje, czy certyfikat podpisu zostanie dołączony do dokumentu podczas jego pobierania. Certyfikat podpisu można również pobrać osobno ze strony logów." msgstr "Kontroluje, czy certyfikat podpisu zostanie dołączony do dokumentu podczas jego pobierania. Certyfikat podpisu można również pobrać osobno ze strony logów."
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "Controls which signatures are allowed to be used when signing a document."
msgstr ""
#: apps/remix/app/components/general/document/document-recipient-link-copy-dialog.tsx #: apps/remix/app/components/general/document/document-recipient-link-copy-dialog.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
msgid "Copied" msgid "Copied"
@@ -1975,6 +2015,10 @@ msgstr "Domyślny język dokumentu"
msgid "Default Document Visibility" msgid "Default Document Visibility"
msgstr "Domyślna widoczność dokumentu" msgstr "Domyślna widoczność dokumentu"
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "Default Signature Settings"
msgstr ""
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx
msgid "delete" msgid "delete"
msgstr "usuń" msgstr "usuń"
@@ -2201,6 +2245,11 @@ msgstr "Dokument \"{0}\" - Odrzucony przez {1}"
msgid "Document \"{0}\" - Rejection Confirmed" msgid "Document \"{0}\" - Rejection Confirmed"
msgstr "Dokument \"{0}\" - Odrzucenie potwierdzone" msgstr "Dokument \"{0}\" - Odrzucenie potwierdzone"
#. placeholder {0}: document.title
#: packages/lib/jobs/definitions/emails/send-document-cancelled-emails.handler.ts
msgid "Document \"{0}\" Cancelled"
msgstr ""
#: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/template-flow/add-template-settings.tsx
#: packages/ui/primitives/document-flow/add-settings.tsx #: packages/ui/primitives/document-flow/add-settings.tsx
#: packages/ui/components/document/document-global-auth-access-select.tsx #: packages/ui/components/document/document-global-auth-access-select.tsx
@@ -2349,6 +2398,10 @@ msgstr "Preferencje dokumentu zaktualizowane"
msgid "Document re-sent" msgid "Document re-sent"
msgstr "Dokument ponownie wysłany" msgstr "Dokument ponownie wysłany"
#: apps/remix/app/components/general/document/document-status.tsx
msgid "Document rejected"
msgstr ""
#: apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx #: apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx
#: apps/remix/app/components/embed/embed-document-rejected.tsx #: apps/remix/app/components/embed/embed-document-rejected.tsx
#: packages/email/template-components/template-document-rejected.tsx #: packages/email/template-components/template-document-rejected.tsx
@@ -2486,6 +2539,10 @@ msgstr "Szkice dokumentów"
msgid "Drag & drop your PDF here." msgid "Drag & drop your PDF here."
msgstr "Przeciągnij i upuść swój PDF tutaj." msgstr "Przeciągnij i upuść swój PDF tutaj."
#: packages/lib/constants/document.ts
msgid "Draw"
msgstr ""
#: packages/ui/primitives/template-flow/add-template-fields.tsx #: packages/ui/primitives/template-flow/add-template-fields.tsx
#: packages/ui/primitives/document-flow/add-fields.tsx #: packages/ui/primitives/document-flow/add-fields.tsx
msgid "Dropdown" msgid "Dropdown"
@@ -2543,6 +2600,7 @@ msgstr "Ujawnienie podpisu elektronicznego"
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx
#: apps/remix/app/components/general/document-signing/document-signing-email-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-email-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
#: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx #: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx
#: apps/remix/app/components/forms/signin.tsx #: apps/remix/app/components/forms/signin.tsx
#: apps/remix/app/components/forms/profile.tsx #: apps/remix/app/components/forms/profile.tsx
@@ -2553,6 +2611,7 @@ msgstr "Ujawnienie podpisu elektronicznego"
#: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-add-dialog.tsx #: apps/remix/app/components/dialogs/team-email-add-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-fields.tsx #: packages/ui/primitives/template-flow/add-template-fields.tsx
@@ -2646,15 +2705,6 @@ msgstr "Włącz podpisywanie linku bezpośredniego"
msgid "Enable signing order" msgid "Enable signing order"
msgstr "Włącz kolejność podpisów" msgstr "Włącz kolejność podpisów"
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
msgid "Enable Typed Signature"
msgstr "Włącz podpis pisany"
#: packages/ui/primitives/template-flow/add-template-fields.tsx
#: packages/ui/primitives/document-flow/add-fields.tsx
msgid "Enable Typed Signatures"
msgstr "Włącz podpisy typu pisanego"
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks._index.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks._index.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx
#: apps/remix/app/routes/_authenticated+/settings+/webhooks._index.tsx #: apps/remix/app/routes/_authenticated+/settings+/webhooks._index.tsx
@@ -2929,7 +2979,7 @@ msgstr "Przejdź do właściciela"
msgid "Go to your <0>public profile settings</0> to add documents." msgid "Go to your <0>public profile settings</0> to add documents."
msgstr "Przejdź do swojego <0>ustawienia profilu publicznego</0>, aby dodać dokumenty." msgstr "Przejdź do swojego <0>ustawienia profilu publicznego</0>, aby dodać dokumenty."
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Green" msgid "Green"
msgstr "Zielony" msgstr "Zielony"
@@ -3483,10 +3533,12 @@ msgstr "Moje szablony"
#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx
#: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/general/claim-account.tsx
#: apps/remix/app/components/general/document-signing/document-signing-name-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-name-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
#: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx
#: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-add-dialog.tsx #: apps/remix/app/components/dialogs/team-email-add-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx #: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/template-flow/add-template-fields.tsx #: packages/ui/primitives/template-flow/add-template-fields.tsx
@@ -3537,6 +3589,7 @@ msgstr "Nowy szablon"
#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/embed/embed-document-signing-page.tsx #: apps/remix/app/components/embed/embed-document-signing-page.tsx
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx #: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
#: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx
msgid "Next" msgid "Next"
msgstr "Dalej" msgstr "Dalej"
@@ -3954,6 +4007,7 @@ msgstr "Wpisz nazwę tokena. Pomoże to później w jego identyfikacji."
#: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/general/claim-account.tsx
#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/forms/signup.tsx
#: apps/remix/app/components/forms/profile.tsx
msgid "Please enter a valid name." msgid "Please enter a valid name."
msgstr "Proszę wpisać poprawną nazwę." msgstr "Proszę wpisać poprawną nazwę."
@@ -3989,10 +4043,6 @@ msgstr "Proszę pamiętać, że ta czynność jest nieodwracalna. Po potwierdzen
msgid "Please note that you will lose access to all documents associated with this team & all the members will be removed and notified" msgid "Please note that you will lose access to all documents associated with this team & all the members will be removed and notified"
msgstr "Proszę pamiętać, że stracisz dostęp do wszystkich dokumentów powiązanych z tym zespołem i wszyscy członkowie zostaną usunięci oraz powiadomieni" msgstr "Proszę pamiętać, że stracisz dostęp do wszystkich dokumentów powiązanych z tym zespołem i wszyscy członkowie zostaną usunięci oraz powiadomieni"
#: apps/remix/app/components/general/document-signing/document-signing-reject-dialog.tsx
msgid "Please provide a reason"
msgstr "Proszę podać powód"
#: apps/remix/app/components/forms/2fa/disable-authenticator-app-dialog.tsx #: apps/remix/app/components/forms/2fa/disable-authenticator-app-dialog.tsx
msgid "Please provide a token from the authenticator, or a backup code. If you do not have a backup code available, please contact support." msgid "Please provide a token from the authenticator, or a backup code. If you do not have a backup code available, please contact support."
msgstr "Proszę podać token z aplikacji uwierzytelniającej lub kod zapasowy. Jeśli nie masz dostępnego kodu zapasowego, skontaktuj się z pomocą techniczną." msgstr "Proszę podać token z aplikacji uwierzytelniającej lub kod zapasowy. Jeśli nie masz dostępnego kodu zapasowego, skontaktuj się z pomocą techniczną."
@@ -4063,6 +4113,10 @@ msgstr "Prywatne"
msgid "Private templates can only be modified and viewed by you." msgid "Private templates can only be modified and viewed by you."
msgstr "Prywatne szablony mogą być modyfikowane i przeglądane tylko przez Ciebie." msgstr "Prywatne szablony mogą być modyfikowane i przeglądane tylko przez Ciebie."
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
msgid "Proceed"
msgstr ""
#: apps/remix/app/routes/_authenticated+/settings+/profile.tsx #: apps/remix/app/routes/_authenticated+/settings+/profile.tsx
#: apps/remix/app/components/general/settings-nav-mobile.tsx #: apps/remix/app/components/general/settings-nav-mobile.tsx
#: apps/remix/app/components/general/settings-nav-desktop.tsx #: apps/remix/app/components/general/settings-nav-desktop.tsx
@@ -4140,6 +4194,10 @@ msgstr "Gotowy"
msgid "Reason" msgid "Reason"
msgstr "Powód" msgstr "Powód"
#: packages/email/template-components/template-document-cancel.tsx
msgid "Reason for cancellation: {cancellationReason}"
msgstr ""
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx #: apps/remix/app/components/general/document/document-page-view-recipients.tsx
msgid "Reason for rejection: " msgid "Reason for rejection: "
msgstr "" msgstr ""
@@ -4222,7 +4280,7 @@ msgstr "Kod odzyskiwania skopiowany"
msgid "Recovery codes" msgid "Recovery codes"
msgstr "Kody odzyskiwania" msgstr "Kody odzyskiwania"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
msgid "Red" msgid "Red"
msgstr "Czerwony" msgstr "Czerwony"
@@ -4243,8 +4301,11 @@ msgstr "Rejestracja zakończona sukcesem"
msgid "Reject Document" msgid "Reject Document"
msgstr "Odrzuć dokument" msgstr "Odrzuć dokument"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx #: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx
#: apps/remix/app/components/general/document/document-status.tsx
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx #: apps/remix/app/components/general/document/document-page-view-recipients.tsx
#: packages/lib/constants/document.ts
msgid "Rejected" msgid "Rejected"
msgstr "Odrzucony" msgstr "Odrzucony"
@@ -4429,8 +4490,6 @@ msgstr "Wiersze na stronę"
#: apps/remix/app/components/general/document-signing/document-signing-text-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-text-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-number-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-number-field.tsx
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
#: apps/remix/app/components/forms/team-branding-preferences-form.tsx
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx #: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
msgid "Save" msgid "Save"
@@ -4740,7 +4799,6 @@ msgstr "Zarejestruj się za pomocą OIDC"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx #: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx
#: apps/remix/app/components/forms/profile.tsx #: apps/remix/app/components/forms/profile.tsx
@@ -4757,12 +4815,17 @@ msgstr "Podpis"
msgid "Signature ID" msgid "Signature ID"
msgstr "Identyfikator podpisu" msgstr "Identyfikator podpisu"
#: apps/remix/app/components/general/document-signing/document-signing-signature-field.tsx #: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx msgid "Signature is too small"
#: apps/remix/app/components/embed/embed-document-signing-page.tsx msgstr ""
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
msgid "Signature is too small. Please provide a more complete signature." #: apps/remix/app/components/forms/profile.tsx
msgstr "Podpis jest zbyt mały. Proszę podać bardziej kompletny podpis." msgid "Signature Pad cannot be empty."
msgstr ""
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "Signature types"
msgstr ""
#: apps/remix/app/routes/_authenticated+/admin+/stats.tsx #: apps/remix/app/routes/_authenticated+/admin+/stats.tsx
msgid "Signatures Collected" msgid "Signatures Collected"
@@ -4987,6 +5050,7 @@ msgid "Subject <0>(Optional)</0>"
msgstr "Temat <0>(Opcjonalnie)</0>" msgstr "Temat <0>(Opcjonalnie)</0>"
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
msgid "Submitting..." msgid "Submitting..."
msgstr "" msgstr ""
@@ -5501,6 +5565,10 @@ msgstr "Token, którego użyłeś do zresetowania hasła, jest albo wygasły, al
msgid "The two-factor authentication code provided is incorrect" msgid "The two-factor authentication code provided is incorrect"
msgstr "" msgstr ""
#: packages/ui/components/document/document-signature-settings-tooltip.tsx
msgid "The types of signatures that recipients are allowed to use when signing the document."
msgstr ""
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx
#: apps/remix/app/routes/_authenticated+/settings+/webhooks.$id.tsx #: apps/remix/app/routes/_authenticated+/settings+/webhooks.$id.tsx
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx #: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
@@ -5582,6 +5650,10 @@ msgstr "Ten dokument został anulowany przez właściciela i nie jest już dost
msgid "This document has been cancelled by the owner." msgid "This document has been cancelled by the owner."
msgstr "Ten dokument został anulowany przez właściciela." msgstr "Ten dokument został anulowany przez właściciela."
#: apps/remix/app/routes/_authenticated+/documents.$id._index.tsx
msgid "This document has been rejected by a recipient"
msgstr ""
#: apps/remix/app/routes/_authenticated+/documents.$id._index.tsx #: apps/remix/app/routes/_authenticated+/documents.$id._index.tsx
msgid "This document has been signed by all recipients" msgid "This document has been signed by all recipients"
msgstr "Ten dokument został podpisany przez wszystkich odbiorców" msgstr "Ten dokument został podpisany przez wszystkich odbiorców"
@@ -5730,6 +5802,10 @@ msgstr "Strefa czasowa"
msgid "Title" msgid "Title"
msgstr "Tytuł" msgstr "Tytuł"
#: packages/ui/primitives/document-flow/add-settings.types.ts
msgid "Title cannot be empty"
msgstr ""
#: apps/remix/app/routes/_unauthenticated+/team.invite.$token.tsx #: apps/remix/app/routes/_unauthenticated+/team.invite.$token.tsx
msgid "To accept this invitation you must create an account." msgid "To accept this invitation you must create an account."
msgstr "Aby zaakceptować to zaproszenie, musisz założyć konto." msgstr "Aby zaakceptować to zaproszenie, musisz założyć konto."
@@ -5897,6 +5973,7 @@ msgstr "Ponowna autoryzacja za pomocą dwuetapowej weryfikacji"
#: apps/remix/app/components/tables/templates-table.tsx #: apps/remix/app/components/tables/templates-table.tsx
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: packages/lib/constants/document.ts
msgid "Type" msgid "Type"
msgstr "Typ" msgstr "Typ"
@@ -6004,6 +6081,7 @@ msgstr "Niezakończony"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.billing.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.billing.tsx
msgid "Unknown" msgid "Unknown"
msgstr "Nieznany" msgstr "Nieznany"
@@ -6014,11 +6092,14 @@ msgstr "Nieopłacone"
#: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
#: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx #: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx
#: apps/remix/app/components/forms/team-document-preferences-form.tsx
#: apps/remix/app/components/forms/team-branding-preferences-form.tsx
#: apps/remix/app/components/forms/public-profile-form.tsx #: apps/remix/app/components/forms/public-profile-form.tsx
#: apps/remix/app/components/dialogs/team-member-update-dialog.tsx #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx
#: apps/remix/app/components/dialogs/team-email-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
msgid "Update" msgid "Update"
msgstr "Zaktualizuj" msgstr "Zaktualizuj"
@@ -6039,6 +6120,8 @@ msgid "Update profile"
msgstr "Zaktualizuj profil" msgstr "Zaktualizuj profil"
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
msgid "Update Recipient" msgid "Update Recipient"
msgstr "Zaktualizuj odbiorcę" msgstr "Zaktualizuj odbiorcę"
@@ -6077,10 +6160,6 @@ msgstr "Zaktualizuj webhook"
msgid "Updating password..." msgid "Updating password..."
msgstr "Aktualizowanie hasła..." msgstr "Aktualizowanie hasła..."
#: apps/remix/app/components/forms/profile.tsx
msgid "Updating profile..."
msgstr "Aktualizacja profilu..."
#: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx #: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx
msgid "Updating Your Information" msgid "Updating Your Information"
msgstr "Aktualizacja Twoich informacji" msgstr "Aktualizacja Twoich informacji"
@@ -6089,6 +6168,10 @@ msgstr "Aktualizacja Twoich informacji"
msgid "Upgrade" msgid "Upgrade"
msgstr "Ulepsz" msgstr "Ulepsz"
#: packages/lib/constants/document.ts
msgid "Upload"
msgstr ""
#: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx #: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx
msgid "Upload a CSV file to create multiple documents from this template. Each row represents one document with its recipient details." msgid "Upload a CSV file to create multiple documents from this template. Each row represents one document with its recipient details."
msgstr "Prześlij plik CSV, aby utworzyć wiele dokumentów z tego szablonu. Każda linia reprezentuje jeden dokument z jego szczegółami odbiorcy." msgstr "Prześlij plik CSV, aby utworzyć wiele dokumentów z tego szablonu. Każda linia reprezentuje jeden dokument z jego szczegółami odbiorcy."
@@ -6113,7 +6196,7 @@ msgstr "Prześlij CSV"
msgid "Upload custom document" msgid "Upload custom document"
msgstr "Prześlij niestandardowy dokument" msgstr "Prześlij niestandardowy dokument"
#: packages/ui/primitives/signature-pad/signature-pad.tsx #: packages/ui/primitives/signature-pad/signature-pad-upload.tsx
msgid "Upload Signature" msgid "Upload Signature"
msgstr "Prześlij podpis" msgstr "Prześlij podpis"
@@ -6287,6 +6370,7 @@ msgstr "Zobacz dokument"
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/general/document-signing/document-signing-form.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx #: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/email/template-components/template-document-rejected.tsx #: packages/email/template-components/template-document-rejected.tsx
#: packages/email/template-components/template-document-invite.tsx #: packages/email/template-components/template-document-invite.tsx
msgid "View Document" msgid "View Document"
@@ -6653,6 +6737,11 @@ msgstr "Witamy w Documenso!"
msgid "Were you trying to edit this document instead?" msgid "Were you trying to edit this document instead?"
msgstr "Czy próbowałeś raczej edytować ten dokument?" msgstr "Czy próbowałeś raczej edytować ten dokument?"
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
#: packages/ui/primitives/document-flow/add-signers.tsx
msgid "When enabled, signers can choose who should sign next in the sequence instead of following the predefined order."
msgstr ""
#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
msgid "When you click continue, you will be prompted to add the first available authenticator on your system." msgid "When you click continue, you will be prompted to add the first available authenticator on your system."
msgstr "Kiedy klikniesz kontynuuj, zostaniesz poproszony o dodanie pierwszego dostępnego autoryzatora w swoim systemie." msgstr "Kiedy klikniesz kontynuuj, zostaniesz poproszony o dodanie pierwszego dostępnego autoryzatora w swoim systemie."

View File

@@ -0,0 +1,53 @@
import { msg } from '@lingui/core/macro';
import { z } from 'zod';
import { DocumentMetaSchema } from '@documenso/prisma/generated/zod/modelSchema/DocumentMetaSchema';
/**
* The full document response schema.
*
* Mainly used for returning a single document from the API.
*/
export const ZDocumentMetaSchema = DocumentMetaSchema.pick({
signingOrder: true,
distributionMethod: true,
id: true,
subject: true,
message: true,
timezone: true,
password: true,
dateFormat: true,
documentId: true,
redirectUrl: true,
typedSignatureEnabled: true,
uploadSignatureEnabled: true,
drawSignatureEnabled: true,
language: true,
emailSettings: true,
});
export type TDocumentMeta = z.infer<typeof ZDocumentMetaSchema>;
/**
* If you update this, you must also update the schema.prisma @default value for
* - Template meta
* - Document meta
*/
export const ZDocumentSignatureSettingsSchema = z
.object({
typedSignatureEnabled: z.boolean(),
uploadSignatureEnabled: z.boolean(),
drawnSignatureEnabled: z.boolean(),
})
.refine(
(data) => {
return (
data.typedSignatureEnabled || data.uploadSignatureEnabled || data.drawnSignatureEnabled
);
},
{
message: msg`At least one signature type must be enabled`.id,
},
);
export type TDocumentSignatureSettings = z.infer<typeof ZDocumentSignatureSettingsSchema>;

View File

@@ -51,6 +51,9 @@ export const ZDocumentSchema = DocumentSchema.pick({
documentId: true, documentId: true,
redirectUrl: true, redirectUrl: true,
typedSignatureEnabled: true, typedSignatureEnabled: true,
uploadSignatureEnabled: true,
drawSignatureEnabled: true,
allowDictateNextSigner: true,
language: true, language: true,
emailSettings: true, emailSettings: true,
}).nullable(), }).nullable(),

View File

@@ -45,6 +45,9 @@ export const ZTemplateSchema = TemplateSchema.pick({
dateFormat: true, dateFormat: true,
signingOrder: true, signingOrder: true,
typedSignatureEnabled: true, typedSignatureEnabled: true,
uploadSignatureEnabled: true,
drawSignatureEnabled: true,
allowDictateNextSigner: true,
distributionMethod: true, distributionMethod: true,
templateId: true, templateId: true,
redirectUrl: true, redirectUrl: true,

View File

@@ -46,7 +46,10 @@ export const ZWebhookDocumentMetaSchema = z.object({
dateFormat: z.string(), dateFormat: z.string(),
redirectUrl: z.string().nullable(), redirectUrl: z.string().nullable(),
signingOrder: z.nativeEnum(DocumentSigningOrder), signingOrder: z.nativeEnum(DocumentSigningOrder),
allowDictateNextSigner: z.boolean(),
typedSignatureEnabled: z.boolean(), typedSignatureEnabled: z.boolean(),
uploadSignatureEnabled: z.boolean(),
drawSignatureEnabled: z.boolean(),
language: z.string(), language: z.string(),
distributionMethod: z.nativeEnum(DocumentDistributionMethod), distributionMethod: z.nativeEnum(DocumentDistributionMethod),
emailSettings: z.any().nullable(), emailSettings: z.any().nullable(),

View File

@@ -1,4 +1,5 @@
import { NEXT_PUBLIC_WEBAPP_URL } from '../constants/app'; import { NEXT_PUBLIC_WEBAPP_URL } from '../constants/app';
import { DocumentSignatureType } from '../constants/document';
import type { TEAM_MEMBER_ROLE_MAP } from '../constants/teams'; import type { TEAM_MEMBER_ROLE_MAP } from '../constants/teams';
import { TEAM_MEMBER_ROLE_HIERARCHY, TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '../constants/teams'; import { TEAM_MEMBER_ROLE_HIERARCHY, TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '../constants/teams';
@@ -44,3 +45,31 @@ export const isTeamRoleWithinUserHierarchy = (
) => { ) => {
return TEAM_MEMBER_ROLE_HIERARCHY[currentUserRole].some((i) => i === roleToCheck); return TEAM_MEMBER_ROLE_HIERARCHY[currentUserRole].some((i) => i === roleToCheck);
}; };
export const extractTeamSignatureSettings = (
settings?: {
typedSignatureEnabled: boolean;
drawSignatureEnabled: boolean;
uploadSignatureEnabled: boolean;
} | null,
) => {
if (!settings) {
return [DocumentSignatureType.TYPE, DocumentSignatureType.UPLOAD, DocumentSignatureType.DRAW];
}
const signatureTypes: DocumentSignatureType[] = [];
if (settings.typedSignatureEnabled) {
signatureTypes.push(DocumentSignatureType.TYPE);
}
if (settings.drawSignatureEnabled) {
signatureTypes.push(DocumentSignatureType.DRAW);
}
if (settings.uploadSignatureEnabled) {
signatureTypes.push(DocumentSignatureType.UPLOAD);
}
return signatureTypes;
};

View File

@@ -0,0 +1,11 @@
-- AlterTable
ALTER TABLE "DocumentMeta" ADD COLUMN "drawSignatureEnabled" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "uploadSignatureEnabled" BOOLEAN NOT NULL DEFAULT true;
-- AlterTable
ALTER TABLE "TeamGlobalSettings" ADD COLUMN "drawSignatureEnabled" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "uploadSignatureEnabled" BOOLEAN NOT NULL DEFAULT true;
-- AlterTable
ALTER TABLE "TemplateMeta" ADD COLUMN "drawSignatureEnabled" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "uploadSignatureEnabled" BOOLEAN NOT NULL DEFAULT true;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "DocumentMeta" ADD COLUMN "allowDictateNextSigner" BOOLEAN NOT NULL DEFAULT false;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "TemplateMeta" ADD COLUMN "allowDictateNextSigner" BOOLEAN NOT NULL DEFAULT false;

View File

@@ -0,0 +1,25 @@
import type { PrismaClient } from '@prisma/client';
export function addPrismaMiddleware(prisma: PrismaClient) {
prisma.$use(async (params, next) => {
// Check if we're creating a new team
if (params.model === 'Team' && params.action === 'create') {
// Execute the team creation
const result = await next(params);
// Create the TeamGlobalSettings
await prisma.teamGlobalSettings.create({
data: {
teamId: result.id,
},
});
return result;
}
// For all other operations, just pass through
return next(params);
});
return prisma;
}

View File

@@ -400,7 +400,12 @@ model DocumentMeta {
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade) document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
redirectUrl String? redirectUrl String?
signingOrder DocumentSigningOrder @default(PARALLEL) signingOrder DocumentSigningOrder @default(PARALLEL)
allowDictateNextSigner Boolean @default(false)
typedSignatureEnabled Boolean @default(true) typedSignatureEnabled Boolean @default(true)
uploadSignatureEnabled Boolean @default(true)
drawSignatureEnabled Boolean @default(true)
language String @default("en") language String @default("en")
distributionMethod DocumentDistributionMethod @default(EMAIL) distributionMethod DocumentDistributionMethod @default(EMAIL)
emailSettings Json? /// [DocumentEmailSettings] @zod.custom.use(ZDocumentEmailSettingsSchema) emailSettings Json? /// [DocumentEmailSettings] @zod.custom.use(ZDocumentEmailSettingsSchema)
@@ -544,9 +549,12 @@ model TeamGlobalSettings {
documentVisibility DocumentVisibility @default(EVERYONE) documentVisibility DocumentVisibility @default(EVERYONE)
documentLanguage String @default("en") documentLanguage String @default("en")
includeSenderDetails Boolean @default(true) includeSenderDetails Boolean @default(true)
typedSignatureEnabled Boolean @default(true)
includeSigningCertificate Boolean @default(true) includeSigningCertificate Boolean @default(true)
typedSignatureEnabled Boolean @default(true)
uploadSignatureEnabled Boolean @default(true)
drawSignatureEnabled Boolean @default(true)
brandingEnabled Boolean @default(false) brandingEnabled Boolean @default(false)
brandingLogo String @default("") brandingLogo String @default("")
brandingUrl String @default("") brandingUrl String @default("")
@@ -667,9 +675,13 @@ model TemplateMeta {
password String? password String?
dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text
signingOrder DocumentSigningOrder? @default(PARALLEL) signingOrder DocumentSigningOrder? @default(PARALLEL)
typedSignatureEnabled Boolean @default(true) allowDictateNextSigner Boolean @default(false)
distributionMethod DocumentDistributionMethod @default(EMAIL) distributionMethod DocumentDistributionMethod @default(EMAIL)
typedSignatureEnabled Boolean @default(true)
uploadSignatureEnabled Boolean @default(true)
drawSignatureEnabled Boolean @default(true)
templateId Int @unique templateId Int @unique
template Template @relation(fields: [templateId], references: [id], onDelete: Cascade) template Template @relation(fields: [templateId], references: [id], onDelete: Cascade)
redirectUrl String? redirectUrl String?

View File

@@ -1,9 +1,12 @@
import type { Document, User } from '@prisma/client'; import type { Document, Team, User } from '@prisma/client';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import fs from 'node:fs'; import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import { createDocument } from '@documenso/lib/server-only/document/create-document';
import { createTemplate } from '@documenso/lib/server-only/template/create-template';
import { prisma } from '..'; import { prisma } from '..';
import { import {
DocumentDataType, DocumentDataType,
@@ -87,6 +90,145 @@ export const unseedDocument = async (documentId: number) => {
}); });
}; };
export const seedTeamDocumentWithMeta = async (team: Team) => {
const documentData = await prisma.documentData.create({
data: {
type: DocumentDataType.BYTES_64,
data: examplePdf,
initialData: examplePdf,
},
});
const document = await createDocument({
userId: team.ownerUserId,
teamId: team.id,
title: `[TEST] Document ${nanoid(8)} - Draft`,
documentDataId: documentData.id,
normalizePdf: true,
requestMetadata: {
auth: null,
requestMetadata: {},
source: 'app',
},
});
const owner = await prisma.user.findFirstOrThrow({
where: {
id: team.ownerUserId,
},
});
await prisma.document.update({
where: {
id: document.id,
},
data: {
status: DocumentStatus.PENDING,
},
});
await prisma.recipient.create({
data: {
email: owner.email,
name: owner.name ?? '',
token: nanoid(),
readStatus: ReadStatus.OPENED,
sendStatus: SendStatus.SENT,
signingStatus: SigningStatus.NOT_SIGNED,
signedAt: new Date(),
document: {
connect: {
id: document.id,
},
},
fields: {
create: {
page: 1,
type: FieldType.SIGNATURE,
inserted: false,
customText: '',
positionX: new Prisma.Decimal(1),
positionY: new Prisma.Decimal(1),
width: new Prisma.Decimal(5),
height: new Prisma.Decimal(5),
documentId: document.id,
},
},
},
});
return await prisma.document.findFirstOrThrow({
where: {
id: document.id,
},
include: {
recipients: true,
},
});
};
export const seedTeamTemplateWithMeta = async (team: Team) => {
const documentData = await prisma.documentData.create({
data: {
type: DocumentDataType.BYTES_64,
data: examplePdf,
initialData: examplePdf,
},
});
const template = await createTemplate({
title: `[TEST] Template ${nanoid(8)} - Draft`,
userId: team.ownerUserId,
teamId: team.id,
templateDocumentDataId: documentData.id,
});
const owner = await prisma.user.findFirstOrThrow({
where: {
id: team.ownerUserId,
},
});
await prisma.recipient.create({
data: {
email: owner.email,
name: owner.name ?? '',
token: nanoid(),
readStatus: ReadStatus.OPENED,
sendStatus: SendStatus.SENT,
signingStatus: SigningStatus.NOT_SIGNED,
signedAt: new Date(),
template: {
connect: {
id: template.id,
},
},
fields: {
create: {
page: 1,
type: FieldType.SIGNATURE,
inserted: false,
customText: '',
positionX: new Prisma.Decimal(1),
positionY: new Prisma.Decimal(1),
width: new Prisma.Decimal(5),
height: new Prisma.Decimal(5),
templateId: template.id,
},
},
},
});
return await prisma.document.findFirstOrThrow({
where: {
id: template.id,
},
include: {
recipients: true,
},
});
};
export const seedDraftDocument = async ( export const seedDraftDocument = async (
sender: User, sender: User,
recipients: (User | string)[], recipients: (User | string)[],

View File

@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable @typescript-eslint/no-var-requires */
const { fontFamily } = require('tailwindcss/defaultTheme'); const { fontFamily } = require('tailwindcss/defaultTheme');
const { default: flattenColorPalette } = require('tailwindcss/lib/util/flattenColorPalette');
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
@@ -14,6 +15,9 @@ module.exports = {
zIndex: { zIndex: {
9999: '9999', 9999: '9999',
}, },
aspectRatio: {
'signature-pad': '16 / 7',
},
colors: { colors: {
border: 'hsl(var(--border))', border: 'hsl(var(--border))',
'field-border': 'hsl(var(--field-border))', 'field-border': 'hsl(var(--field-border))',
@@ -147,5 +151,17 @@ module.exports = {
require('tailwindcss-animate'), require('tailwindcss-animate'),
require('@tailwindcss/typography'), require('@tailwindcss/typography'),
require('@tailwindcss/container-queries'), require('@tailwindcss/container-queries'),
addVariablesForColors,
], ],
}; };
function addVariablesForColors({ addBase, theme }) {
let allColors = flattenColorPalette(theme('colors'));
let newVars = Object.fromEntries(
Object.entries(allColors).map(([key, val]) => [`--${key}`, val]),
);
addBase({
':root': newVars,
});
}

View File

@@ -56,9 +56,12 @@ import {
ZSearchDocumentsMutationSchema, ZSearchDocumentsMutationSchema,
ZSetSigningOrderForDocumentMutationSchema, ZSetSigningOrderForDocumentMutationSchema,
ZSuccessResponseSchema, ZSuccessResponseSchema,
} from './schema';
import { updateDocumentRoute } from './update-document';
import {
ZUpdateDocumentRequestSchema, ZUpdateDocumentRequestSchema,
ZUpdateDocumentResponseSchema, ZUpdateDocumentResponseSchema,
} from './schema'; } from './update-document.types';
export const documentRouter = router({ export const documentRouter = router({
/** /**
@@ -335,20 +338,12 @@ export const documentRouter = router({
}); });
}), }),
updateDocument: updateDocumentRoute,
/** /**
* @public * @deprecated Delete this after updateDocument endpoint is deployed
*
* Todo: Refactor to updateDocument.
*/ */
setSettingsForDocument: authenticatedProcedure setSettingsForDocument: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/document/update',
summary: 'Update document',
tags: ['Document'],
},
})
.input(ZUpdateDocumentRequestSchema) .input(ZUpdateDocumentRequestSchema)
.output(ZUpdateDocumentResponseSchema) .output(ZUpdateDocumentResponseSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@@ -368,9 +363,12 @@ export const documentRouter = router({
dateFormat: meta.dateFormat, dateFormat: meta.dateFormat,
language: meta.language, language: meta.language,
typedSignatureEnabled: meta.typedSignatureEnabled, typedSignatureEnabled: meta.typedSignatureEnabled,
uploadSignatureEnabled: meta.uploadSignatureEnabled,
drawSignatureEnabled: meta.drawSignatureEnabled,
redirectUrl: meta.redirectUrl, redirectUrl: meta.redirectUrl,
distributionMethod: meta.distributionMethod, distributionMethod: meta.distributionMethod,
signingOrder: meta.signingOrder, signingOrder: meta.signingOrder,
allowDictateNextSigner: meta.allowDictateNextSigner,
emailSettings: meta.emailSettings, emailSettings: meta.emailSettings,
requestMetadata: ctx.metadata, requestMetadata: ctx.metadata,
}); });

View File

@@ -109,6 +109,14 @@ export const ZDocumentMetaTypedSignatureEnabledSchema = z
.boolean() .boolean()
.describe('Whether to allow recipients to sign using a typed signature.'); .describe('Whether to allow recipients to sign using a typed signature.');
export const ZDocumentMetaDrawSignatureEnabledSchema = z
.boolean()
.describe('Whether to allow recipients to sign using a draw signature.');
export const ZDocumentMetaUploadSignatureEnabledSchema = z
.boolean()
.describe('Whether to allow recipients to sign using an uploaded signature.');
export const ZFindDocumentsRequestSchema = ZFindSearchParamsSchema.extend({ export const ZFindDocumentsRequestSchema = ZFindSearchParamsSchema.extend({
templateId: z templateId: z
.number() .number()
@@ -233,6 +241,8 @@ export const ZCreateDocumentV2RequestSchema = z.object({
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(), redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
language: ZDocumentMetaLanguageSchema.optional(), language: ZDocumentMetaLanguageSchema.optional(),
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(), typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
emailSettings: ZDocumentEmailSettingsSchema.optional(), emailSettings: ZDocumentEmailSettingsSchema.optional(),
}) })
.optional(), .optional(),
@@ -249,35 +259,6 @@ export const ZCreateDocumentV2ResponseSchema = z.object({
), ),
}); });
export const ZUpdateDocumentRequestSchema = z.object({
documentId: z.number(),
data: z
.object({
title: ZDocumentTitleSchema.optional(),
externalId: ZDocumentExternalIdSchema.nullish(),
visibility: ZDocumentVisibilitySchema.optional(),
globalAccessAuth: ZDocumentAccessAuthTypesSchema.nullish(),
globalActionAuth: ZDocumentActionAuthTypesSchema.nullish(),
})
.optional(),
meta: z
.object({
subject: ZDocumentMetaSubjectSchema.optional(),
message: ZDocumentMetaMessageSchema.optional(),
timezone: ZDocumentMetaTimezoneSchema.optional(),
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
language: ZDocumentMetaLanguageSchema.optional(),
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
emailSettings: ZDocumentEmailSettingsSchema.optional(),
})
.optional(),
});
export const ZUpdateDocumentResponseSchema = ZDocumentLiteSchema;
export const ZSetFieldsForDocumentMutationSchema = z.object({ export const ZSetFieldsForDocumentMutationSchema = z.object({
documentId: z.number(), documentId: z.number(),
fields: z.array( fields: z.array(

View File

@@ -0,0 +1,52 @@
import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
import { updateDocument } from '@documenso/lib/server-only/document/update-document';
import { authenticatedProcedure } from '../trpc';
import {
ZUpdateDocumentRequestSchema,
ZUpdateDocumentResponseSchema,
} from './update-document.types';
import { updateDocumentMeta } from './update-document.types';
/**
* Public route.
*/
export const updateDocumentRoute = authenticatedProcedure
.meta(updateDocumentMeta)
.input(ZUpdateDocumentRequestSchema)
.output(ZUpdateDocumentResponseSchema)
.mutation(async ({ input, ctx }) => {
const { teamId } = ctx;
const { documentId, data, meta = {} } = input;
const userId = ctx.user.id;
if (Object.values(meta).length > 0) {
await upsertDocumentMeta({
userId: ctx.user.id,
teamId,
documentId,
subject: meta.subject,
message: meta.message,
timezone: meta.timezone,
dateFormat: meta.dateFormat,
language: meta.language,
typedSignatureEnabled: meta.typedSignatureEnabled,
uploadSignatureEnabled: meta.uploadSignatureEnabled,
drawSignatureEnabled: meta.drawSignatureEnabled,
redirectUrl: meta.redirectUrl,
distributionMethod: meta.distributionMethod,
signingOrder: meta.signingOrder,
emailSettings: meta.emailSettings,
requestMetadata: ctx.metadata,
});
}
return await updateDocument({
userId,
teamId,
documentId,
data,
requestMetadata: ctx.metadata,
});
});

View File

@@ -0,0 +1,68 @@
import { DocumentSigningOrder } from '@prisma/client';
// import type { OpenApiMeta } from 'trpc-to-openapi';
import { z } from 'zod';
import { ZDocumentLiteSchema } from '@documenso/lib/types/document';
import {
ZDocumentAccessAuthTypesSchema,
ZDocumentActionAuthTypesSchema,
} from '@documenso/lib/types/document-auth';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import type { TrpcRouteMeta } from '../trpc';
import {
ZDocumentExternalIdSchema,
ZDocumentMetaDateFormatSchema,
ZDocumentMetaDistributionMethodSchema,
ZDocumentMetaDrawSignatureEnabledSchema,
ZDocumentMetaLanguageSchema,
ZDocumentMetaMessageSchema,
ZDocumentMetaRedirectUrlSchema,
ZDocumentMetaSubjectSchema,
ZDocumentMetaTimezoneSchema,
ZDocumentMetaTypedSignatureEnabledSchema,
ZDocumentMetaUploadSignatureEnabledSchema,
ZDocumentTitleSchema,
ZDocumentVisibilitySchema,
} from './schema';
export const updateDocumentMeta: TrpcRouteMeta = {
openapi: {
method: 'POST',
path: '/document/update',
summary: 'Update document',
tags: ['Document'],
},
};
export const ZUpdateDocumentRequestSchema = z.object({
documentId: z.number(),
data: z
.object({
title: ZDocumentTitleSchema.optional(),
externalId: ZDocumentExternalIdSchema.nullish(),
visibility: ZDocumentVisibilitySchema.optional(),
globalAccessAuth: ZDocumentAccessAuthTypesSchema.nullish(),
globalActionAuth: ZDocumentActionAuthTypesSchema.nullish(),
})
.optional(),
meta: z
.object({
subject: ZDocumentMetaSubjectSchema.optional(),
message: ZDocumentMetaMessageSchema.optional(),
timezone: ZDocumentMetaTimezoneSchema.optional(),
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
allowDictateNextSigner: z.boolean().optional(),
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
language: ZDocumentMetaLanguageSchema.optional(),
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
emailSettings: ZDocumentEmailSettingsSchema.optional(),
})
.optional(),
});
export const ZUpdateDocumentResponseSchema = ZDocumentLiteSchema;

View File

@@ -436,12 +436,13 @@ export const recipientRouter = router({
completeDocumentWithToken: procedure completeDocumentWithToken: procedure
.input(ZCompleteDocumentWithTokenMutationSchema) .input(ZCompleteDocumentWithTokenMutationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { token, documentId, authOptions } = input; const { token, documentId, authOptions, nextSigner } = input;
return await completeDocumentWithToken({ return await completeDocumentWithToken({
token, token,
documentId, documentId,
authOptions, authOptions,
nextSigner,
userId: ctx.user?.id, userId: ctx.user?.id,
requestMetadata: ctx.metadata.requestMetadata, requestMetadata: ctx.metadata.requestMetadata,
}); });

View File

@@ -212,6 +212,12 @@ export const ZCompleteDocumentWithTokenMutationSchema = z.object({
token: z.string(), token: z.string(),
documentId: z.number(), documentId: z.number(),
authOptions: ZRecipientActionAuthSchema.optional(), authOptions: ZRecipientActionAuthSchema.optional(),
nextSigner: z
.object({
email: z.string().email(),
name: z.string().min(1),
})
.optional(),
}); });
export type TCompleteDocumentWithTokenMutationSchema = z.infer< export type TCompleteDocumentWithTokenMutationSchema = z.infer<

View File

@@ -33,7 +33,6 @@ import { resendTeamEmailVerification } from '@documenso/lib/server-only/team/res
import { resendTeamMemberInvitation } from '@documenso/lib/server-only/team/resend-team-member-invitation'; import { resendTeamMemberInvitation } from '@documenso/lib/server-only/team/resend-team-member-invitation';
import { updateTeam } from '@documenso/lib/server-only/team/update-team'; import { updateTeam } from '@documenso/lib/server-only/team/update-team';
import { updateTeamBrandingSettings } from '@documenso/lib/server-only/team/update-team-branding-settings'; import { updateTeamBrandingSettings } from '@documenso/lib/server-only/team/update-team-branding-settings';
import { updateTeamDocumentSettings } from '@documenso/lib/server-only/team/update-team-document-settings';
import { updateTeamEmail } from '@documenso/lib/server-only/team/update-team-email'; import { updateTeamEmail } from '@documenso/lib/server-only/team/update-team-email';
import { updateTeamMember } from '@documenso/lib/server-only/team/update-team-member'; import { updateTeamMember } from '@documenso/lib/server-only/team/update-team-member';
import { updateTeamPublicProfile } from '@documenso/lib/server-only/team/update-team-public-profile'; import { updateTeamPublicProfile } from '@documenso/lib/server-only/team/update-team-public-profile';
@@ -66,12 +65,12 @@ import {
ZResendTeamEmailVerificationMutationSchema, ZResendTeamEmailVerificationMutationSchema,
ZResendTeamMemberInvitationMutationSchema, ZResendTeamMemberInvitationMutationSchema,
ZUpdateTeamBrandingSettingsMutationSchema, ZUpdateTeamBrandingSettingsMutationSchema,
ZUpdateTeamDocumentSettingsMutationSchema,
ZUpdateTeamEmailMutationSchema, ZUpdateTeamEmailMutationSchema,
ZUpdateTeamMemberMutationSchema, ZUpdateTeamMemberMutationSchema,
ZUpdateTeamMutationSchema, ZUpdateTeamMutationSchema,
ZUpdateTeamPublicProfileMutationSchema, ZUpdateTeamPublicProfileMutationSchema,
} from './schema'; } from './schema';
import { updateTeamDocumentSettingsRoute } from './update-team-document-settings';
export const teamRouter = router({ export const teamRouter = router({
// Internal endpoint for now. // Internal endpoint for now.
@@ -571,18 +570,7 @@ export const teamRouter = router({
return await getTeamPrices(); return await getTeamPrices();
}), }),
// Internal endpoint. Use updateTeam instead. updateTeamDocumentSettings: updateTeamDocumentSettingsRoute,
updateTeamDocumentSettings: authenticatedProcedure
.input(ZUpdateTeamDocumentSettingsMutationSchema)
.mutation(async ({ ctx, input }) => {
const { teamId, settings } = input;
return await updateTeamDocumentSettings({
userId: ctx.user.id,
teamId,
settings,
});
}),
// Internal endpoint for now. // Internal endpoint for now.
acceptTeamInvitation: authenticatedProcedure acceptTeamInvitation: authenticatedProcedure

View File

@@ -1,7 +1,6 @@
import { DocumentVisibility, TeamMemberRole } from '@prisma/client'; import { TeamMemberRole } from '@prisma/client';
import { z } from 'zod'; import { z } from 'zod';
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
import { PROTECTED_TEAM_URLS } from '@documenso/lib/constants/teams'; import { PROTECTED_TEAM_URLS } from '@documenso/lib/constants/teams';
import { ZFindSearchParamsSchema } from '@documenso/lib/types/search-params'; import { ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
@@ -195,20 +194,6 @@ export const ZUpdateTeamBrandingSettingsMutationSchema = z.object({
}), }),
}); });
export const ZUpdateTeamDocumentSettingsMutationSchema = z.object({
teamId: z.number(),
settings: z.object({
documentVisibility: z
.nativeEnum(DocumentVisibility)
.optional()
.default(DocumentVisibility.EVERYONE),
documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES).optional().default('en'),
includeSenderDetails: z.boolean().optional().default(false),
typedSignatureEnabled: z.boolean().optional().default(true),
includeSigningCertificate: z.boolean().optional().default(true),
}),
});
export type TCreateTeamMutationSchema = z.infer<typeof ZCreateTeamMutationSchema>; export type TCreateTeamMutationSchema = z.infer<typeof ZCreateTeamMutationSchema>;
export type TCreateTeamEmailVerificationMutationSchema = z.infer< export type TCreateTeamEmailVerificationMutationSchema = z.infer<
typeof ZCreateTeamEmailVerificationMutationSchema typeof ZCreateTeamEmailVerificationMutationSchema
@@ -247,6 +232,3 @@ export type TResendTeamMemberInvitationMutationSchema = z.infer<
export type TUpdateTeamBrandingSettingsMutationSchema = z.infer< export type TUpdateTeamBrandingSettingsMutationSchema = z.infer<
typeof ZUpdateTeamBrandingSettingsMutationSchema typeof ZUpdateTeamBrandingSettingsMutationSchema
>; >;
export type TUpdateTeamDocumentSettingsMutationSchema = z.infer<
typeof ZUpdateTeamDocumentSettingsMutationSchema
>;

View File

@@ -0,0 +1,71 @@
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 { authenticatedProcedure } from '../trpc';
import {
ZUpdateTeamDocumentSettingsRequestSchema,
ZUpdateTeamDocumentSettingsResponseSchema,
} from './update-team-document-settings.types';
/**
* Private route.
*/
export const updateTeamDocumentSettingsRoute = authenticatedProcedure
.input(ZUpdateTeamDocumentSettingsRequestSchema)
.output(ZUpdateTeamDocumentSettingsResponseSchema)
.mutation(async ({ ctx, input }) => {
const { user } = ctx;
const { teamId, settings } = input;
const {
documentVisibility,
documentLanguage,
includeSenderDetails,
includeSigningCertificate,
typedSignatureEnabled,
uploadSignatureEnabled,
drawSignatureEnabled,
} = settings;
const member = await prisma.teamMember.findFirst({
where: {
userId: user.id,
teamId,
role: {
in: TEAM_MEMBER_ROLE_PERMISSIONS_MAP.MANAGE_TEAM,
},
},
});
if (!member) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'You do not have permission to update this team.',
});
}
return await prisma.teamGlobalSettings.upsert({
where: {
teamId,
},
create: {
teamId,
documentVisibility,
documentLanguage,
includeSenderDetails,
includeSigningCertificate,
typedSignatureEnabled,
uploadSignatureEnabled,
drawSignatureEnabled,
},
update: {
documentVisibility,
documentLanguage,
includeSenderDetails,
includeSigningCertificate,
typedSignatureEnabled,
uploadSignatureEnabled,
drawSignatureEnabled,
},
});
});

View File

@@ -0,0 +1,23 @@
import { z } from 'zod';
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
import TeamGlobalSettingsSchema from '@documenso/prisma/generated/zod/modelSchema/TeamGlobalSettingsSchema';
export const ZUpdateTeamDocumentSettingsRequestSchema = z.object({
teamId: z.number(),
settings: z.object({
documentVisibility: z
.nativeEnum(DocumentVisibility)
.optional()
.default(DocumentVisibility.EVERYONE),
documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES).optional().default('en'),
includeSenderDetails: z.boolean().optional().default(false),
includeSigningCertificate: z.boolean().optional().default(true),
typedSignatureEnabled: z.boolean().optional().default(true),
uploadSignatureEnabled: z.boolean().optional().default(true),
drawSignatureEnabled: z.boolean().optional().default(true),
}),
});
export const ZUpdateTeamDocumentSettingsResponseSchema = TeamGlobalSettingsSchema;

View File

@@ -19,12 +19,14 @@ import { TemplateDirectLinkSchema } from '@documenso/prisma/generated/zod/modelS
import { import {
ZDocumentMetaDateFormatSchema, ZDocumentMetaDateFormatSchema,
ZDocumentMetaDistributionMethodSchema, ZDocumentMetaDistributionMethodSchema,
ZDocumentMetaDrawSignatureEnabledSchema,
ZDocumentMetaLanguageSchema, ZDocumentMetaLanguageSchema,
ZDocumentMetaMessageSchema, ZDocumentMetaMessageSchema,
ZDocumentMetaRedirectUrlSchema, ZDocumentMetaRedirectUrlSchema,
ZDocumentMetaSubjectSchema, ZDocumentMetaSubjectSchema,
ZDocumentMetaTimezoneSchema, ZDocumentMetaTimezoneSchema,
ZDocumentMetaTypedSignatureEnabledSchema, ZDocumentMetaTypedSignatureEnabledSchema,
ZDocumentMetaUploadSignatureEnabledSchema,
} from '../document-router/schema'; } from '../document-router/schema';
import { ZSignFieldWithTokenMutationSchema } from '../field-router/schema'; import { ZSignFieldWithTokenMutationSchema } from '../field-router/schema';
@@ -164,7 +166,10 @@ export const ZUpdateTemplateRequestSchema = z.object({
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(), redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
language: ZDocumentMetaLanguageSchema.optional(), language: ZDocumentMetaLanguageSchema.optional(),
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(), typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(), signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
allowDictateNextSigner: z.boolean().optional(),
}) })
.optional(), .optional(),
}); });

View File

@@ -9,8 +9,8 @@ import { isAdmin } from '@documenso/lib/utils/is-admin';
import type { TrpcContext } from './context'; import type { TrpcContext } from './context';
// Can't import type from trpc-to-openapi because it breaks nextjs build, not sure why. // Can't import type from trpc-to-openapi because it breaks build, not sure why.
type OpenApiMeta = { export type TrpcRouteMeta = {
openapi?: { openapi?: {
enabled?: boolean; enabled?: boolean;
method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE'; method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
@@ -30,7 +30,7 @@ type OpenApiMeta = {
} & Record<string, unknown>; } & Record<string, unknown>;
const t = initTRPC const t = initTRPC
.meta<OpenApiMeta>() .meta<TrpcRouteMeta>()
.context<TrpcContext>() .context<TrpcContext>()
.create({ .create({
transformer: SuperJSON, transformer: SuperJSON,

View File

@@ -0,0 +1,58 @@
import { Trans } from '@lingui/react/macro';
import { InfoIcon } from 'lucide-react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
export const DocumentSignatureSettingsTooltip = () => {
return (
<Tooltip>
<TooltipTrigger>
<InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="text-foreground max-w-md space-y-2 p-4">
<h2>
<strong>
<Trans>Signature types</Trans>
</strong>
</h2>
<p>
<Trans>
The types of signatures that recipients are allowed to use when signing the document.
</Trans>
</p>
<ul className="ml-3.5 list-outside list-disc space-y-0.5 py-2">
<li>
<Trans>
<strong>
<Trans>Drawn</Trans>
</strong>
{' - '}
<Trans>A signature that is drawn using a mouse or stylus.</Trans>
</Trans>
</li>
<li>
<Trans>
<strong>
<Trans>Typed</Trans>
</strong>
{' - '}
<Trans>A signature that is typed using a keyboard.</Trans>
</Trans>
</li>
<li>
<Trans>
<strong>
<Trans>Uploaded</Trans>
</strong>
{' - '}
<Trans>A signature that is uploaded from a file.</Trans>
</Trans>
</li>
</ul>
</TooltipContent>
</Tooltip>
);
};

View File

@@ -47,9 +47,8 @@ import { cn } from '../../lib/utils';
import { Alert, AlertDescription } from '../alert'; import { Alert, AlertDescription } from '../alert';
import { Button } from '../button'; import { Button } from '../button';
import { Card, CardContent } from '../card'; import { Card, CardContent } from '../card';
import { Checkbox } from '../checkbox';
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from '../command'; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from '../command';
import { Form, FormControl, FormField, FormItem, FormLabel } from '../form/form'; import { Form } from '../form/form';
import { Popover, PopoverContent, PopoverTrigger } from '../popover'; import { Popover, PopoverContent, PopoverTrigger } from '../popover';
import { useStep } from '../stepper'; import { useStep } from '../stepper';
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip';
@@ -94,7 +93,6 @@ export type AddFieldsFormProps = {
onSubmit: (_data: TAddFieldsFormSchema) => void; onSubmit: (_data: TAddFieldsFormSchema) => void;
canGoBack?: boolean; canGoBack?: boolean;
isDocumentPdfLoaded: boolean; isDocumentPdfLoaded: boolean;
typedSignatureEnabled?: boolean;
teamId?: number; teamId?: number;
}; };
@@ -106,7 +104,6 @@ export const AddFieldsFormPartial = ({
onSubmit, onSubmit,
canGoBack = false, canGoBack = false,
isDocumentPdfLoaded, isDocumentPdfLoaded,
typedSignatureEnabled,
teamId, teamId,
}: AddFieldsFormProps) => { }: AddFieldsFormProps) => {
const { toast } = useToast(); const { toast } = useToast();
@@ -137,7 +134,6 @@ export const AddFieldsFormPartial = ({
recipients.find((recipient) => recipient.id === field.recipientId)?.email ?? '', recipients.find((recipient) => recipient.id === field.recipientId)?.email ?? '',
fieldMeta: field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : undefined, fieldMeta: field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : undefined,
})), })),
typedSignatureEnabled: typedSignatureEnabled ?? false,
}, },
}); });
@@ -787,31 +783,6 @@ export const AddFieldsFormPartial = ({
)} )}
<Form {...form}> <Form {...form}>
<FormField
control={form.control}
name="typedSignatureEnabled"
render={({ field: { value, ...field } }) => (
<FormItem className="mb-6 flex flex-row items-center space-x-2 space-y-0">
<FormControl>
<Checkbox
{...field}
id="typedSignatureEnabled"
checked={value}
onCheckedChange={(checked) => field.onChange(checked)}
disabled={form.formState.isSubmitting}
/>
</FormControl>
<FormLabel
htmlFor="typedSignatureEnabled"
className="text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
<Trans>Enable Typed Signatures</Trans>
</FormLabel>
</FormItem>
)}
/>
<div className="-mx-2 flex-1 overflow-y-auto px-2"> <div className="-mx-2 flex-1 overflow-y-auto px-2">
<fieldset disabled={isFieldsDisabled} className="my-2 grid grid-cols-3 gap-4"> <fieldset disabled={isFieldsDisabled} className="my-2 grid grid-cols-3 gap-4">
<button <button

View File

@@ -18,7 +18,6 @@ export const ZAddFieldsFormSchema = z.object({
fieldMeta: ZFieldMetaSchema, fieldMeta: ZFieldMetaSchema,
}), }),
), ),
typedSignatureEnabled: z.boolean(),
}); });
export type TAddFieldsFormSchema = z.infer<typeof ZAddFieldsFormSchema>; export type TAddFieldsFormSchema = z.infer<typeof ZAddFieldsFormSchema>;

View File

@@ -2,6 +2,7 @@ import { useEffect } from 'react';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { Trans } from '@lingui/react/macro'; import { Trans } from '@lingui/react/macro';
import { useLingui } from '@lingui/react/macro';
import { DocumentVisibility, TeamMemberRole } from '@prisma/client'; import { DocumentVisibility, TeamMemberRole } from '@prisma/client';
import { DocumentStatus, type Field, type Recipient, SendStatus } from '@prisma/client'; import { DocumentStatus, type Field, type Recipient, SendStatus } from '@prisma/client';
import { InfoIcon } from 'lucide-react'; import { InfoIcon } from 'lucide-react';
@@ -9,10 +10,12 @@ import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats'; import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
import { DOCUMENT_SIGNATURE_TYPES } from '@documenso/lib/constants/document';
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n'; import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones'; import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
import type { TDocument } from '@documenso/lib/types/document'; import type { TDocument } from '@documenso/lib/types/document';
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth'; import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
import { extractTeamSignatureSettings } from '@documenso/lib/utils/teams';
import { import {
DocumentGlobalAuthAccessSelect, DocumentGlobalAuthAccessSelect,
DocumentGlobalAuthAccessTooltip, DocumentGlobalAuthAccessTooltip,
@@ -39,7 +42,9 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from '@documenso/ui/primitives/form/form'; } from '@documenso/ui/primitives/form/form';
import { MultiSelectCombobox } from '@documenso/ui/primitives/multi-select-combobox';
import { DocumentSignatureSettingsTooltip } from '../../components/document/document-signature-settings-tooltip';
import { Combobox } from '../combobox'; import { Combobox } from '../combobox';
import { Input } from '../input'; import { Input } from '../input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../select';
@@ -78,6 +83,8 @@ export const AddSettingsFormPartial = ({
currentTeamMemberRole, currentTeamMemberRole,
onSubmit, onSubmit,
}: AddSettingsFormProps) => { }: AddSettingsFormProps) => {
const { t } = useLingui();
const { documentAuthOption } = extractDocumentAuthMethods({ const { documentAuthOption } = extractDocumentAuthMethods({
documentAuth: document.authOptions, documentAuth: document.authOptions,
}); });
@@ -90,6 +97,7 @@ export const AddSettingsFormPartial = ({
visibility: document.visibility || '', visibility: document.visibility || '',
globalAccessAuth: documentAuthOption?.globalAccessAuth || undefined, globalAccessAuth: documentAuthOption?.globalAccessAuth || undefined,
globalActionAuth: documentAuthOption?.globalActionAuth || undefined, globalActionAuth: documentAuthOption?.globalActionAuth || undefined,
meta: { meta: {
timezone: timezone:
TIME_ZONES.find((timezone) => timezone === document.documentMeta?.timezone) ?? TIME_ZONES.find((timezone) => timezone === document.documentMeta?.timezone) ??
@@ -99,6 +107,7 @@ export const AddSettingsFormPartial = ({
?.value ?? DEFAULT_DOCUMENT_DATE_FORMAT, ?.value ?? DEFAULT_DOCUMENT_DATE_FORMAT,
redirectUrl: document.documentMeta?.redirectUrl ?? '', redirectUrl: document.documentMeta?.redirectUrl ?? '',
language: document.documentMeta?.language ?? 'en', language: document.documentMeta?.language ?? 'en',
signatureTypes: extractTeamSignatureSettings(document.documentMeta),
}, },
}, },
}); });
@@ -189,9 +198,11 @@ export const AddSettingsFormPartial = ({
</TooltipTrigger> </TooltipTrigger>
<TooltipContent className="text-foreground max-w-md space-y-2 p-4"> <TooltipContent className="text-foreground max-w-md space-y-2 p-4">
<Trans>
Controls the language for the document, including the language to be used Controls the language for the document, including the language to be used
for email notifications, and the final certificate that is generated and for email notifications, and the final certificate that is generated and
attached to the document. attached to the document.
</Trans>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</FormLabel> </FormLabel>
@@ -314,6 +325,34 @@ export const AddSettingsFormPartial = ({
)} )}
/> />
<FormField
control={form.control}
name="meta.signatureTypes"
render={({ field }) => (
<FormItem>
<FormLabel className="flex flex-row items-center">
<Trans>Allowed Signature Types</Trans>
<DocumentSignatureSettingsTooltip />
</FormLabel>
<FormControl>
<MultiSelectCombobox
options={Object.values(DOCUMENT_SIGNATURE_TYPES).map((option) => ({
label: t(option.label),
value: option.value,
}))}
selectedValues={field.value}
onChange={field.onChange}
className="bg-background w-full"
emptySelectionPlaceholder="Select signature types"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField <FormField
control={form.control} control={form.control}
name="meta.dateFormat" name="meta.dateFormat"

View File

@@ -1,7 +1,9 @@
import { msg } from '@lingui/core/macro';
import { DocumentVisibility } from '@prisma/client'; import { DocumentVisibility } from '@prisma/client';
import { z } from 'zod'; import { z } from 'zod';
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats'; import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
import { DocumentSignatureType } from '@documenso/lib/constants/document';
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n'; import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones'; import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
import { import {
@@ -26,7 +28,10 @@ export const ZMapNegativeOneToUndefinedSchema = z
}); });
export const ZAddSettingsFormSchema = z.object({ export const ZAddSettingsFormSchema = z.object({
title: z.string().trim().min(1, { message: "Title can't be empty" }), title: z
.string()
.trim()
.min(1, { message: msg`Title cannot be empty`.id }),
externalId: z.string().optional(), externalId: z.string().optional(),
visibility: z.nativeEnum(DocumentVisibility).optional(), visibility: z.nativeEnum(DocumentVisibility).optional(),
globalAccessAuth: ZMapNegativeOneToUndefinedSchema.pipe( globalAccessAuth: ZMapNegativeOneToUndefinedSchema.pipe(
@@ -49,6 +54,9 @@ export const ZAddSettingsFormSchema = z.object({
.union([z.string(), z.enum(SUPPORTED_LANGUAGE_CODES)]) .union([z.string(), z.enum(SUPPORTED_LANGUAGE_CODES)])
.optional() .optional()
.default('en'), .default('en'),
signatureTypes: z.array(z.nativeEnum(DocumentSignatureType)).min(1, {
message: msg`At least one signature type must be enabled`.id,
}),
}), }),
}); });

View File

@@ -9,7 +9,7 @@ import { Trans } from '@lingui/react/macro';
import type { Field, Recipient } from '@prisma/client'; import type { Field, Recipient } from '@prisma/client';
import { DocumentSigningOrder, RecipientRole, SendStatus } from '@prisma/client'; import { DocumentSigningOrder, RecipientRole, SendStatus } from '@prisma/client';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { GripVerticalIcon, Plus, Trash } from 'lucide-react'; import { GripVerticalIcon, HelpCircle, Plus, Trash } from 'lucide-react';
import { useFieldArray, useForm } from 'react-hook-form'; import { useFieldArray, useForm } from 'react-hook-form';
import { prop, sortBy } from 'remeda'; import { prop, sortBy } from 'remeda';
@@ -29,6 +29,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '
import { FormErrorMessage } from '../form/form-error-message'; import { FormErrorMessage } from '../form/form-error-message';
import { Input } from '../input'; import { Input } from '../input';
import { useStep } from '../stepper'; import { useStep } from '../stepper';
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip';
import { useToast } from '../use-toast'; import { useToast } from '../use-toast';
import type { TAddSignersFormSchema } from './add-signers.types'; import type { TAddSignersFormSchema } from './add-signers.types';
import { ZAddSignersFormSchema } from './add-signers.types'; import { ZAddSignersFormSchema } from './add-signers.types';
@@ -48,6 +49,7 @@ export type AddSignersFormProps = {
recipients: Recipient[]; recipients: Recipient[];
fields: Field[]; fields: Field[];
signingOrder?: DocumentSigningOrder | null; signingOrder?: DocumentSigningOrder | null;
allowDictateNextSigner?: boolean;
isDocumentEnterprise: boolean; isDocumentEnterprise: boolean;
onSubmit: (_data: TAddSignersFormSchema) => void; onSubmit: (_data: TAddSignersFormSchema) => void;
isDocumentPdfLoaded: boolean; isDocumentPdfLoaded: boolean;
@@ -58,6 +60,7 @@ export const AddSignersFormPartial = ({
recipients, recipients,
fields, fields,
signingOrder, signingOrder,
allowDictateNextSigner,
isDocumentEnterprise, isDocumentEnterprise,
onSubmit, onSubmit,
isDocumentPdfLoaded, isDocumentPdfLoaded,
@@ -104,6 +107,7 @@ export const AddSignersFormPartial = ({
) )
: defaultRecipients, : defaultRecipients,
signingOrder: signingOrder || DocumentSigningOrder.PARALLEL, signingOrder: signingOrder || DocumentSigningOrder.PARALLEL,
allowDictateNextSigner: allowDictateNextSigner ?? false,
}, },
}); });
@@ -354,6 +358,7 @@ export const AddSignersFormPartial = ({
form.setValue('signers', updatedSigners); form.setValue('signers', updatedSigners);
form.setValue('signingOrder', DocumentSigningOrder.PARALLEL); form.setValue('signingOrder', DocumentSigningOrder.PARALLEL);
form.setValue('allowDictateNextSigner', false);
}, [form]); }, [form]);
return ( return (
@@ -389,6 +394,11 @@ export const AddSignersFormPartial = ({
field.onChange( field.onChange(
checked ? DocumentSigningOrder.SEQUENTIAL : DocumentSigningOrder.PARALLEL, checked ? DocumentSigningOrder.SEQUENTIAL : DocumentSigningOrder.PARALLEL,
); );
// If sequential signing is turned off, disable dictate next signer
if (!checked) {
form.setValue('allowDictateNextSigner', false);
}
}} }}
disabled={isSubmitting || hasDocumentBeenSent} disabled={isSubmitting || hasDocumentBeenSent}
/> />
@@ -403,6 +413,50 @@ export const AddSignersFormPartial = ({
</FormItem> </FormItem>
)} )}
/> />
<FormField
control={form.control}
name="allowDictateNextSigner"
render={({ field: { value, ...field } }) => (
<FormItem className="mb-6 flex flex-row items-center space-x-2 space-y-0">
<FormControl>
<Checkbox
{...field}
id="allowDictateNextSigner"
checked={value}
onCheckedChange={field.onChange}
disabled={isSubmitting || hasDocumentBeenSent || !isSigningOrderSequential}
/>
</FormControl>
<div className="flex items-center">
<FormLabel
htmlFor="allowDictateNextSigner"
className="text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
<Trans>Allow signers to dictate next signer</Trans>
</FormLabel>
<Tooltip>
<TooltipTrigger asChild>
<span className="text-muted-foreground ml-1 cursor-help">
<HelpCircle className="h-3.5 w-3.5" />
</span>
</TooltipTrigger>
<TooltipContent className="max-w-80 p-4">
<p>
<Trans>
When enabled, signers can choose who should sign next in the sequence
instead of following the predefined order.
</Trans>
</p>
</TooltipContent>
</Tooltip>
</div>
</FormItem>
)}
/>
<DragDropContext <DragDropContext
onDragEnd={onDragEnd} onDragEnd={onDragEnd}
sensors={[ sensors={[

View File

@@ -25,6 +25,7 @@ export const ZAddSignersFormSchema = z
}), }),
), ),
signingOrder: z.nativeEnum(DocumentSigningOrder), signingOrder: z.nativeEnum(DocumentSigningOrder),
allowDictateNextSigner: z.boolean().default(false),
}) })
.refine( .refine(
(schema) => { (schema) => {

View File

@@ -24,11 +24,14 @@ type ComboBoxOption<T = OptionValue> = {
type MultiSelectComboboxProps<T = OptionValue> = { type MultiSelectComboboxProps<T = OptionValue> = {
emptySelectionPlaceholder?: React.ReactElement | string; emptySelectionPlaceholder?: React.ReactElement | string;
enableClearAllButton?: boolean; enableClearAllButton?: boolean;
enableSearch?: boolean;
className?: string;
loading?: boolean; loading?: boolean;
inputPlaceholder?: MessageDescriptor; inputPlaceholder?: MessageDescriptor;
onChange: (_values: T[]) => void; onChange: (_values: T[]) => void;
options: ComboBoxOption<T>[]; options: ComboBoxOption<T>[];
selectedValues: T[]; selectedValues: T[];
testId?: string;
}; };
/** /**
@@ -41,11 +44,14 @@ type MultiSelectComboboxProps<T = OptionValue> = {
export function MultiSelectCombobox<T = OptionValue>({ export function MultiSelectCombobox<T = OptionValue>({
emptySelectionPlaceholder = 'Select values...', emptySelectionPlaceholder = 'Select values...',
enableClearAllButton, enableClearAllButton,
enableSearch = true,
className,
inputPlaceholder, inputPlaceholder,
loading, loading,
onChange, onChange,
options, options,
selectedValues, selectedValues,
testId,
}: MultiSelectComboboxProps<T>) { }: MultiSelectComboboxProps<T>) {
const { _ } = useLingui(); const { _ } = useLingui();
@@ -59,8 +65,6 @@ export function MultiSelectCombobox<T = OptionValue>({
} }
onChange(newSelectedOptions); onChange(newSelectedOptions);
setOpen(false);
}; };
const selectedOptions = React.useMemo(() => { const selectedOptions = React.useMemo(() => {
@@ -107,7 +111,8 @@ export function MultiSelectCombobox<T = OptionValue>({
role="combobox" role="combobox"
disabled={loading} disabled={loading}
aria-expanded={open} aria-expanded={open}
className="w-[200px] px-3" className={cn('w-[200px] px-3', className)}
data-testid={testId}
> >
<AnimatePresence> <AnimatePresence>
{loading ? ( {loading ? (
@@ -146,7 +151,7 @@ export function MultiSelectCombobox<T = OptionValue>({
<PopoverContent className="w-[200px] p-0"> <PopoverContent className="w-[200px] p-0">
<Command> <Command>
<CommandInput placeholder={inputPlaceholder && _(inputPlaceholder)} /> {enableSearch && <CommandInput placeholder={inputPlaceholder && _(inputPlaceholder)} />}
<CommandEmpty> <CommandEmpty>
<Trans>No value found.</Trans> <Trans>No value found.</Trans>
</CommandEmpty> </CommandEmpty>

View File

@@ -1,3 +1,5 @@
import { SIGNATURE_CANVAS_DPI } from '@documenso/lib/constants/signatures';
import { Point } from './point'; import { Point } from './point';
export class Canvas { export class Canvas {
@@ -14,7 +16,7 @@ export class Canvas {
private lastVelocity = 0; private lastVelocity = 0;
private readonly VELOCITY_FILTER_WEIGHT = 0.5; private readonly VELOCITY_FILTER_WEIGHT = 0.5;
private readonly DPI = 2; private readonly DPI = SIGNATURE_CANVAS_DPI;
constructor(canvas: HTMLCanvasElement) { constructor(canvas: HTMLCanvasElement) {
this.$canvas = canvas; this.$canvas = canvas;

View File

@@ -0,0 +1,63 @@
import { Trans } from '@lingui/react/macro';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { cn } from '../../lib/utils';
export type SignaturePadColorPickerProps = {
selectedColor: string;
setSelectedColor: (color: string) => void;
className?: string;
};
export const SignaturePadColorPicker = ({
selectedColor,
setSelectedColor,
className,
}: SignaturePadColorPickerProps) => {
return (
<div className={cn('text-foreground absolute right-2 top-2 filter', className)}>
<Select defaultValue={selectedColor} onValueChange={(value) => setSelectedColor(value)}>
<SelectTrigger className="h-auto w-auto border-none p-0.5">
<SelectValue placeholder="" />
</SelectTrigger>
<SelectContent className="w-[100px]" align="end">
<SelectItem value="black">
<div className="text-muted-foreground flex items-center text-[0.688rem]">
<div className="border-border mr-1 h-4 w-4 rounded-full border-2 bg-black shadow-sm" />
<Trans>Black</Trans>
</div>
</SelectItem>
<SelectItem value="red">
<div className="text-muted-foreground flex items-center text-[0.688rem]">
<div className="border-border mr-1 h-4 w-4 rounded-full border-2 bg-[red] shadow-sm" />
<Trans>Red</Trans>
</div>
</SelectItem>
<SelectItem value="blue">
<div className="text-muted-foreground flex items-center text-[0.688rem]">
<div className="border-border mr-1 h-4 w-4 rounded-full border-2 bg-[blue] shadow-sm" />
<Trans>Blue</Trans>
</div>
</SelectItem>
<SelectItem value="green">
<div className="text-muted-foreground flex items-center text-[0.688rem]">
<div className="border-border mr-1 h-4 w-4 rounded-full border-2 bg-[green] shadow-sm" />
<Trans>Green</Trans>
</div>
</SelectItem>
</SelectContent>
</Select>
</div>
);
};

View File

@@ -0,0 +1,150 @@
import type { HTMLAttributes } from 'react';
import { useState } from 'react';
import type { MessageDescriptor } from '@lingui/core';
import { Trans, useLingui } from '@lingui/react/macro';
import { motion } from 'framer-motion';
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
import { Dialog, DialogClose, DialogContent, DialogFooter } from '@documenso/ui/primitives/dialog';
import { cn } from '../../lib/utils';
import { Button } from '../button';
import { SignaturePad } from './signature-pad';
import { SignatureRender } from './signature-render';
export type SignaturePadDialogProps = Omit<HTMLAttributes<HTMLCanvasElement>, 'onChange'> & {
disabled?: boolean;
value?: string;
onChange: (_value: string) => void;
dialogConfirmText?: MessageDescriptor | string;
disableAnimation?: boolean;
typedSignatureEnabled?: boolean;
uploadSignatureEnabled?: boolean;
drawSignatureEnabled?: boolean;
};
export const SignaturePadDialog = ({
className,
value,
onChange,
disabled = false,
disableAnimation = false,
typedSignatureEnabled,
uploadSignatureEnabled,
drawSignatureEnabled,
dialogConfirmText,
}: SignaturePadDialogProps) => {
const { i18n } = useLingui();
const [showSignatureModal, setShowSignatureModal] = useState(false);
const [signature, setSignature] = useState<string>(value ?? '');
return (
<div
className={cn(
'aspect-signature-pad bg-background relative block w-full select-none rounded-lg border',
className,
{
'pointer-events-none opacity-50': disabled,
},
)}
>
{value && (
<div className="inset-0 h-full w-full">
<SignatureRender value={value} />
</div>
)}
<motion.button
data-testid="signature-pad-dialog-button"
type="button"
disabled={disabled}
className="absolute inset-0 flex items-center justify-center bg-transparent"
onClick={() => setShowSignatureModal(true)}
whileHover="onHover"
>
{!value && !disableAnimation && (
<motion.svg
width="120"
height="120"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="text-muted-foreground/60"
variants={{
onHover: {
scale: 1.1,
transition: {
type: 'spring',
stiffness: 300,
damping: 12,
mass: 0.8,
restDelta: 0.001,
},
},
}}
>
<motion.path
d="M1.5 11H14.5M1.5 14C1.5 14 8.72 2 4.86938 2H4.875C2.01 2 1.97437 14.0694 8 6.51188V6.5C8 6.5 9 11.3631 11.5 7.52375V7.5C11.5 7.5 11.5 9 14.5 9"
stroke="currentColor"
strokeWidth="1.1"
strokeLinecap="round"
strokeLinejoin="round"
initial={{ pathLength: 0, opacity: 0 }}
animate={{
pathLength: 1,
opacity: 1,
transition: {
pathLength: {
duration: 2,
ease: 'easeInOut',
},
opacity: { duration: 0.6 },
},
}}
/>
</motion.svg>
)}
</motion.button>
<Dialog open={showSignatureModal} onOpenChange={disabled ? undefined : setShowSignatureModal}>
<DialogContent hideClose={true} className="p-6 pt-4">
<SignaturePad
id="signature"
value={value}
className={className}
disabled={disabled}
onChange={({ value }) => setSignature(value)}
typedSignatureEnabled={typedSignatureEnabled}
uploadSignatureEnabled={uploadSignatureEnabled}
drawSignatureEnabled={drawSignatureEnabled}
/>
<DialogFooter>
<DialogClose asChild>
<Button type="button" variant="ghost">
<Trans>Cancel</Trans>
</Button>
</DialogClose>
<Button
type="button"
disabled={!signature}
onClick={() => {
onChange(signature);
setShowSignatureModal(false);
}}
>
{dialogConfirmText ? (
parseMessageDescriptor(i18n._, dialogConfirmText)
) : (
<Trans>Next</Trans>
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
};

View File

@@ -0,0 +1,327 @@
import type { MouseEvent, PointerEvent, RefObject, TouchEvent } from 'react';
import { useMemo, useRef, useState } from 'react';
import { Trans } from '@lingui/react/macro';
import { Undo2 } from 'lucide-react';
import type { StrokeOptions } from 'perfect-freehand';
import { getStroke } from 'perfect-freehand';
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
import {
SIGNATURE_CANVAS_DPI,
SIGNATURE_MIN_COVERAGE_THRESHOLD,
} from '@documenso/lib/constants/signatures';
import { cn } from '../../lib/utils';
import { getSvgPathFromStroke } from './helper';
import { Point } from './point';
import { SignaturePadColorPicker } from './signature-pad-color-picker';
const checkSignatureValidity = (element: RefObject<HTMLCanvasElement>) => {
if (!element.current) {
return false;
}
const ctx = element.current.getContext('2d');
if (!ctx) {
return false;
}
const imageData = ctx.getImageData(0, 0, element.current.width, element.current.height);
const data = imageData.data;
let filledPixels = 0;
const totalPixels = data.length / 4;
for (let i = 0; i < data.length; i += 4) {
if (data[i + 3] > 0) filledPixels++;
}
const filledPercentage = filledPixels / totalPixels;
const isValid = filledPercentage > SIGNATURE_MIN_COVERAGE_THRESHOLD;
return isValid;
};
export type SignaturePadDrawProps = {
className?: string;
value: string;
onChange: (_signatureDataUrl: string) => void;
};
export const SignaturePadDraw = ({
className,
value,
onChange,
...props
}: SignaturePadDrawProps) => {
const $el = useRef<HTMLCanvasElement>(null);
const $imageData = useRef<ImageData | null>(null);
const $fileInput = useRef<HTMLInputElement>(null);
const [isPressed, setIsPressed] = useState(false);
const [lines, setLines] = useState<Point[][]>([]);
const [currentLine, setCurrentLine] = useState<Point[]>([]);
const [isSignatureValid, setIsSignatureValid] = useState<boolean | null>(null);
const [selectedColor, setSelectedColor] = useState('black');
const perfectFreehandOptions = useMemo(() => {
const size = $el.current ? Math.min($el.current.height, $el.current.width) * 0.03 : 10;
return {
size,
thinning: 0.25,
streamline: 0.5,
smoothing: 0.5,
end: {
taper: size * 2,
},
} satisfies StrokeOptions;
}, []);
const onMouseDown = (event: MouseEvent | PointerEvent | TouchEvent) => {
if (event.cancelable) {
event.preventDefault();
}
setIsPressed(true);
const point = Point.fromEvent(event, SIGNATURE_CANVAS_DPI, $el.current);
setCurrentLine([point]);
};
const onMouseMove = (event: MouseEvent | PointerEvent | TouchEvent) => {
if (event.cancelable) {
event.preventDefault();
}
if (!isPressed) {
return;
}
const point = Point.fromEvent(event, SIGNATURE_CANVAS_DPI, $el.current);
const lastPoint = currentLine[currentLine.length - 1];
if (lastPoint && point.distanceTo(lastPoint) > 5) {
setCurrentLine([...currentLine, point]);
// Update the canvas here to draw the lines
if ($el.current) {
const ctx = $el.current.getContext('2d');
if (ctx) {
ctx.restore();
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
ctx.fillStyle = selectedColor;
lines.forEach((line) => {
const pathData = new Path2D(
getSvgPathFromStroke(getStroke(line, perfectFreehandOptions)),
);
ctx.fill(pathData);
});
const pathData = new Path2D(
getSvgPathFromStroke(getStroke([...currentLine, point], perfectFreehandOptions)),
);
ctx.fill(pathData);
}
}
}
};
const onMouseUp = (event: MouseEvent | PointerEvent | TouchEvent, addLine = true) => {
if (event.cancelable) {
event.preventDefault();
}
setIsPressed(false);
const point = Point.fromEvent(event, SIGNATURE_CANVAS_DPI, $el.current);
const newLines = [...lines];
if (addLine && currentLine.length > 0) {
newLines.push([...currentLine, point]);
setCurrentLine([]);
}
setLines(newLines);
if ($el.current && newLines.length > 0) {
const ctx = $el.current.getContext('2d');
if (ctx) {
ctx.restore();
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
ctx.fillStyle = selectedColor;
newLines.forEach((line) => {
const pathData = new Path2D(
getSvgPathFromStroke(getStroke(line, perfectFreehandOptions)),
);
ctx.fill(pathData);
});
const isValidSignature = checkSignatureValidity($el);
setIsSignatureValid(isValidSignature);
if (isValidSignature) {
onChange?.($el.current.toDataURL());
}
ctx.save();
}
}
};
const onMouseEnter = (event: MouseEvent | PointerEvent | TouchEvent) => {
if (event.cancelable) {
event.preventDefault();
}
if ('buttons' in event && event.buttons === 1) {
onMouseDown(event);
}
};
const onMouseLeave = (event: MouseEvent | PointerEvent | TouchEvent) => {
if (event.cancelable) {
event.preventDefault();
}
if (isPressed) {
onMouseUp(event, true);
} else {
onMouseUp(event, false);
}
};
const onClearClick = () => {
if ($el.current) {
const ctx = $el.current.getContext('2d');
ctx?.clearRect(0, 0, $el.current.width, $el.current.height);
$imageData.current = null;
}
if ($fileInput.current) {
$fileInput.current.value = '';
}
onChange('');
setLines([]);
setCurrentLine([]);
setIsPressed(false);
};
const onUndoClick = () => {
if (lines.length === 0 || !$el.current) {
return;
}
const newLines = lines.slice(0, -1);
setLines(newLines);
// Clear and redraw the canvas
const ctx = $el.current.getContext('2d');
const { width, height } = $el.current;
ctx?.clearRect(0, 0, width, height);
if ($imageData.current) {
ctx?.putImageData($imageData.current, 0, 0);
}
newLines.forEach((line) => {
const pathData = new Path2D(getSvgPathFromStroke(getStroke(line, perfectFreehandOptions)));
ctx?.fill(pathData);
});
onChange?.($el.current.toDataURL());
};
unsafe_useEffectOnce(() => {
if ($el.current) {
$el.current.width = $el.current.clientWidth * SIGNATURE_CANVAS_DPI;
$el.current.height = $el.current.clientHeight * SIGNATURE_CANVAS_DPI;
}
if ($el.current && value) {
const ctx = $el.current.getContext('2d');
const { width, height } = $el.current;
const img = new Image();
img.onload = () => {
ctx?.drawImage(img, 0, 0, Math.min(width, img.width), Math.min(height, img.height));
const defaultImageData = ctx?.getImageData(0, 0, width, height) || null;
$imageData.current = defaultImageData;
};
img.src = value;
}
});
return (
<div className={cn('h-full w-full', className)}>
<canvas
data-testid="signature-pad-draw"
ref={$el}
className={cn('h-full w-full', {
'dark:hue-rotate-180 dark:invert': selectedColor === 'black',
})}
style={{ touchAction: 'none' }}
onPointerMove={(event) => onMouseMove(event)}
onPointerDown={(event) => onMouseDown(event)}
onPointerUp={(event) => onMouseUp(event)}
onPointerLeave={(event) => onMouseLeave(event)}
onPointerEnter={(event) => onMouseEnter(event)}
{...props}
/>
<SignaturePadColorPicker selectedColor={selectedColor} setSelectedColor={setSelectedColor} />
<div className="absolute bottom-3 right-3 flex gap-2">
<button
type="button"
className="focus-visible:ring-ring ring-offset-background text-muted-foreground/60 hover:text-muted-foreground rounded-full p-0 text-[0.688rem] focus-visible:outline-none focus-visible:ring-2"
onClick={() => onClearClick()}
>
<Trans>Clear Signature</Trans>
</button>
</div>
{isSignatureValid === false && (
<div className="absolute bottom-4 left-4 flex gap-2">
<span className="text-destructive text-xs">
<Trans>Signature is too small</Trans>
</span>
</div>
)}
{isSignatureValid && lines.length > 0 && (
<div className="absolute bottom-4 left-4 flex gap-2">
<button
type="button"
title="undo"
className="focus-visible:ring-ring ring-offset-background text-muted-foreground/60 hover:text-muted-foreground rounded-full p-0 text-[0.688rem] focus-visible:outline-none focus-visible:ring-2"
onClick={onUndoClick}
>
<Undo2 className="h-4 w-4" />
<span className="sr-only">Undo</span>
</button>
</div>
)}
</div>
);
};

View File

@@ -0,0 +1,29 @@
import { useState } from 'react';
import { cn } from '../../lib/utils';
export type SignaturePadTypeProps = {
className?: string;
value?: string;
onChange: (_value: string) => void;
};
export const SignaturePadType = ({ className, value, onChange }: SignaturePadTypeProps) => {
// Colors don't actually work for text.
const [selectedColor, setSelectedColor] = useState('black');
return (
<div className={cn('flex h-full w-full items-center justify-center', className)}>
<input
data-testid="signature-pad-type-input"
placeholder="Type your signature"
className="font-signature w-full bg-transparent px-4 text-center text-7xl text-black placeholder:text-4xl focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 dark:text-white"
// style={{ color: selectedColor }}
value={value}
onChange={(event) => onChange(event.target.value.trimStart())}
/>
{/* <SignaturePadColorPicker selectedColor={selectedColor} setSelectedColor={setSelectedColor} /> */}
</div>
);
};

View File

@@ -0,0 +1,166 @@
import { useRef } from 'react';
import { Trans } from '@lingui/react/macro';
import { motion } from 'framer-motion';
import { UploadCloudIcon } from 'lucide-react';
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
import { SIGNATURE_CANVAS_DPI } from '@documenso/lib/constants/signatures';
import { cn } from '../../lib/utils';
const loadImage = async (file: File | undefined): Promise<HTMLImageElement> => {
if (!file) {
throw new Error('No file selected');
}
if (!file.type.startsWith('image/')) {
throw new Error('Invalid file type');
}
if (file.size > 5 * 1024 * 1024) {
throw new Error('Image size should be less than 5MB');
}
return new Promise((resolve, reject) => {
const img = new Image();
const objectUrl = URL.createObjectURL(file);
img.onload = () => {
URL.revokeObjectURL(objectUrl);
resolve(img);
};
img.onerror = () => {
URL.revokeObjectURL(objectUrl);
reject(new Error('Failed to load image'));
};
img.src = objectUrl;
});
};
const loadImageOntoCanvas = (
image: HTMLImageElement,
canvas: HTMLCanvasElement,
ctx: CanvasRenderingContext2D,
): ImageData => {
const scale = Math.min((canvas.width * 0.8) / image.width, (canvas.height * 0.8) / image.height);
const x = (canvas.width - image.width * scale) / 2;
const y = (canvas.height - image.height * scale) / 2;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
ctx.drawImage(image, x, y, image.width * scale, image.height * scale);
ctx.restore();
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
return imageData;
};
export type SignaturePadUploadProps = {
className?: string;
value: string;
onChange: (_signatureDataUrl: string) => void;
};
export const SignaturePadUpload = ({
className,
value,
onChange,
...props
}: SignaturePadUploadProps) => {
const $el = useRef<HTMLCanvasElement>(null);
const $imageData = useRef<ImageData | null>(null);
const $fileInput = useRef<HTMLInputElement>(null);
const handleImageUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
try {
const img = await loadImage(event.target.files?.[0]);
if (!$el.current) return;
const ctx = $el.current.getContext('2d');
if (!ctx) return;
$imageData.current = loadImageOntoCanvas(img, $el.current, ctx);
onChange?.($el.current.toDataURL());
} catch (error) {
console.error(error);
}
};
unsafe_useEffectOnce(() => {
// Todo: Not really sure if this is required for uploaded images.
if ($el.current) {
$el.current.width = $el.current.clientWidth * SIGNATURE_CANVAS_DPI;
$el.current.height = $el.current.clientHeight * SIGNATURE_CANVAS_DPI;
}
if ($el.current && value) {
const ctx = $el.current.getContext('2d');
const { width, height } = $el.current;
const img = new Image();
img.onload = () => {
ctx?.drawImage(img, 0, 0, Math.min(width, img.width), Math.min(height, img.height));
const defaultImageData = ctx?.getImageData(0, 0, width, height) || null;
$imageData.current = defaultImageData;
};
img.src = value;
}
});
return (
<div className={cn('relative h-full w-full', className)}>
<canvas
data-testid="signature-pad-upload"
ref={$el}
className="h-full w-full dark:hue-rotate-180 dark:invert"
style={{ touchAction: 'none' }}
{...props}
/>
<input
ref={$fileInput}
type="file"
accept="image/*"
className="hidden"
onChange={handleImageUpload}
/>
<motion.button
className="absolute inset-0 flex h-full w-full items-center justify-center"
initial="initial"
animate="animate"
whileHover="hover"
onClick={() => $fileInput.current?.click()}
>
{!value && (
<motion.div>
<div className="text-muted-foreground flex flex-col items-center justify-center">
<div className="flex flex-col items-center">
<UploadCloudIcon className="h-8 w-8" />
<span className="text-lg font-semibold">
<Trans>Upload Signature</Trans>
</span>
</div>
</div>
</motion.div>
)}
</motion.button>
</div>
);
};

View File

@@ -1,591 +1,199 @@
import type { HTMLAttributes, MouseEvent, PointerEvent, TouchEvent } from 'react'; import type { HTMLAttributes } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react'; import { useState } from 'react';
import { Trans } from '@lingui/react/macro'; import { KeyboardIcon, UploadCloudIcon } from 'lucide-react';
import { Undo2, Upload } from 'lucide-react'; import { match } from 'ts-pattern';
import type { StrokeOptions } from 'perfect-freehand';
import { getStroke } from 'perfect-freehand';
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once'; import { DocumentSignatureType } from '@documenso/lib/constants/document';
import { Input } from '@documenso/ui/primitives/input'; import { isBase64Image } from '@documenso/lib/constants/signatures';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { SignatureIcon } from '../../icons/signature';
import { cn } from '../../lib/utils'; import { cn } from '../../lib/utils';
import { getSvgPathFromStroke } from './helper'; import { SignaturePadDraw } from './signature-pad-draw';
import { Point } from './point'; import { SignaturePadType } from './signature-pad-type';
import { SignaturePadUpload } from './signature-pad-upload';
import { Tabs, TabsContent, TabsList, TabsTrigger } from './signature-tabs';
const DPI = 2; export type SignaturePadValue = {
type: DocumentSignatureType;
const isBase64Image = (value: string) => value.startsWith('data:image/png;base64,'); value: string;
const loadImage = async (file: File | undefined): Promise<HTMLImageElement> => {
if (!file) {
throw new Error('No file selected');
}
if (!file.type.startsWith('image/')) {
throw new Error('Invalid file type');
}
if (file.size > 5 * 1024 * 1024) {
throw new Error('Image size should be less than 5MB');
}
return new Promise((resolve, reject) => {
const img = new Image();
const objectUrl = URL.createObjectURL(file);
img.onload = () => {
URL.revokeObjectURL(objectUrl);
resolve(img);
};
img.onerror = () => {
URL.revokeObjectURL(objectUrl);
reject(new Error('Failed to load image'));
};
img.src = objectUrl;
});
};
const loadImageOntoCanvas = (
image: HTMLImageElement,
canvas: HTMLCanvasElement,
ctx: CanvasRenderingContext2D,
): ImageData => {
const scale = Math.min((canvas.width * 0.8) / image.width, (canvas.height * 0.8) / image.height);
const x = (canvas.width - image.width * scale) / 2;
const y = (canvas.height - image.height * scale) / 2;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
ctx.drawImage(image, x, y, image.width * scale, image.height * scale);
ctx.restore();
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
return imageData;
}; };
export type SignaturePadProps = Omit<HTMLAttributes<HTMLCanvasElement>, 'onChange'> & { export type SignaturePadProps = Omit<HTMLAttributes<HTMLCanvasElement>, 'onChange'> & {
onChange?: (_signatureDataUrl: string | null) => void; value?: string;
containerClassName?: string; onChange?: (_value: SignaturePadValue) => void;
disabled?: boolean; disabled?: boolean;
allowTypedSignature?: boolean;
defaultValue?: string; typedSignatureEnabled?: boolean;
uploadSignatureEnabled?: boolean;
drawSignatureEnabled?: boolean;
onValidityChange?: (isValid: boolean) => void; onValidityChange?: (isValid: boolean) => void;
minCoverageThreshold?: number;
}; };
export const SignaturePad = ({ export const SignaturePad = ({
className, value = '',
containerClassName,
defaultValue,
onChange, onChange,
disabled = false, disabled = false,
allowTypedSignature, typedSignatureEnabled = true,
onValidityChange, uploadSignatureEnabled = true,
minCoverageThreshold = 0.01, drawSignatureEnabled = true,
...props
}: SignaturePadProps) => { }: SignaturePadProps) => {
const $el = useRef<HTMLCanvasElement>(null); const [imageSignature, setImageSignature] = useState(isBase64Image(value) ? value : '');
const $imageData = useRef<ImageData | null>(null); const [drawSignature, setDrawSignature] = useState(isBase64Image(value) ? value : '');
const $fileInput = useRef<HTMLInputElement>(null); const [typedSignature, setTypedSignature] = useState(isBase64Image(value) ? '' : value);
const [isPressed, setIsPressed] = useState(false); /**
const [lines, setLines] = useState<Point[][]>([]); * This is cooked.
const [currentLine, setCurrentLine] = useState<Point[]>([]); *
const [selectedColor, setSelectedColor] = useState('black'); * Get the first enabled tab that has a signature if possible, otherwise just get
const [typedSignature, setTypedSignature] = useState( * the first enabled tab.
defaultValue && !isBase64Image(defaultValue) ? defaultValue : '', */
const [tab, setTab] = useState(
((): 'draw' | 'text' | 'image' => {
// First passthrough to check to see if there's a signature for a given tab.
if (drawSignatureEnabled && drawSignature) {
return 'draw';
}
if (typedSignatureEnabled && typedSignature) {
return 'text';
}
if (uploadSignatureEnabled && imageSignature) {
return 'image';
}
// Second passthrough to just select the first avaliable tab.
if (drawSignatureEnabled) {
return 'draw';
}
if (typedSignatureEnabled) {
return 'text';
}
if (uploadSignatureEnabled) {
return 'image';
}
throw new Error('No signature enabled');
})(),
); );
const perfectFreehandOptions = useMemo(() => { const onImageSignatureChange = (value: string) => {
const size = $el.current ? Math.min($el.current.height, $el.current.width) * 0.03 : 10; setImageSignature(value);
return { onChange?.({
size, type: DocumentSignatureType.UPLOAD,
thinning: 0.25, value,
streamline: 0.5, });
smoothing: 0.5,
end: {
taper: size * 2,
},
} satisfies StrokeOptions;
}, []);
const checkSignatureValidity = () => {
if ($el.current) {
const ctx = $el.current.getContext('2d');
if (ctx) {
const imageData = ctx.getImageData(0, 0, $el.current.width, $el.current.height);
const data = imageData.data;
let filledPixels = 0;
const totalPixels = data.length / 4;
for (let i = 0; i < data.length; i += 4) {
if (data[i + 3] > 0) filledPixels++;
}
const filledPercentage = filledPixels / totalPixels;
const isValid = filledPercentage > minCoverageThreshold;
onValidityChange?.(isValid);
return isValid;
}
}
}; };
const onMouseDown = (event: MouseEvent | PointerEvent | TouchEvent) => { const onDrawSignatureChange = (value: string) => {
if (event.cancelable) { setDrawSignature(value);
event.preventDefault();
}
setIsPressed(true); onChange?.({
type: DocumentSignatureType.DRAW,
if (typedSignature) { value,
setTypedSignature(''); });
if ($el.current) {
const ctx = $el.current.getContext('2d');
ctx?.clearRect(0, 0, $el.current.width, $el.current.height);
}
}
const point = Point.fromEvent(event, DPI, $el.current);
setCurrentLine([point]);
}; };
const onMouseMove = (event: MouseEvent | PointerEvent | TouchEvent) => { const onTypedSignatureChange = (value: string) => {
if (event.cancelable) { setTypedSignature(value);
event.preventDefault();
}
if (!isPressed) { onChange?.({
type: DocumentSignatureType.TYPE,
value,
});
};
const onTabChange = (value: 'draw' | 'text' | 'image') => {
if (disabled) {
return; return;
} }
const point = Point.fromEvent(event, DPI, $el.current); setTab(value);
const lastPoint = currentLine[currentLine.length - 1];
if (lastPoint && point.distanceTo(lastPoint) > 5) { match(value)
setCurrentLine([...currentLine, point]); .with('draw', () => {
onDrawSignatureChange(drawSignature);
// Update the canvas here to draw the lines })
if ($el.current) { .with('text', () => {
const ctx = $el.current.getContext('2d'); onTypedSignatureChange(typedSignature);
})
if (ctx) { .with('image', () => {
ctx.restore(); onImageSignatureChange(imageSignature);
ctx.imageSmoothingEnabled = true; })
ctx.imageSmoothingQuality = 'high'; .exhaustive();
ctx.fillStyle = selectedColor;
lines.forEach((line) => {
const pathData = new Path2D(
getSvgPathFromStroke(getStroke(line, perfectFreehandOptions)),
);
ctx.fill(pathData);
});
const pathData = new Path2D(
getSvgPathFromStroke(getStroke([...currentLine, point], perfectFreehandOptions)),
);
ctx.fill(pathData);
}
}
}
}; };
const onMouseUp = (event: MouseEvent | PointerEvent | TouchEvent, addLine = true) => { if (!drawSignatureEnabled && !typedSignatureEnabled && !uploadSignatureEnabled) {
if (event.cancelable) { return null;
event.preventDefault();
} }
setIsPressed(false);
const point = Point.fromEvent(event, DPI, $el.current);
const newLines = [...lines];
if (addLine && currentLine.length > 0) {
newLines.push([...currentLine, point]);
setCurrentLine([]);
}
setLines(newLines);
if ($el.current && newLines.length > 0) {
const ctx = $el.current.getContext('2d');
if (ctx) {
ctx.restore();
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
ctx.fillStyle = selectedColor;
newLines.forEach((line) => {
const pathData = new Path2D(
getSvgPathFromStroke(getStroke(line, perfectFreehandOptions)),
);
ctx.fill(pathData);
});
const isValidSignature = checkSignatureValidity();
if (isValidSignature) {
onChange?.($el.current.toDataURL());
}
ctx.save();
}
}
};
const onMouseEnter = (event: MouseEvent | PointerEvent | TouchEvent) => {
if (event.cancelable) {
event.preventDefault();
}
if ('buttons' in event && event.buttons === 1) {
onMouseDown(event);
}
};
const onMouseLeave = (event: MouseEvent | PointerEvent | TouchEvent) => {
if (event.cancelable) {
event.preventDefault();
}
if (isPressed) {
onMouseUp(event, true);
} else {
onMouseUp(event, false);
}
};
const onClearClick = () => {
if ($el.current) {
const ctx = $el.current.getContext('2d');
ctx?.clearRect(0, 0, $el.current.width, $el.current.height);
$imageData.current = null;
}
if ($fileInput.current) {
$fileInput.current.value = '';
}
onChange?.(null);
setTypedSignature('');
setLines([]);
setCurrentLine([]);
setIsPressed(false);
};
const renderTypedSignature = () => {
if ($el.current && typedSignature) {
const ctx = $el.current.getContext('2d');
if (ctx) {
const canvasWidth = $el.current.width;
const canvasHeight = $el.current.height;
const fontFamily = 'Caveat';
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = selectedColor;
// Calculate the desired width (25ch)
const desiredWidth = canvasWidth * 0.85; // 85% of canvas width
// Start with a base font size
let fontSize = 18;
ctx.font = `${fontSize}px ${fontFamily}`;
// Measure 10 characters and calculate scale factor
const characterWidth = ctx.measureText('m'.repeat(10)).width;
const scaleFactor = desiredWidth / characterWidth;
// Apply scale factor to font size
fontSize = fontSize * scaleFactor;
// Adjust font size if it exceeds canvas width
ctx.font = `${fontSize}px ${fontFamily}`;
const textWidth = ctx.measureText(typedSignature).width;
if (textWidth > desiredWidth) {
fontSize = fontSize * (desiredWidth / textWidth);
}
// Set final font and render text
ctx.font = `${fontSize}px ${fontFamily}`;
ctx.fillText(typedSignature, canvasWidth / 2, canvasHeight / 2);
}
}
};
const handleTypedSignatureChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newValue = event.target.value;
// Deny input while drawing.
if (isPressed) {
return;
}
if (lines.length > 0) {
setLines([]);
setCurrentLine([]);
}
setTypedSignature(newValue);
if ($el.current) {
const ctx = $el.current.getContext('2d');
ctx?.clearRect(0, 0, $el.current.width, $el.current.height);
}
if (newValue.trim() !== '') {
onChange?.(newValue);
onValidityChange?.(true);
} else {
onChange?.(null);
onValidityChange?.(false);
}
};
const handleImageUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
try {
const img = await loadImage(event.target.files?.[0]);
if (!$el.current) return;
const ctx = $el.current.getContext('2d');
if (!ctx) return;
$imageData.current = loadImageOntoCanvas(img, $el.current, ctx);
onChange?.($el.current.toDataURL());
setLines([]);
setCurrentLine([]);
setTypedSignature('');
} catch (error) {
console.error(error);
}
};
useEffect(() => {
if (typedSignature.trim() !== '' && !isBase64Image(typedSignature)) {
renderTypedSignature();
onChange?.(typedSignature);
}
}, [typedSignature, selectedColor]);
const onUndoClick = () => {
if (lines.length === 0 && typedSignature.length === 0) {
return;
}
if (typedSignature.length > 0) {
const newTypedSignature = typedSignature.slice(0, -1);
setTypedSignature(newTypedSignature);
// You might want to call onChange here as well
// onChange?.(newTypedSignature);
} else {
const newLines = lines.slice(0, -1);
setLines(newLines);
// Clear and redraw the canvas
if ($el.current) {
const ctx = $el.current.getContext('2d');
const { width, height } = $el.current;
ctx?.clearRect(0, 0, width, height);
if (typeof defaultValue === 'string' && $imageData.current) {
ctx?.putImageData($imageData.current, 0, 0);
}
newLines.forEach((line) => {
const pathData = new Path2D(
getSvgPathFromStroke(getStroke(line, perfectFreehandOptions)),
);
ctx?.fill(pathData);
});
onChange?.($el.current.toDataURL());
}
}
};
useEffect(() => {
if ($el.current) {
$el.current.width = $el.current.clientWidth * DPI;
$el.current.height = $el.current.clientHeight * DPI;
}
if (defaultValue && typedSignature) {
renderTypedSignature();
}
}, []);
unsafe_useEffectOnce(() => {
if ($el.current && typeof defaultValue === 'string') {
const ctx = $el.current.getContext('2d');
const { width, height } = $el.current;
const img = new Image();
img.onload = () => {
ctx?.drawImage(img, 0, 0, Math.min(width, img.width), Math.min(height, img.height));
const defaultImageData = ctx?.getImageData(0, 0, width, height) || null;
$imageData.current = defaultImageData;
};
img.src = defaultValue;
}
});
return ( return (
<div <Tabs
className={cn('relative block select-none', containerClassName, { defaultValue={tab}
'pointer-events-none opacity-50': disabled, className={cn({
'pointer-events-none': disabled,
})} })}
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
onValueChange={(value) => onTabChange(value as 'draw' | 'text' | 'image')}
> >
<canvas <TabsList>
data-testid="signature-pad" {drawSignatureEnabled && (
ref={$el} <TabsTrigger value="draw">
<SignatureIcon className="mr-2 size-4" />
Draw
</TabsTrigger>
)}
{typedSignatureEnabled && (
<TabsTrigger value="text">
<KeyboardIcon className="mr-2 size-4" />
Type
</TabsTrigger>
)}
{uploadSignatureEnabled && (
<TabsTrigger value="image">
<UploadCloudIcon className="mr-2 size-4" />
Upload
</TabsTrigger>
)}
</TabsList>
<TabsContent
value="draw"
className="border-border aspect-signature-pad dark:bg-background relative flex items-center justify-center rounded-md border bg-neutral-50 text-center"
>
<SignaturePadDraw
className="h-full w-full"
onChange={onDrawSignatureChange}
value={drawSignature}
/>
</TabsContent>
<TabsContent
value="text"
className="border-border aspect-signature-pad dark:bg-background relative flex items-center justify-center rounded-md border bg-neutral-50 text-center"
>
<SignaturePadType value={typedSignature} onChange={onTypedSignatureChange} />
</TabsContent>
<TabsContent
value="image"
className={cn( className={cn(
'relative block', 'border-border aspect-signature-pad dark:bg-background relative rounded-md border bg-neutral-50',
{ {
'dark:hue-rotate-180 dark:invert': selectedColor === 'black', 'bg-white': imageSignature,
}, },
className,
)} )}
style={{ touchAction: 'none' }}
onPointerMove={(event) => onMouseMove(event)}
onPointerDown={(event) => onMouseDown(event)}
onPointerUp={(event) => onMouseUp(event)}
onPointerLeave={(event) => onMouseLeave(event)}
onPointerEnter={(event) => onMouseEnter(event)}
{...props}
/>
{allowTypedSignature && (
<div
className={cn('ml-4 pb-1', {
'ml-10': lines.length > 0 || typedSignature.length > 0,
})}
> >
<Input <SignaturePadUpload value={imageSignature} onChange={onImageSignatureChange} />
placeholder="Type your signature" </TabsContent>
className="w-1/2 border-none p-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0" </Tabs>
value={typedSignature}
onChange={handleTypedSignatureChange}
/>
</div>
)}
<div className="text-foreground absolute left-3 top-3 filter">
<div
className="focus-visible:ring-ring ring-offset-background text-muted-foreground/60 hover:text-muted-foreground flex cursor-pointer flex-row gap-2 rounded-full p-0 text-[0.688rem] focus-visible:outline-none focus-visible:ring-2"
onClick={() => $fileInput.current?.click()}
>
<Input
ref={$fileInput}
type="file"
accept="image/*"
className="hidden"
onChange={handleImageUpload}
disabled={disabled}
/>
<Upload className="h-4 w-4" />
<span>
<Trans>Upload Signature</Trans>
</span>
</div>
</div>
<div className="text-foreground absolute right-2 top-2 filter">
<Select defaultValue={selectedColor} onValueChange={(value) => setSelectedColor(value)}>
<SelectTrigger className="h-auto w-auto border-none p-0.5">
<SelectValue placeholder="" />
</SelectTrigger>
<SelectContent className="w-[100px]" align="end">
<SelectItem value="black">
<div className="text-muted-foreground flex items-center text-[0.688rem]">
<div className="border-border mr-1 h-4 w-4 rounded-full border-2 bg-black shadow-sm" />
<Trans>Black</Trans>
</div>
</SelectItem>
<SelectItem value="red">
<div className="text-muted-foreground flex items-center text-[0.688rem]">
<div className="border-border mr-1 h-4 w-4 rounded-full border-2 bg-[red] shadow-sm" />
<Trans>Red</Trans>
</div>
</SelectItem>
<SelectItem value="blue">
<div className="text-muted-foreground flex items-center text-[0.688rem]">
<div className="border-border mr-1 h-4 w-4 rounded-full border-2 bg-[blue] shadow-sm" />
<Trans>Blue</Trans>
</div>
</SelectItem>
<SelectItem value="green">
<div className="text-muted-foreground flex items-center text-[0.688rem]">
<div className="border-border mr-1 h-4 w-4 rounded-full border-2 bg-[green] shadow-sm" />
<Trans>Green</Trans>
</div>
</SelectItem>
</SelectContent>
</Select>
</div>
<div className="absolute bottom-3 right-3 flex gap-2">
<button
type="button"
className="focus-visible:ring-ring ring-offset-background text-muted-foreground/60 hover:text-muted-foreground rounded-full p-0 text-[0.688rem] focus-visible:outline-none focus-visible:ring-2"
onClick={() => onClearClick()}
>
<Trans>Clear Signature</Trans>
</button>
</div>
{(lines.length > 0 || typedSignature.length > 0) && (
<div className="absolute bottom-4 left-4 flex gap-2">
<button
type="button"
title="undo"
className="focus-visible:ring-ring ring-offset-background text-muted-foreground/60 hover:text-muted-foreground rounded-full p-0 text-[0.688rem] focus-visible:outline-none focus-visible:ring-2"
onClick={onUndoClick}
>
<Undo2 className="h-4 w-4" />
<span className="sr-only">Undo</span>
</button>
</div>
)}
</div>
); );
}; };

View File

@@ -0,0 +1,128 @@
import { useEffect, useRef } from 'react';
import { SIGNATURE_CANVAS_DPI, isBase64Image } from '@documenso/lib/constants/signatures';
import { cn } from '../../lib/utils';
export type SignatureRenderProps = {
className?: string;
value: string;
};
/**
* Renders a typed, uploaded or drawn signature.
*/
export const SignatureRender = ({ className, value }: SignatureRenderProps) => {
const $el = useRef<HTMLCanvasElement>(null);
const $imageData = useRef<ImageData | null>(null);
const renderTypedSignature = () => {
if (!$el.current) {
return;
}
const ctx = $el.current.getContext('2d');
if (!ctx) {
return;
}
ctx.clearRect(0, 0, $el.current.width, $el.current.height);
const canvasWidth = $el.current.width;
const canvasHeight = $el.current.height;
const fontFamily = 'Caveat';
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// ctx.fillStyle = selectedColor; // Todo: Color not implemented...
// Calculate the desired width (25ch)
const desiredWidth = canvasWidth * 0.85; // 85% of canvas width
// Start with a base font size
let fontSize = 18;
ctx.font = `${fontSize}px ${fontFamily}`;
// Measure 10 characters and calculate scale factor
const characterWidth = ctx.measureText('m'.repeat(10)).width;
const scaleFactor = desiredWidth / characterWidth;
// Apply scale factor to font size
fontSize = fontSize * scaleFactor;
// Adjust font size if it exceeds canvas width
ctx.font = `${fontSize}px ${fontFamily}`;
const textWidth = ctx.measureText(value).width;
if (textWidth > desiredWidth) {
fontSize = fontSize * (desiredWidth / textWidth);
}
// Set final font and render text
ctx.font = `${fontSize}px ${fontFamily}`;
ctx.fillText(value, canvasWidth / 2, canvasHeight / 2);
};
const renderImageSignature = () => {
if (!$el.current || typeof value !== 'string') {
return;
}
const ctx = $el.current.getContext('2d');
if (!ctx) {
return;
}
ctx.clearRect(0, 0, $el.current.width, $el.current.height);
const { width, height } = $el.current;
const img = new Image();
img.onload = () => {
// Calculate the scaled dimensions while maintaining aspect ratio
const scale = Math.min(width / img.width, height / img.height);
const scaledWidth = img.width * scale;
const scaledHeight = img.height * scale;
// Calculate center position
const x = (width - scaledWidth) / 2;
const y = (height - scaledHeight) / 2;
ctx?.drawImage(img, x, y, scaledWidth, scaledHeight);
const defaultImageData = ctx?.getImageData(0, 0, width, height) || null;
$imageData.current = defaultImageData;
};
img.src = value;
};
useEffect(() => {
if ($el.current) {
$el.current.width = $el.current.clientWidth * SIGNATURE_CANVAS_DPI;
$el.current.height = $el.current.clientHeight * SIGNATURE_CANVAS_DPI;
}
}, []);
useEffect(() => {
if (isBase64Image(value)) {
renderImageSignature();
} else {
renderTypedSignature();
}
}, [value]);
return (
<canvas
ref={$el}
className={cn('h-full w-full dark:hue-rotate-180 dark:invert', className)}
style={{ touchAction: 'none' }}
/>
);
};

View File

@@ -0,0 +1,147 @@
import * as React from 'react';
import { motion } from 'framer-motion';
import { cn } from '../../lib/utils';
interface TabsProps extends React.HTMLAttributes<HTMLDivElement> {
defaultValue?: string;
value?: string;
onValueChange?: (value: string) => void;
children: React.ReactNode;
}
interface TabsContextValue {
value: string;
onValueChange: (value: string) => void;
}
const TabsContext = React.createContext<TabsContextValue | undefined>(undefined);
function useTabs() {
const context = React.useContext(TabsContext);
if (!context) {
throw new Error('useTabs must be used within a Tabs provider');
}
return context;
}
export function Tabs({
defaultValue,
value,
onValueChange,
children,
className,
...props
}: TabsProps) {
const [tabValue, setTabValue] = React.useState(defaultValue || '');
const handleValueChange = React.useCallback(
(newValue: string) => {
setTabValue(newValue);
onValueChange?.(newValue);
},
[onValueChange],
);
const contextValue = React.useMemo(
() => ({
value: value !== undefined ? value : tabValue,
onValueChange: handleValueChange,
}),
[value, tabValue, handleValueChange],
);
return (
<TabsContext.Provider value={contextValue}>
<div className={cn('w-full', className)} {...props}>
{children}
</div>
</TabsContext.Provider>
);
}
interface TabsListProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
export function TabsList({ children, className, ...props }: TabsListProps) {
return (
<div
className={cn('border-border flex flex-wrap border-b', className)}
role="tabslist"
{...props}
>
{children}
</div>
);
}
interface TabsTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
value: string;
icon?: React.ReactNode;
children: React.ReactNode;
}
export function TabsTrigger({ value, icon, children, className, ...props }: TabsTriggerProps) {
const { value: selectedValue, onValueChange } = useTabs();
const isSelected = selectedValue === value;
return (
<button
role="tab"
type="button"
aria-selected={isSelected}
data-state={isSelected ? 'active' : 'inactive'}
onClick={() => onValueChange(value)}
className={cn(
'relative flex items-center px-4 py-3 text-sm font-medium transition-all',
'focus-visible:ring-ring focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
isSelected ? 'text-foreground' : 'text-muted-foreground hover:text-foreground',
className,
)}
{...props}
>
{icon && <span className="flex items-center">{icon}</span>}
{children}
{isSelected && (
<motion.div
layoutId="activeTabIndicator"
className="bg-foreground/40 absolute bottom-0 left-0 h-0.5 w-full rounded-full"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{
type: 'spring',
stiffness: 500,
damping: 50,
}}
/>
)}
</button>
);
}
interface TabsContentProps extends React.HTMLAttributes<HTMLDivElement> {
value: string;
children: React.ReactNode;
}
export function TabsContent({ value, children, className, ...props }: TabsContentProps) {
const { value: selectedValue } = useTabs();
const isSelected = selectedValue === value;
if (!isSelected) {
return null;
}
return (
<div
role="tabpanel"
data-state={isSelected ? 'active' : 'inactive'}
className={cn('mt-4', className)}
{...props}
>
{children}
</div>
);
}

View File

@@ -54,10 +54,9 @@ import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitive
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
import { getSignerColorStyles, useSignerColors } from '../../lib/signer-colors'; import { getSignerColorStyles, useSignerColors } from '../../lib/signer-colors';
import { Checkbox } from '../checkbox';
import type { FieldFormType } from '../document-flow/add-fields'; import type { FieldFormType } from '../document-flow/add-fields';
import { FieldAdvancedSettings } from '../document-flow/field-item-advanced-settings'; import { FieldAdvancedSettings } from '../document-flow/field-item-advanced-settings';
import { Form, FormControl, FormField, FormItem, FormLabel } from '../form/form'; import { Form } from '../form/form';
import { useStep } from '../stepper'; import { useStep } from '../stepper';
import type { TAddTemplateFieldsFormSchema } from './add-template-fields.types'; import type { TAddTemplateFieldsFormSchema } from './add-template-fields.types';
@@ -74,7 +73,6 @@ export type AddTemplateFieldsFormProps = {
fields: Field[]; fields: Field[];
onSubmit: (_data: TAddTemplateFieldsFormSchema) => void; onSubmit: (_data: TAddTemplateFieldsFormSchema) => void;
teamId?: number; teamId?: number;
typedSignatureEnabled?: boolean;
}; };
export const AddTemplateFieldsFormPartial = ({ export const AddTemplateFieldsFormPartial = ({
@@ -84,7 +82,6 @@ export const AddTemplateFieldsFormPartial = ({
fields, fields,
onSubmit, onSubmit,
teamId, teamId,
typedSignatureEnabled,
}: AddTemplateFieldsFormProps) => { }: AddTemplateFieldsFormProps) => {
const { _ } = useLingui(); const { _ } = useLingui();
const { toast } = useToast(); const { toast } = useToast();
@@ -119,7 +116,6 @@ export const AddTemplateFieldsFormPartial = ({
recipients.find((recipient) => recipient.id === field.recipientId)?.token ?? '', recipients.find((recipient) => recipient.id === field.recipientId)?.token ?? '',
fieldMeta: field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : undefined, fieldMeta: field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : undefined,
})), })),
typedSignatureEnabled: typedSignatureEnabled ?? false,
}, },
}); });
@@ -483,12 +479,6 @@ export const AddTemplateFieldsFormPartial = ({
form.setValue('fields', updatedFields); form.setValue('fields', updatedFields);
}; };
const isTypedSignatureEnabled = form.watch('typedSignatureEnabled');
const handleTypedSignatureChange = (value: boolean) => {
form.setValue('typedSignatureEnabled', value, { shouldDirty: true });
};
return ( return (
<> <>
{showAdvancedSettings && currentField ? ( {showAdvancedSettings && currentField ? (
@@ -662,31 +652,6 @@ export const AddTemplateFieldsFormPartial = ({
)} )}
<Form {...form}> <Form {...form}>
<FormField
control={form.control}
name="typedSignatureEnabled"
render={({ field: { value, ...field } }) => (
<FormItem className="mb-6 flex flex-row items-center space-x-2 space-y-0">
<FormControl>
<Checkbox
{...field}
id="typedSignatureEnabled"
checked={value}
onCheckedChange={(checked) => field.onChange(checked)}
disabled={form.formState.isSubmitting}
/>
</FormControl>
<FormLabel
htmlFor="typedSignatureEnabled"
className="text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
<Trans>Enable Typed Signatures</Trans>
</FormLabel>
</FormItem>
)}
/>
<div className="-mx-2 flex-1 overflow-y-auto px-2"> <div className="-mx-2 flex-1 overflow-y-auto px-2">
<fieldset className="my-2 grid grid-cols-3 gap-4"> <fieldset className="my-2 grid grid-cols-3 gap-4">
<button <button

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