mirror of
https://github.com/documenso/documenso.git
synced 2025-11-10 04:22:32 +10:00
feat: add signature configurations (#1710)
Add ability to enable or disable allowed signature types: - Drawn - Typed - Uploaded **Tabbed style signature dialog**  **Document settings**  **Team preferences**  - 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
This commit is contained in:
@ -3,8 +3,8 @@ import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
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 DocumentData, type Field, FieldType } from '@prisma/client';
|
||||
import { LucideChevronDown, LucideChevronUp } from 'lucide-react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { useSearchParams } from 'react-router';
|
||||
@ -25,12 +25,11 @@ import type {
|
||||
} from '@documenso/trpc/server/field-router/schema';
|
||||
import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||
import { ElementVisible } from '@documenso/ui/primitives/element-visible';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { Label } from '@documenso/ui/primitives/label';
|
||||
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 { BrandingLogo } from '~/components/general/branding-logo';
|
||||
@ -69,16 +68,8 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const {
|
||||
fullName,
|
||||
email,
|
||||
signature,
|
||||
signatureValid,
|
||||
setFullName,
|
||||
setEmail,
|
||||
setSignature,
|
||||
setSignatureValid,
|
||||
} = useRequiredDocumentSigningContext();
|
||||
const { fullName, email, signature, setFullName, setEmail, setSignature } =
|
||||
useRequiredDocumentSigningContext();
|
||||
|
||||
const [hasFinishedInit, setHasFinishedInit] = useState(false);
|
||||
const [hasDocumentLoaded, setHasDocumentLoaded] = useState(false);
|
||||
@ -194,10 +185,6 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
|
||||
const onCompleteClick = async () => {
|
||||
try {
|
||||
if (hasSignatureField && !signatureValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const valid = validateFieldsInserted(pendingFields);
|
||||
|
||||
if (!valid) {
|
||||
@ -419,34 +406,16 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
<Trans>Signature</Trans>
|
||||
</Label>
|
||||
|
||||
<Card className="mt-2" gradient degrees={-120}>
|
||||
<CardContent className="p-0">
|
||||
<SignaturePad
|
||||
className="h-44 w-full"
|
||||
disabled={isThrottled || isSubmitting}
|
||||
defaultValue={signature ?? undefined}
|
||||
onChange={(value) => {
|
||||
setSignature(value);
|
||||
}}
|
||||
onValidityChange={(isValid) => {
|
||||
setSignatureValid(isValid);
|
||||
}}
|
||||
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>
|
||||
)}
|
||||
<SignaturePadDialog
|
||||
className="mt-2"
|
||||
disabled={isThrottled || isSubmitting}
|
||||
disableAnimation
|
||||
value={signature ?? ''}
|
||||
onChange={(v) => setSignature(v ?? '')}
|
||||
typedSignatureEnabled={metadata?.typedSignatureEnabled}
|
||||
uploadSignatureEnabled={metadata?.uploadSignatureEnabled}
|
||||
drawSignatureEnabled={metadata?.drawSignatureEnabled}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -54,6 +54,8 @@ export const EmbedDocumentFields = ({
|
||||
onSignField={onSignField}
|
||||
onUnsignField={onUnsignField}
|
||||
typedSignatureEnabled={metadata?.typedSignatureEnabled}
|
||||
uploadSignatureEnabled={metadata?.uploadSignatureEnabled}
|
||||
drawSignatureEnabled={metadata?.drawSignatureEnabled}
|
||||
/>
|
||||
))
|
||||
.with(FieldType.INITIALS, () => (
|
||||
|
||||
@ -21,13 +21,12 @@ import type { RecipientWithFields } from '@documenso/prisma/types/recipient-with
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||
import { ElementVisible } from '@documenso/ui/primitives/element-visible';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { Label } from '@documenso/ui/primitives/label';
|
||||
import { PDFViewer } from '@documenso/ui/primitives/pdf-viewer';
|
||||
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 { BrandingLogo } from '~/components/general/branding-logo';
|
||||
@ -70,15 +69,8 @@ export const EmbedSignDocumentClientPage = ({
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const {
|
||||
fullName,
|
||||
email,
|
||||
signature,
|
||||
signatureValid,
|
||||
setFullName,
|
||||
setSignature,
|
||||
setSignatureValid,
|
||||
} = useRequiredDocumentSigningContext();
|
||||
const { fullName, email, signature, setFullName, setSignature } =
|
||||
useRequiredDocumentSigningContext();
|
||||
|
||||
const [hasFinishedInit, setHasFinishedInit] = useState(false);
|
||||
const [hasDocumentLoaded, setHasDocumentLoaded] = useState(false);
|
||||
@ -129,10 +121,6 @@ export const EmbedSignDocumentClientPage = ({
|
||||
|
||||
const onCompleteClick = async () => {
|
||||
try {
|
||||
if (hasSignatureField && !signatureValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const valid = validateFieldsInserted(fieldsRequiringValidation);
|
||||
|
||||
if (!valid) {
|
||||
@ -432,34 +420,16 @@ export const EmbedSignDocumentClientPage = ({
|
||||
<Trans>Signature</Trans>
|
||||
</Label>
|
||||
|
||||
<Card className="mt-2" gradient degrees={-120}>
|
||||
<CardContent className="p-0">
|
||||
<SignaturePad
|
||||
className="h-44 w-full"
|
||||
disabled={isThrottled || isSubmitting}
|
||||
defaultValue={signature ?? undefined}
|
||||
onChange={(value) => {
|
||||
setSignature(value);
|
||||
}}
|
||||
onValidityChange={(isValid) => {
|
||||
setSignatureValid(isValid);
|
||||
}}
|
||||
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>
|
||||
)}
|
||||
<SignaturePadDialog
|
||||
className="mt-2"
|
||||
disabled={isThrottled || isSubmitting}
|
||||
disableAnimation
|
||||
value={signature ?? ''}
|
||||
onChange={(v) => setSignature(v ?? '')}
|
||||
typedSignatureEnabled={metadata?.typedSignatureEnabled}
|
||||
uploadSignatureEnabled={metadata?.uploadSignatureEnabled}
|
||||
drawSignatureEnabled={metadata?.drawSignatureEnabled}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
@ -477,9 +447,7 @@ export const EmbedSignDocumentClientPage = ({
|
||||
) : (
|
||||
<Button
|
||||
className={allowDocumentRejection ? 'col-start-2' : 'col-span-2'}
|
||||
disabled={
|
||||
isThrottled || (!isAssistantMode && hasSignatureField && !signatureValid)
|
||||
}
|
||||
disabled={isThrottled}
|
||||
loading={isSubmitting}
|
||||
onClick={() => throttledOnCompleteClick()}
|
||||
>
|
||||
|
||||
@ -19,12 +19,15 @@ import {
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
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';
|
||||
|
||||
export const ZProfileFormSchema = z.object({
|
||||
name: z.string().trim().min(1, { message: 'Please enter a valid name.' }),
|
||||
signature: z.string().min(1, 'Signature Pad cannot be empty'),
|
||||
name: z
|
||||
.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({
|
||||
@ -109,22 +112,20 @@ export const ProfileForm = ({ className }: ProfileFormProps) => {
|
||||
</Label>
|
||||
<Input id="email" type="email" className="bg-muted mt-2" value={user.email} disabled />
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="signature"
|
||||
render={({ field: { onChange } }) => (
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>Signature</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<SignaturePad
|
||||
className="h-44 w-full"
|
||||
<SignaturePadDialog
|
||||
disabled={isSubmitting}
|
||||
containerClassName={cn('rounded-lg border bg-background')}
|
||||
defaultValue={user.signature ?? undefined}
|
||||
value={value}
|
||||
onChange={(v) => onChange(v ?? '')}
|
||||
allowTypedSignature={true}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@ -134,7 +135,7 @@ export const ProfileForm = ({ className }: ProfileFormProps) => {
|
||||
</fieldset>
|
||||
|
||||
<Button type="submit" loading={isSubmitting} className="self-end">
|
||||
{isSubmitting ? <Trans>Updating profile...</Trans> : <Trans>Update profile</Trans>}
|
||||
<Trans>Update profile</Trans>
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@ -30,7 +30,7 @@ import {
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/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 { UserProfileSkeleton } from '~/components/general/user-profile-skeleton';
|
||||
@ -353,16 +353,15 @@ export const SignUpForm = ({
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="signature"
|
||||
render={({ field: { onChange } }) => (
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>Sign Here</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<SignaturePad
|
||||
className="h-36 w-full"
|
||||
<SignaturePadDialog
|
||||
disabled={isSubmitting}
|
||||
containerClassName="mt-2 rounded-lg border bg-background"
|
||||
value={value}
|
||||
onChange={(v) => onChange(v ?? '')}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
@ -308,7 +308,7 @@ export function TeamBrandingPreferencesForm({ team, settings }: TeamBrandingPref
|
||||
|
||||
<div className="flex flex-row justify-end space-x-4">
|
||||
<Button type="submit" loading={form.formState.isSubmitting}>
|
||||
<Trans>Save</Trans>
|
||||
<Trans>Update</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
@ -8,12 +8,15 @@ import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { DOCUMENT_SIGNATURE_TYPES, DocumentSignatureType } from '@documenso/lib/constants/document';
|
||||
import {
|
||||
SUPPORTED_LANGUAGES,
|
||||
SUPPORTED_LANGUAGE_CODES,
|
||||
isValidLanguageCode,
|
||||
} from '@documenso/lib/constants/i18n';
|
||||
import { extractTeamSignatureSettings } from '@documenso/lib/utils/teams';
|
||||
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 { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
@ -23,7 +26,9 @@ import {
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { MultiSelectCombobox } from '@documenso/ui/primitives/multi-select-combobox';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@ -38,8 +43,10 @@ const ZTeamDocumentPreferencesFormSchema = z.object({
|
||||
documentVisibility: z.nativeEnum(DocumentVisibility),
|
||||
documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES),
|
||||
includeSenderDetails: z.boolean(),
|
||||
typedSignatureEnabled: 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>;
|
||||
@ -69,8 +76,8 @@ export const TeamDocumentPreferencesForm = ({
|
||||
? settings?.documentLanguage
|
||||
: 'en',
|
||||
includeSenderDetails: settings?.includeSenderDetails ?? false,
|
||||
typedSignatureEnabled: settings?.typedSignatureEnabled ?? true,
|
||||
includeSigningCertificate: settings?.includeSigningCertificate ?? true,
|
||||
signatureTypes: extractTeamSignatureSettings(settings),
|
||||
},
|
||||
resolver: zodResolver(ZTeamDocumentPreferencesFormSchema),
|
||||
});
|
||||
@ -84,7 +91,7 @@ export const TeamDocumentPreferencesForm = ({
|
||||
documentLanguage,
|
||||
includeSenderDetails,
|
||||
includeSigningCertificate,
|
||||
typedSignatureEnabled,
|
||||
signatureTypes,
|
||||
} = data;
|
||||
|
||||
await updateTeamDocumentPreferences({
|
||||
@ -93,8 +100,10 @@ export const TeamDocumentPreferencesForm = ({
|
||||
documentVisibility,
|
||||
documentLanguage,
|
||||
includeSenderDetails,
|
||||
typedSignatureEnabled,
|
||||
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
|
||||
control={form.control}
|
||||
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
|
||||
control={form.control}
|
||||
name="includeSigningCertificate"
|
||||
@ -301,7 +318,7 @@ export const TeamDocumentPreferencesForm = ({
|
||||
|
||||
<div className="flex flex-row justify-end space-x-4">
|
||||
<Button type="submit" loading={form.formState.isSubmitting}>
|
||||
<Trans>Save</Trans>
|
||||
<Trans>Update</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
@ -24,7 +24,6 @@ import type {
|
||||
} from '@documenso/trpc/server/field-router/schema';
|
||||
import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||
import {
|
||||
DocumentFlowFormContainerContent,
|
||||
DocumentFlowFormContainerFooter,
|
||||
@ -35,7 +34,7 @@ import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/ty
|
||||
import { ElementVisible } from '@documenso/ui/primitives/element-visible';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
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 { DocumentSigningCheckboxField } from '~/components/general/document-signing/document-signing-checkbox-field';
|
||||
@ -73,8 +72,7 @@ export const DirectTemplateSigningForm = ({
|
||||
template,
|
||||
onSubmit,
|
||||
}: DirectTemplateSigningFormProps) => {
|
||||
const { fullName, signature, signatureValid, setFullName, setSignature } =
|
||||
useRequiredDocumentSigningContext();
|
||||
const { fullName, signature, setFullName, setSignature } = useRequiredDocumentSigningContext();
|
||||
|
||||
const [localFields, setLocalFields] = useState<DirectTemplateLocalField[]>(directRecipientFields);
|
||||
const [validateUninsertedFields, setValidateUninsertedFields] = useState(false);
|
||||
@ -135,8 +133,6 @@ export const DirectTemplateSigningForm = ({
|
||||
);
|
||||
};
|
||||
|
||||
const hasSignatureField = localFields.some((field) => field.type === FieldType.SIGNATURE);
|
||||
|
||||
const uninsertedFields = useMemo(() => {
|
||||
return sortFieldsByPosition(localFields.filter((field) => !field.inserted));
|
||||
}, [localFields]);
|
||||
@ -149,10 +145,6 @@ export const DirectTemplateSigningForm = ({
|
||||
const handleSubmit = async () => {
|
||||
setValidateUninsertedFields(true);
|
||||
|
||||
if (hasSignatureField && !signatureValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isFieldsValid = validateFieldsInserted(localFields);
|
||||
|
||||
if (!isFieldsValid) {
|
||||
@ -240,6 +232,8 @@ export const DirectTemplateSigningForm = ({
|
||||
onSignField={onSignField}
|
||||
onUnsignField={onUnsignField}
|
||||
typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled}
|
||||
uploadSignatureEnabled={template.templateMeta?.uploadSignatureEnabled}
|
||||
drawSignatureEnabled={template.templateMeta?.drawSignatureEnabled}
|
||||
/>
|
||||
))
|
||||
.with(FieldType.INITIALS, () => (
|
||||
@ -384,19 +378,15 @@ export const DirectTemplateSigningForm = ({
|
||||
<Trans>Signature</Trans>
|
||||
</Label>
|
||||
|
||||
<Card className="mt-2" gradient degrees={-120}>
|
||||
<CardContent className="p-0">
|
||||
<SignaturePad
|
||||
className="h-44 w-full"
|
||||
disabled={isSubmitting}
|
||||
defaultValue={signature ?? undefined}
|
||||
onChange={(value) => {
|
||||
setSignature(value);
|
||||
}}
|
||||
allowTypedSignature={template.templateMeta?.typedSignatureEnabled}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<SignaturePadDialog
|
||||
className="mt-2"
|
||||
disabled={isSubmitting}
|
||||
value={signature ?? ''}
|
||||
onChange={(value) => setSignature(value)}
|
||||
typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled}
|
||||
uploadSignatureEnabled={template.templateMeta?.uploadSignatureEnabled}
|
||||
drawSignatureEnabled={template.templateMeta?.drawSignatureEnabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -20,11 +20,10 @@ import { trpc } from '@documenso/trpc/react';
|
||||
import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { Label } from '@documenso/ui/primitives/label';
|
||||
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 {
|
||||
@ -71,8 +70,7 @@ export const DocumentSigningForm = ({
|
||||
|
||||
const assistantSignersId = useId();
|
||||
|
||||
const { fullName, signature, setFullName, setSignature, signatureValid, setSignatureValid } =
|
||||
useRequiredDocumentSigningContext();
|
||||
const { fullName, signature, setFullName, setSignature } = useRequiredDocumentSigningContext();
|
||||
|
||||
const [validateUninsertedFields, setValidateUninsertedFields] = useState(false);
|
||||
const [isConfirmationDialogOpen, setIsConfirmationDialogOpen] = useState(false);
|
||||
@ -120,10 +118,6 @@ export const DocumentSigningForm = ({
|
||||
|
||||
const isFieldsValid = validateFieldsInserted(fieldsRequiringValidation);
|
||||
|
||||
if (hasSignatureField && !signatureValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isFieldsValid) {
|
||||
return;
|
||||
}
|
||||
@ -423,32 +417,15 @@ export const DocumentSigningForm = ({
|
||||
<Trans>Signature</Trans>
|
||||
</Label>
|
||||
|
||||
<Card className="mt-2" gradient degrees={-120}>
|
||||
<CardContent className="p-0">
|
||||
<SignaturePad
|
||||
className="h-44 w-full"
|
||||
disabled={isSubmitting}
|
||||
defaultValue={signature ?? undefined}
|
||||
onValidityChange={(isValid) => {
|
||||
setSignatureValid(isValid);
|
||||
}}
|
||||
onChange={(value) => {
|
||||
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>
|
||||
)}
|
||||
<SignaturePadDialog
|
||||
className="mt-2"
|
||||
disabled={isSubmitting}
|
||||
value={signature ?? ''}
|
||||
onChange={(v) => setSignature(v ?? '')}
|
||||
typedSignatureEnabled={document.documentMeta?.typedSignatureEnabled}
|
||||
uploadSignatureEnabled={document.documentMeta?.uploadSignatureEnabled}
|
||||
drawSignatureEnabled={document.documentMeta?.drawSignatureEnabled}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -177,6 +177,8 @@ export const DocumentSigningPageView = ({
|
||||
key={field.id}
|
||||
field={field}
|
||||
typedSignatureEnabled={documentMeta?.typedSignatureEnabled}
|
||||
uploadSignatureEnabled={documentMeta?.uploadSignatureEnabled}
|
||||
drawSignatureEnabled={documentMeta?.drawSignatureEnabled}
|
||||
/>
|
||||
))
|
||||
.with(FieldType.INITIALS, () => (
|
||||
|
||||
@ -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 = {
|
||||
fullName: string;
|
||||
@ -7,8 +9,6 @@ export type DocumentSigningContextValue = {
|
||||
setEmail: (_value: string) => void;
|
||||
signature: string | null;
|
||||
setSignature: (_value: string | null) => void;
|
||||
signatureValid: boolean;
|
||||
setSignatureValid: (_valid: boolean) => void;
|
||||
};
|
||||
|
||||
const DocumentSigningContext = createContext<DocumentSigningContextValue | null>(null);
|
||||
@ -31,6 +31,9 @@ export interface DocumentSigningProviderProps {
|
||||
fullName?: string | null;
|
||||
email?: string | null;
|
||||
signature?: string | null;
|
||||
typedSignatureEnabled?: boolean;
|
||||
uploadSignatureEnabled?: boolean;
|
||||
drawSignatureEnabled?: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
@ -38,18 +41,31 @@ export const DocumentSigningProvider = ({
|
||||
fullName: initialFullName,
|
||||
email: initialEmail,
|
||||
signature: initialSignature,
|
||||
typedSignatureEnabled = true,
|
||||
uploadSignatureEnabled = true,
|
||||
drawSignatureEnabled = true,
|
||||
children,
|
||||
}: DocumentSigningProviderProps) => {
|
||||
const [fullName, setFullName] = useState(initialFullName || '');
|
||||
const [email, setEmail] = useState(initialEmail || '');
|
||||
const [signature, setSignature] = useState(initialSignature || null);
|
||||
const [signatureValid, setSignatureValid] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialSignature) {
|
||||
setSignature(initialSignature);
|
||||
}
|
||||
}, [initialSignature]);
|
||||
// Ensure the user signature doesn't show up if it's not allowed.
|
||||
const [signature, setSignature] = useState(
|
||||
(() => {
|
||||
const sig = initialSignature || '';
|
||||
const isBase64 = isBase64Image(sig);
|
||||
|
||||
if (isBase64 && (uploadSignatureEnabled || drawSignatureEnabled)) {
|
||||
return sig;
|
||||
}
|
||||
|
||||
if (!isBase64 && typedSignatureEnabled) {
|
||||
return sig;
|
||||
}
|
||||
|
||||
return null;
|
||||
})(),
|
||||
);
|
||||
|
||||
return (
|
||||
<DocumentSigningContext.Provider
|
||||
@ -60,8 +76,6 @@ export const DocumentSigningProvider = ({
|
||||
setEmail,
|
||||
signature,
|
||||
setSignature,
|
||||
signatureValid,
|
||||
setSignatureValid,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@ -17,7 +17,6 @@ import type {
|
||||
} from '@documenso/trpc/server/field-router/schema';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
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 { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
@ -29,11 +28,14 @@ import { useRequiredDocumentSigningContext } from './document-signing-provider';
|
||||
import { useDocumentSigningRecipientContext } from './document-signing-recipient-provider';
|
||||
|
||||
type SignatureFieldState = 'empty' | 'signed-image' | 'signed-text';
|
||||
|
||||
export type DocumentSigningSignatureFieldProps = {
|
||||
field: FieldWithSignature;
|
||||
onSignField?: (value: TSignFieldWithTokenMutationSchema) => Promise<void> | void;
|
||||
onUnsignField?: (value: TRemovedSignedFieldWithTokenMutationSchema) => Promise<void> | void;
|
||||
typedSignatureEnabled?: boolean;
|
||||
uploadSignatureEnabled?: boolean;
|
||||
drawSignatureEnabled?: boolean;
|
||||
};
|
||||
|
||||
export const DocumentSigningSignatureField = ({
|
||||
@ -41,6 +43,8 @@ export const DocumentSigningSignatureField = ({
|
||||
onSignField,
|
||||
onUnsignField,
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
}: DocumentSigningSignatureFieldProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
@ -52,12 +56,8 @@ export const DocumentSigningSignatureField = ({
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [fontSize, setFontSize] = useState(2);
|
||||
|
||||
const {
|
||||
signature: providedSignature,
|
||||
setSignature: setProvidedSignature,
|
||||
signatureValid,
|
||||
setSignatureValid,
|
||||
} = useRequiredDocumentSigningContext();
|
||||
const { signature: providedSignature, setSignature: setProvidedSignature } =
|
||||
useRequiredDocumentSigningContext();
|
||||
|
||||
const { executeActionAuthProcedure } = useRequiredDocumentSigningAuthContext();
|
||||
|
||||
@ -89,7 +89,7 @@ export const DocumentSigningSignatureField = ({
|
||||
}, [field.inserted, signature?.signatureImageAsBase64]);
|
||||
|
||||
const onPreSign = () => {
|
||||
if (!providedSignature || !signatureValid) {
|
||||
if (!providedSignature) {
|
||||
setShowSignatureModal(true);
|
||||
return false;
|
||||
}
|
||||
@ -102,6 +102,7 @@ export const DocumentSigningSignatureField = ({
|
||||
const onDialogSignClick = () => {
|
||||
setShowSignatureModal(false);
|
||||
setProvidedSignature(localSignature);
|
||||
|
||||
if (!localSignature) {
|
||||
return;
|
||||
}
|
||||
@ -116,14 +117,14 @@ export const DocumentSigningSignatureField = ({
|
||||
try {
|
||||
const value = signature || providedSignature;
|
||||
|
||||
if (!value || (signature && !signatureValid)) {
|
||||
if (!value) {
|
||||
setShowSignatureModal(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const isTypedSignature = !value.startsWith('data:image');
|
||||
|
||||
if (isTypedSignature && !typedSignatureEnabled) {
|
||||
if (isTypedSignature && typedSignatureEnabled === false) {
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`Typed signatures are not allowed. Please draw your signature.`),
|
||||
@ -275,29 +276,14 @@ export const DocumentSigningSignatureField = ({
|
||||
</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<div className="">
|
||||
<Label htmlFor="signature">
|
||||
<Trans>Signature</Trans>
|
||||
</Label>
|
||||
|
||||
<div className="border-border mt-2 rounded-md border">
|
||||
<SignaturePad
|
||||
id="signature"
|
||||
className="h-44 w-full"
|
||||
onChange={(value) => setLocalSignature(value)}
|
||||
allowTypedSignature={typedSignatureEnabled}
|
||||
onValidityChange={(isValid) => {
|
||||
setSignatureValid(isValid);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{!signatureValid && (
|
||||
<div className="text-destructive mt-2 text-sm">
|
||||
<Trans>Signature is too small. Please provide a more complete signature.</Trans>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<SignaturePad
|
||||
className="mt-2"
|
||||
value={localSignature ?? ''}
|
||||
onChange={({ value }) => setLocalSignature(value)}
|
||||
typedSignatureEnabled={typedSignatureEnabled}
|
||||
uploadSignatureEnabled={uploadSignatureEnabled}
|
||||
drawSignatureEnabled={drawSignatureEnabled}
|
||||
/>
|
||||
|
||||
<DocumentSigningDisclosure />
|
||||
|
||||
@ -317,7 +303,7 @@ export const DocumentSigningSignatureField = ({
|
||||
<Button
|
||||
type="button"
|
||||
className="flex-1"
|
||||
disabled={!localSignature || !signatureValid}
|
||||
disabled={!localSignature}
|
||||
onClick={() => onDialogSignClick()}
|
||||
>
|
||||
<Trans>Sign</Trans>
|
||||
|
||||
@ -5,6 +5,7 @@ import { useLingui } from '@lingui/react';
|
||||
import { DocumentDistributionMethod, DocumentStatus } from '@prisma/client';
|
||||
import { useNavigate, useSearchParams } from 'react-router';
|
||||
|
||||
import { DocumentSignatureType } from '@documenso/lib/constants/document';
|
||||
import { isValidLanguageCode } from '@documenso/lib/constants/i18n';
|
||||
import {
|
||||
DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
@ -71,7 +72,7 @@ export const DocumentEditForm = ({
|
||||
|
||||
const { recipients, fields } = document;
|
||||
|
||||
const { mutateAsync: updateDocumentSettings } = trpc.document.setSettingsForDocument.useMutation({
|
||||
const { mutateAsync: updateDocument } = trpc.document.updateDocument.useMutation({
|
||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
onSuccess: (newData) => {
|
||||
utils.document.getDocumentWithDetailsById.setData(
|
||||
@ -174,9 +175,9 @@ export const DocumentEditForm = ({
|
||||
|
||||
const onAddSettingsFormSubmit = async (data: TAddSettingsFormSchema) => {
|
||||
try {
|
||||
const { timezone, dateFormat, redirectUrl, language } = data.meta;
|
||||
const { timezone, dateFormat, redirectUrl, language, signatureTypes } = data.meta;
|
||||
|
||||
await updateDocumentSettings({
|
||||
await updateDocument({
|
||||
documentId: document.id,
|
||||
data: {
|
||||
title: data.title,
|
||||
@ -190,6 +191,9 @@ export const DocumentEditForm = ({
|
||||
dateFormat,
|
||||
redirectUrl,
|
||||
language: isValidLanguageCode(language) ? language : undefined,
|
||||
typedSignatureEnabled: signatureTypes.includes(DocumentSignatureType.TYPE),
|
||||
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
|
||||
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
|
||||
},
|
||||
});
|
||||
|
||||
@ -213,7 +217,7 @@ export const DocumentEditForm = ({
|
||||
signingOrder: data.signingOrder,
|
||||
}),
|
||||
|
||||
updateDocumentSettings({
|
||||
updateDocument({
|
||||
documentId: document.id,
|
||||
meta: {
|
||||
allowDictateNextSigner: data.allowDictateNextSigner,
|
||||
@ -249,14 +253,6 @@ export const DocumentEditForm = ({
|
||||
fields: data.fields,
|
||||
});
|
||||
|
||||
await updateDocumentSettings({
|
||||
documentId: document.id,
|
||||
|
||||
meta: {
|
||||
typedSignatureEnabled: data.typedSignatureEnabled,
|
||||
},
|
||||
});
|
||||
|
||||
// Clear all field data from localStorage
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
@ -386,7 +382,6 @@ export const DocumentEditForm = ({
|
||||
fields={fields}
|
||||
onSubmit={onAddFieldsFormSubmit}
|
||||
isDocumentPdfLoaded={isDocumentPdfLoaded}
|
||||
typedSignatureEnabled={document.documentMeta?.typedSignatureEnabled}
|
||||
teamId={team?.id}
|
||||
/>
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
import { DocumentSignatureType } from '@documenso/lib/constants/document';
|
||||
import { isValidLanguageCode } from '@documenso/lib/constants/i18n';
|
||||
import {
|
||||
DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
@ -124,6 +125,8 @@ export const TemplateEditForm = ({
|
||||
});
|
||||
|
||||
const onAddSettingsFormSubmit = async (data: TAddTemplateSettingsFormSchema) => {
|
||||
const { signatureTypes } = data.meta;
|
||||
|
||||
try {
|
||||
await updateTemplateSettings({
|
||||
templateId: template.id,
|
||||
@ -136,6 +139,9 @@ export const TemplateEditForm = ({
|
||||
},
|
||||
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,
|
||||
},
|
||||
});
|
||||
@ -188,13 +194,6 @@ export const TemplateEditForm = ({
|
||||
fields: data.fields,
|
||||
});
|
||||
|
||||
await updateTemplateSettings({
|
||||
templateId: template.id,
|
||||
meta: {
|
||||
typedSignatureEnabled: data.typedSignatureEnabled,
|
||||
},
|
||||
});
|
||||
|
||||
// Clear all field data from localStorage
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
@ -286,7 +285,6 @@ export const TemplateEditForm = ({
|
||||
fields={fields}
|
||||
onSubmit={onAddFieldsFormSubmit}
|
||||
teamId={team?.id}
|
||||
typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled}
|
||||
/>
|
||||
</Stepper>
|
||||
</DocumentFlowFormContainer>
|
||||
|
||||
@ -79,7 +79,14 @@ export default function DirectTemplatePage() {
|
||||
const { template, directTemplateRecipient } = data;
|
||||
|
||||
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
|
||||
documentAuthOptions={template.authOptions}
|
||||
recipient={directTemplateRecipient}
|
||||
|
||||
@ -233,6 +233,9 @@ export default function SigningPage() {
|
||||
email={recipient.email}
|
||||
fullName={user?.email === recipient.email ? user?.name : recipient.name}
|
||||
signature={user?.email === recipient.email ? user?.signature : undefined}
|
||||
typedSignatureEnabled={document.documentMeta?.typedSignatureEnabled}
|
||||
uploadSignatureEnabled={document.documentMeta?.uploadSignatureEnabled}
|
||||
drawSignatureEnabled={document.documentMeta?.drawSignatureEnabled}
|
||||
>
|
||||
<DocumentSigningAuthProvider
|
||||
documentAuthOptions={document.authOptions}
|
||||
|
||||
@ -131,7 +131,14 @@ export default function EmbedDirectTemplatePage() {
|
||||
} = useSuperLoaderData<typeof loader>();
|
||||
|
||||
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
|
||||
documentAuthOptions={template.authOptions}
|
||||
recipient={recipient}
|
||||
|
||||
@ -156,6 +156,9 @@ export default function EmbedSignDocumentPage() {
|
||||
email={recipient.email}
|
||||
fullName={user?.email === recipient.email ? user?.name : recipient.name}
|
||||
signature={user?.email === recipient.email ? user?.signature : undefined}
|
||||
typedSignatureEnabled={document.documentMeta?.typedSignatureEnabled}
|
||||
uploadSignatureEnabled={document.documentMeta?.uploadSignatureEnabled}
|
||||
drawSignatureEnabled={document.documentMeta?.drawSignatureEnabled}
|
||||
>
|
||||
<DocumentSigningAuthProvider
|
||||
documentAuthOptions={document.authOptions}
|
||||
|
||||
@ -326,6 +326,8 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
allowDictateNextSigner: body.meta.allowDictateNextSigner,
|
||||
language: body.meta.language,
|
||||
typedSignatureEnabled: body.meta.typedSignatureEnabled,
|
||||
uploadSignatureEnabled: body.meta.uploadSignatureEnabled,
|
||||
drawSignatureEnabled: body.meta.drawSignatureEnabled,
|
||||
distributionMethod: body.meta.distributionMethod,
|
||||
emailSettings: body.meta.emailSettings,
|
||||
requestMetadata: metadata,
|
||||
|
||||
@ -158,6 +158,8 @@ export const ZCreateDocumentMutationSchema = z.object({
|
||||
allowDictateNextSigner: z.boolean().optional(),
|
||||
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
|
||||
typedSignatureEnabled: z.boolean().optional().default(true),
|
||||
uploadSignatureEnabled: z.boolean().optional().default(true),
|
||||
drawSignatureEnabled: z.boolean().optional().default(true),
|
||||
distributionMethod: z.nativeEnum(DocumentDistributionMethod).optional(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
})
|
||||
@ -291,6 +293,8 @@ export const ZGenerateDocumentFromTemplateMutationSchema = z.object({
|
||||
language: z.enum(SUPPORTED_LANGUAGE_CODES),
|
||||
distributionMethod: z.nativeEnum(DocumentDistributionMethod),
|
||||
typedSignatureEnabled: z.boolean(),
|
||||
uploadSignatureEnabled: z.boolean(),
|
||||
drawSignatureEnabled: z.boolean(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema,
|
||||
})
|
||||
.partial()
|
||||
|
||||
@ -246,7 +246,9 @@ test('[DOCUMENT_AUTH]: should allow field signing when required for recipient au
|
||||
});
|
||||
}
|
||||
|
||||
await signSignaturePad(page);
|
||||
if (fields.some((field) => field.type === FieldType.SIGNATURE)) {
|
||||
await signSignaturePad(page);
|
||||
}
|
||||
|
||||
for (const field of fields) {
|
||||
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
||||
@ -349,7 +351,9 @@ test('[DOCUMENT_AUTH]: should allow field signing when required for recipient an
|
||||
});
|
||||
}
|
||||
|
||||
await signSignaturePad(page);
|
||||
if (fields.some((field) => field.type === FieldType.SIGNATURE)) {
|
||||
await signSignaturePad(page);
|
||||
}
|
||||
|
||||
for (const field of fields) {
|
||||
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
||||
|
||||
@ -11,7 +11,7 @@ import { prisma } from '@documenso/prisma';
|
||||
import { seedPendingDocumentWithFullFields } from '@documenso/prisma/seed/documents';
|
||||
import { seedUser } from '@documenso/prisma/seed/users';
|
||||
|
||||
import { signSignaturePad } from '../fixtures/signature';
|
||||
import { signDirectSignaturePad, signSignaturePad } from '../fixtures/signature';
|
||||
|
||||
test('[NEXT_RECIPIENT_DICTATION]: should allow updating next recipient when dictation is enabled', async ({
|
||||
page,
|
||||
@ -322,7 +322,7 @@ test('[NEXT_RECIPIENT_DICTATION]: should allow assistant to dictate next signer'
|
||||
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
||||
|
||||
if (field.type === FieldType.SIGNATURE) {
|
||||
await signSignaturePad(page);
|
||||
await signDirectSignaturePad(page);
|
||||
await page.getByRole('button', { name: 'Sign', exact: true }).click();
|
||||
}
|
||||
|
||||
|
||||
@ -222,7 +222,10 @@ test.describe('Signing Certificate Tests', () => {
|
||||
|
||||
// Toggle signing certificate setting
|
||||
await page.getByLabel('Include the Signing Certificate in the Document').click();
|
||||
await page.getByRole('button', { name: /Save/ }).first().click();
|
||||
await page
|
||||
.getByRole('button', { name: /Update/ })
|
||||
.first()
|
||||
.click();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
@ -236,7 +239,10 @@ test.describe('Signing Certificate Tests', () => {
|
||||
|
||||
// Toggle the setting back to true
|
||||
await page.getByLabel('Include the Signing Certificate in the Document').click();
|
||||
await page.getByRole('button', { name: /Save/ }).first().click();
|
||||
await page
|
||||
.getByRole('button', { name: /Update/ })
|
||||
.first()
|
||||
.click();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
|
||||
@ -1,40 +1,28 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Will open the signature pad dialog and sign it.
|
||||
*/
|
||||
export const signSignaturePad = async (page: Page) => {
|
||||
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) {
|
||||
throw new Error('Signature pad not found');
|
||||
}
|
||||
|
||||
// Calculate center point
|
||||
const centerX = box.x + box.width / 2;
|
||||
const centerY = box.y + box.height / 2;
|
||||
|
||||
// Calculate square size (making it slightly smaller than the canvas)
|
||||
const squareSize = Math.min(box.width, box.height) * 0.4; // 40% of the smallest dimension
|
||||
|
||||
// Move to center
|
||||
await page.mouse.move(centerX, centerY);
|
||||
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();
|
||||
// Click Next button
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
};
|
||||
|
||||
/**
|
||||
* For when the signature pad is already open.
|
||||
*/
|
||||
export const signDirectSignaturePad = async (page: Page) => {
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// Click type tab
|
||||
await page.getByRole('tab', { name: 'Type' }).click();
|
||||
await page.getByTestId('signature-pad-type-input').fill('Signature');
|
||||
};
|
||||
|
||||
@ -23,7 +23,7 @@ test('[TEAMS]: update the default document visibility in the team global setting
|
||||
// !: Brittle selector
|
||||
await page.getByRole('combobox').first().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();
|
||||
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 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();
|
||||
await expect(toast).toBeVisible();
|
||||
|
||||
182
packages/app-tests/e2e/teams/team-signature-settings.spec.ts
Normal file
182
packages/app-tests/e2e/teams/team-signature-settings.spec.ts
Normal 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'));
|
||||
}
|
||||
});
|
||||
@ -298,6 +298,7 @@ test('[DIRECT_TEMPLATES]: use direct template link with 2 recipients', async ({
|
||||
await page.goto(formatDirectTemplatePath(template.directLink?.token || ''));
|
||||
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
await page.getByPlaceholder('recipient@documenso.com').fill(seedTestEmail());
|
||||
|
||||
await page.getByRole('button', { name: 'Continue' }).click();
|
||||
|
||||
@ -4,6 +4,7 @@ import { getUserByEmail } from '@documenso/lib/server-only/user/get-user-by-emai
|
||||
import { seedUser } from '@documenso/prisma/seed/users';
|
||||
|
||||
import { apiSignin } from '../fixtures/authentication';
|
||||
import { signSignaturePad } from '../fixtures/signature';
|
||||
|
||||
test('[USER] update full name', async ({ page }) => {
|
||||
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.getByPlaceholder('Type your signature').fill('John Doe');
|
||||
await signSignaturePad(page);
|
||||
|
||||
await page.getByRole('button', { name: 'Update profile' }).click();
|
||||
|
||||
|
||||
@ -34,3 +34,29 @@ export const DOCUMENT_DISTRIBUTION_METHODS: Record<string, DocumentDistributionM
|
||||
description: msg`None`,
|
||||
},
|
||||
} 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>;
|
||||
|
||||
4
packages/lib/constants/signatures.ts
Normal file
4
packages/lib/constants/signatures.ts
Normal 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,');
|
||||
@ -23,6 +23,8 @@ const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
|
||||
brandingHidePoweredBy: z.boolean(),
|
||||
teamId: z.number(),
|
||||
typedSignatureEnabled: z.boolean(),
|
||||
uploadSignatureEnabled: z.boolean(),
|
||||
drawSignatureEnabled: z.boolean(),
|
||||
})
|
||||
.nullish(),
|
||||
}),
|
||||
|
||||
@ -27,6 +27,8 @@ export type CreateDocumentMetaOptions = {
|
||||
allowDictateNextSigner?: boolean;
|
||||
distributionMethod?: DocumentDistributionMethod;
|
||||
typedSignatureEnabled?: boolean;
|
||||
uploadSignatureEnabled?: boolean;
|
||||
drawSignatureEnabled?: boolean;
|
||||
language?: SupportedLanguageCodes;
|
||||
requestMetadata: ApiRequestMetadata;
|
||||
};
|
||||
@ -46,6 +48,8 @@ export const upsertDocumentMeta = async ({
|
||||
emailSettings,
|
||||
distributionMethod,
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
language,
|
||||
requestMetadata,
|
||||
}: CreateDocumentMetaOptions) => {
|
||||
@ -99,6 +103,8 @@ export const upsertDocumentMeta = async ({
|
||||
emailSettings,
|
||||
distributionMethod,
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
language,
|
||||
},
|
||||
update: {
|
||||
@ -113,6 +119,8 @@ export const upsertDocumentMeta = async ({
|
||||
emailSettings,
|
||||
distributionMethod,
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
language,
|
||||
},
|
||||
});
|
||||
|
||||
@ -158,6 +158,10 @@ export const createDocumentV2 = async ({
|
||||
language: meta?.language || team?.teamGlobalSettings?.documentLanguage,
|
||||
typedSignatureEnabled:
|
||||
meta?.typedSignatureEnabled ?? team?.teamGlobalSettings?.typedSignatureEnabled,
|
||||
uploadSignatureEnabled:
|
||||
meta?.uploadSignatureEnabled ?? team?.teamGlobalSettings?.uploadSignatureEnabled,
|
||||
drawSignatureEnabled:
|
||||
meta?.drawSignatureEnabled ?? team?.teamGlobalSettings?.drawSignatureEnabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -128,8 +128,10 @@ export const createDocument = async ({
|
||||
documentMeta: {
|
||||
create: {
|
||||
language: team?.teamGlobalSettings?.documentLanguage,
|
||||
typedSignatureEnabled: team?.teamGlobalSettings?.typedSignatureEnabled,
|
||||
timezone: timezone,
|
||||
typedSignatureEnabled: team?.teamGlobalSettings?.typedSignatureEnabled ?? true,
|
||||
uploadSignatureEnabled: team?.teamGlobalSettings?.uploadSignatureEnabled ?? true,
|
||||
drawSignatureEnabled: team?.teamGlobalSettings?.drawSignatureEnabled ?? true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -201,7 +201,7 @@ export const signFieldWithToken = async ({
|
||||
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');
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -324,6 +324,9 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
language: metaLanguage,
|
||||
signingOrder: metaSigningOrder,
|
||||
distributionMethod: template.templateMeta?.distributionMethod,
|
||||
typedSignatureEnabled: template.templateMeta?.typedSignatureEnabled,
|
||||
uploadSignatureEnabled: template.templateMeta?.uploadSignatureEnabled,
|
||||
drawSignatureEnabled: template.templateMeta?.drawSignatureEnabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -96,6 +96,9 @@ export const createDocumentFromTemplateLegacy = async ({
|
||||
signingOrder: template.templateMeta?.signingOrder ?? undefined,
|
||||
language:
|
||||
template.templateMeta?.language || template.team?.teamGlobalSettings?.documentLanguage,
|
||||
typedSignatureEnabled: template.templateMeta?.typedSignatureEnabled,
|
||||
uploadSignatureEnabled: template.templateMeta?.uploadSignatureEnabled,
|
||||
drawSignatureEnabled: template.templateMeta?.drawSignatureEnabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -82,9 +82,11 @@ export type CreateDocumentFromTemplateOptions = {
|
||||
signingOrder?: DocumentSigningOrder;
|
||||
language?: SupportedLanguageCodes;
|
||||
distributionMethod?: DocumentDistributionMethod;
|
||||
typedSignatureEnabled?: boolean;
|
||||
allowDictateNextSigner?: boolean;
|
||||
emailSettings?: TDocumentEmailSettings;
|
||||
typedSignatureEnabled?: boolean;
|
||||
uploadSignatureEnabled?: boolean;
|
||||
drawSignatureEnabled?: boolean;
|
||||
};
|
||||
requestMetadata: ApiRequestMetadata;
|
||||
};
|
||||
@ -405,6 +407,10 @@ export const createDocumentFromTemplate = async ({
|
||||
template.team?.teamGlobalSettings?.documentLanguage,
|
||||
typedSignatureEnabled:
|
||||
override?.typedSignatureEnabled ?? template.templateMeta?.typedSignatureEnabled,
|
||||
uploadSignatureEnabled:
|
||||
override?.uploadSignatureEnabled ?? template.templateMeta?.uploadSignatureEnabled,
|
||||
drawSignatureEnabled:
|
||||
override?.drawSignatureEnabled ?? template.templateMeta?.drawSignatureEnabled,
|
||||
allowDictateNextSigner:
|
||||
override?.allowDictateNextSigner ??
|
||||
template.templateMeta?.allowDictateNextSigner ??
|
||||
|
||||
@ -4,6 +4,8 @@ import { prisma } from '@documenso/prisma';
|
||||
import { TemplateSchema } from '@documenso/prisma/generated/zod/modelSchema//TemplateSchema';
|
||||
import type { TCreateTemplateMutationSchema } from '@documenso/trpc/server/template-router/schema';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
|
||||
export type CreateTemplateOptions = TCreateTemplateMutationSchema & {
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
@ -19,8 +21,10 @@ export const createTemplate = async ({
|
||||
teamId,
|
||||
templateDocumentDataId,
|
||||
}: CreateTemplateOptions) => {
|
||||
let team = null;
|
||||
|
||||
if (teamId) {
|
||||
await prisma.team.findFirstOrThrow({
|
||||
team = await prisma.team.findFirst({
|
||||
where: {
|
||||
id: teamId,
|
||||
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({
|
||||
@ -38,6 +49,14 @@ export const createTemplate = async ({
|
||||
userId,
|
||||
templateDocumentDataId,
|
||||
teamId,
|
||||
templateMeta: {
|
||||
create: {
|
||||
language: team?.teamGlobalSettings?.documentLanguage,
|
||||
typedSignatureEnabled: team?.teamGlobalSettings?.typedSignatureEnabled ?? true,
|
||||
uploadSignatureEnabled: team?.teamGlobalSettings?.uploadSignatureEnabled ?? true,
|
||||
drawSignatureEnabled: team?.teamGlobalSettings?.drawSignatureEnabled ?? true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -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"
|
||||
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
|
||||
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."
|
||||
@ -465,6 +469,14 @@ msgstr "<0>Passkey erforderlich</0> - Der Empfänger muss ein Konto haben und de
|
||||
msgid "<0>Sender:</0> All"
|
||||
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
|
||||
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?"
|
||||
@ -898,6 +910,16 @@ msgstr "Alle Zeiten"
|
||||
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"
|
||||
|
||||
#: 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
|
||||
msgid "Allows authenticating using biometrics, password managers, hardware keys, etc."
|
||||
msgstr "Erlaubt die Authentifizierung mit biometrischen Daten, Passwort-Managern, Hardware-Schlüsseln usw."
|
||||
@ -1237,6 +1259,13 @@ msgstr ""
|
||||
msgid "Assisting"
|
||||
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
|
||||
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."
|
||||
@ -1309,11 +1338,11 @@ msgstr "Bitte bestätige vor dem Start deine E-Mail-Adresse, indem du auf den Bu
|
||||
msgid "Billing"
|
||||
msgstr "Abrechnung"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
|
||||
msgid "Black"
|
||||
msgstr "Schwarz"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
|
||||
msgid "Blue"
|
||||
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."
|
||||
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
|
||||
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."
|
||||
@ -1436,6 +1469,8 @@ msgstr ""
|
||||
#: apps/remix/app/components/dialogs/document-move-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/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/field-item-advanced-settings.tsx
|
||||
msgid "Cancel"
|
||||
@ -1522,7 +1557,7 @@ msgstr "Datei löschen"
|
||||
msgid "Clear filters"
|
||||
msgstr "Filter löschen"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
|
||||
msgid "Clear Signature"
|
||||
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/dialogs/public-profile-template-manage-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
|
||||
msgid "Continue"
|
||||
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."
|
||||
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
|
||||
msgid "Controls whether the recipients can sign the documents using a typed signature. Enable or disable the typed signature globally."
|
||||
msgstr "Legt fest, ob die Empfänger die Dokumente mit einer getippten Unterschrift unterschreiben können. Aktivieren oder deaktivieren Sie die getippte Unterschrift global."
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx
|
||||
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 ""
|
||||
|
||||
#: 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."
|
||||
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
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx
|
||||
msgid "Copied"
|
||||
@ -1975,6 +2015,10 @@ msgstr "Standardsprache des Dokuments"
|
||||
msgid "Default Document Visibility"
|
||||
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
|
||||
msgid "delete"
|
||||
msgstr "löschen"
|
||||
@ -2201,6 +2245,11 @@ msgstr "Dokument \"{0}\" - Abgelehnt von {1}"
|
||||
msgid "Document \"{0}\" - Rejection Confirmed"
|
||||
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/document-flow/add-settings.tsx
|
||||
#: packages/ui/components/document/document-global-auth-access-select.tsx
|
||||
@ -2349,6 +2398,10 @@ msgstr "Dokumentpräferenzen aktualisiert"
|
||||
msgid "Document re-sent"
|
||||
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/components/embed/embed-document-rejected.tsx
|
||||
#: packages/email/template-components/template-document-rejected.tsx
|
||||
@ -2486,6 +2539,10 @@ msgstr "Entwurfte Dokumente"
|
||||
msgid "Drag & drop your PDF here."
|
||||
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/document-flow/add-fields.tsx
|
||||
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-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-complete-dialog.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/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/team-email-update-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-fields.tsx
|
||||
@ -2646,15 +2705,6 @@ msgstr "Direktlink-Signierung aktivieren"
|
||||
msgid "Enable signing order"
|
||||
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.$id.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."
|
||||
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"
|
||||
msgstr "Grün"
|
||||
|
||||
@ -3483,10 +3533,12 @@ msgstr "Meine Vorlagen"
|
||||
#: apps/remix/app/components/tables/admin-dashboard-users-table.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-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/team-email-update-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-fields.tsx
|
||||
@ -3537,6 +3589,7 @@ msgstr "Neue Vorlage"
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx
|
||||
msgid "Next"
|
||||
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/forms/signup.tsx
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
msgid "Please enter a valid name."
|
||||
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"
|
||||
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
|
||||
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."
|
||||
@ -4063,6 +4113,10 @@ msgstr "Privat"
|
||||
msgid "Private templates can only be modified and viewed by you."
|
||||
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/components/general/settings-nav-mobile.tsx
|
||||
#: apps/remix/app/components/general/settings-nav-desktop.tsx
|
||||
@ -4140,6 +4194,10 @@ msgstr "Bereit"
|
||||
msgid "Reason"
|
||||
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
|
||||
msgid "Reason for rejection: "
|
||||
msgstr ""
|
||||
@ -4222,7 +4280,7 @@ msgstr "Wiederherstellungscode kopiert"
|
||||
msgid "Recovery codes"
|
||||
msgstr "Wiederherstellungscodes"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
|
||||
msgid "Red"
|
||||
msgstr "Rot"
|
||||
|
||||
@ -4243,8 +4301,11 @@ msgstr "Registrierung erfolgreich"
|
||||
msgid "Reject Document"
|
||||
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/document/document-status.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
msgid "Rejected"
|
||||
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-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
|
||||
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
|
||||
msgid "Save"
|
||||
@ -4740,7 +4799,6 @@ msgstr "Registrieren mit OIDC"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.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-form.tsx
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
@ -4757,12 +4815,17 @@ msgstr "Unterschrift"
|
||||
msgid "Signature ID"
|
||||
msgstr "Signatur-ID"
|
||||
|
||||
#: 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/embed/embed-document-signing-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
msgid "Signature is too small. Please provide a more complete signature."
|
||||
msgstr "Die Unterschrift ist zu klein. Bitte geben Sie eine vollständigere Unterschrift an."
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
|
||||
msgid "Signature is too small"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
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
|
||||
msgid "Signatures Collected"
|
||||
@ -4987,6 +5050,7 @@ msgid "Subject <0>(Optional)</0>"
|
||||
msgstr "Betreff <0>(Optional)</0>"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx
|
||||
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
|
||||
msgid "Submitting..."
|
||||
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"
|
||||
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+/settings+/webhooks.$id.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."
|
||||
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
|
||||
msgid "This document has been signed by all recipients"
|
||||
msgstr "Dieses Dokument wurde von allen Empfängern unterschrieben"
|
||||
@ -5730,6 +5802,10 @@ msgstr "Zeitzone"
|
||||
msgid "Title"
|
||||
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
|
||||
msgid "To accept this invitation you must create an account."
|
||||
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/admin-document-recipient-item-table.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
msgid "Type"
|
||||
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/_authenticated+/t.$teamUrl+/settings.billing.tsx
|
||||
msgid "Unknown"
|
||||
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-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/dialogs/team-member-update-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/team-email-update-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
|
||||
msgid "Update"
|
||||
msgstr "Aktualisieren"
|
||||
|
||||
@ -6039,6 +6120,8 @@ msgid "Update profile"
|
||||
msgstr "Profil aktualisieren"
|
||||
|
||||
#: 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"
|
||||
msgstr "Empfänger aktualisieren"
|
||||
|
||||
@ -6077,10 +6160,6 @@ msgstr "Webhook aktualisieren"
|
||||
msgid "Updating password..."
|
||||
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
|
||||
msgid "Updating Your Information"
|
||||
msgstr "Aktualisierung Ihrer Informationen"
|
||||
@ -6089,6 +6168,10 @@ msgstr "Aktualisierung Ihrer Informationen"
|
||||
msgid "Upgrade"
|
||||
msgstr "Upgrade"
|
||||
|
||||
#: packages/lib/constants/document.ts
|
||||
msgid "Upload"
|
||||
msgstr ""
|
||||
|
||||
#: 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."
|
||||
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"
|
||||
msgstr "Benutzerdefiniertes Dokument hochladen"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-upload.tsx
|
||||
msgid "Upload Signature"
|
||||
msgstr "Signatur hochladen"
|
||||
|
||||
@ -6287,6 +6370,7 @@ msgstr "Dokument anzeigen"
|
||||
#: 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/email/template-components/template-document-rejected.tsx
|
||||
#: packages/email/template-components/template-document-invite.tsx
|
||||
msgid "View Document"
|
||||
@ -6653,6 +6737,11 @@ msgstr "Willkommen bei Documenso!"
|
||||
msgid "Were you trying to edit this document instead?"
|
||||
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
|
||||
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."
|
||||
|
||||
@ -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"
|
||||
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
|
||||
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."
|
||||
@ -460,6 +464,14 @@ msgstr "<0>Require passkey</0> - The recipient must have an account and passkey
|
||||
msgid "<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
|
||||
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?"
|
||||
@ -893,6 +905,16 @@ msgstr "All Time"
|
||||
msgid "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
|
||||
msgid "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"
|
||||
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
|
||||
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."
|
||||
@ -1304,11 +1333,11 @@ msgstr "Before you get started, please confirm your email address by clicking th
|
||||
msgid "Billing"
|
||||
msgstr "Billing"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
|
||||
msgid "Black"
|
||||
msgstr "Black"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
|
||||
msgid "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."
|
||||
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
|
||||
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."
|
||||
@ -1431,6 +1464,8 @@ msgstr "Can prepare"
|
||||
#: apps/remix/app/components/dialogs/document-move-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/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/field-item-advanced-settings.tsx
|
||||
msgid "Cancel"
|
||||
@ -1517,7 +1552,7 @@ msgstr "Clear file"
|
||||
msgid "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"
|
||||
msgstr "Clear Signature"
|
||||
|
||||
@ -1692,6 +1727,7 @@ msgstr "Content"
|
||||
#: 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/passkey-create-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
|
||||
#: packages/ui/primitives/document-flow/document-flow-root.tsx
|
||||
msgid "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."
|
||||
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
|
||||
msgid "Controls whether the recipients can sign the documents using a typed signature. Enable or disable the typed signature globally."
|
||||
msgstr "Controls whether the recipients can sign the documents using a typed signature. Enable or disable the typed signature globally."
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx
|
||||
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 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
|
||||
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."
|
||||
|
||||
#: 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
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx
|
||||
msgid "Copied"
|
||||
@ -1970,6 +2010,10 @@ msgstr "Default Document Language"
|
||||
msgid "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
|
||||
msgid "delete"
|
||||
msgstr "delete"
|
||||
@ -2196,6 +2240,11 @@ msgstr "Document \"{0}\" - Rejected by {1}"
|
||||
msgid "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/document-flow/add-settings.tsx
|
||||
#: packages/ui/components/document/document-global-auth-access-select.tsx
|
||||
@ -2344,6 +2393,10 @@ msgstr "Document preferences updated"
|
||||
msgid "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/components/embed/embed-document-rejected.tsx
|
||||
#: packages/email/template-components/template-document-rejected.tsx
|
||||
@ -2481,6 +2534,10 @@ msgstr "Drafted Documents"
|
||||
msgid "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/document-flow/add-fields.tsx
|
||||
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-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-complete-dialog.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/profile.tsx
|
||||
@ -2548,6 +2606,7 @@ msgstr "Electronic Signature Disclosure"
|
||||
#: 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-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-fields.tsx
|
||||
@ -2641,15 +2700,6 @@ msgstr "Enable Direct Link Signing"
|
||||
msgid "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.$id.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."
|
||||
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"
|
||||
msgstr "Green"
|
||||
|
||||
@ -3478,10 +3528,12 @@ msgstr "My templates"
|
||||
#: apps/remix/app/components/tables/admin-dashboard-users-table.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-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/team-email-update-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-fields.tsx
|
||||
@ -3532,6 +3584,7 @@ msgstr "New Template"
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx
|
||||
msgid "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/forms/signup.tsx
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
msgid "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"
|
||||
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
|
||||
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."
|
||||
@ -4058,6 +4108,10 @@ msgstr "Private"
|
||||
msgid "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/components/general/settings-nav-mobile.tsx
|
||||
#: apps/remix/app/components/general/settings-nav-desktop.tsx
|
||||
@ -4135,6 +4189,10 @@ msgstr "Ready"
|
||||
msgid "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
|
||||
msgid "Reason for rejection: "
|
||||
msgstr "Reason for rejection: "
|
||||
@ -4217,7 +4275,7 @@ msgstr "Recovery code copied"
|
||||
msgid "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"
|
||||
msgstr "Red"
|
||||
|
||||
@ -4238,8 +4296,11 @@ msgstr "Registration Successful"
|
||||
msgid "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/document/document-status.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
msgid "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-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
|
||||
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
|
||||
msgid "Save"
|
||||
@ -4735,7 +4794,6 @@ msgstr "Sign Up with OIDC"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.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-form.tsx
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
@ -4752,12 +4810,17 @@ msgstr "Signature"
|
||||
msgid "Signature ID"
|
||||
msgstr "Signature ID"
|
||||
|
||||
#: 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/embed/embed-document-signing-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
msgid "Signature is too small. Please provide a more complete signature."
|
||||
msgstr "Signature is too small. Please provide a more complete signature."
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
|
||||
msgid "Signature is too small"
|
||||
msgstr "Signature is too small"
|
||||
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
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
|
||||
msgid "Signatures Collected"
|
||||
@ -4982,6 +5045,7 @@ msgid "Subject <0>(Optional)</0>"
|
||||
msgstr "Subject <0>(Optional)</0>"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx
|
||||
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
|
||||
msgid "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"
|
||||
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+/settings+/webhooks.$id.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."
|
||||
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
|
||||
msgid "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"
|
||||
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
|
||||
msgid "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/admin-document-recipient-item-table.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
msgid "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/_authenticated+/t.$teamUrl+/settings.billing.tsx
|
||||
msgid "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-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/dialogs/team-member-update-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/team-email-update-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
|
||||
msgid "Update"
|
||||
msgstr "Update"
|
||||
|
||||
@ -6036,6 +6117,8 @@ msgid "Update profile"
|
||||
msgstr "Update profile"
|
||||
|
||||
#: 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"
|
||||
msgstr "Update Recipient"
|
||||
|
||||
@ -6074,10 +6157,6 @@ msgstr "Update webhook"
|
||||
msgid "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
|
||||
msgid "Updating Your Information"
|
||||
msgstr "Updating Your Information"
|
||||
@ -6086,6 +6165,10 @@ msgstr "Updating Your Information"
|
||||
msgid "Upgrade"
|
||||
msgstr "Upgrade"
|
||||
|
||||
#: packages/lib/constants/document.ts
|
||||
msgid "Upload"
|
||||
msgstr "Upload"
|
||||
|
||||
#: 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."
|
||||
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"
|
||||
msgstr "Upload custom document"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-upload.tsx
|
||||
msgid "Upload Signature"
|
||||
msgstr "Upload Signature"
|
||||
|
||||
@ -6284,6 +6367,7 @@ msgstr "View document"
|
||||
#: 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/email/template-components/template-document-rejected.tsx
|
||||
#: packages/email/template-components/template-document-invite.tsx
|
||||
msgid "View Document"
|
||||
@ -6650,6 +6734,11 @@ msgstr "Welcome to Documenso!"
|
||||
msgid "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
|
||||
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."
|
||||
|
||||
@ -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"
|
||||
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
|
||||
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."
|
||||
@ -465,6 +469,14 @@ msgstr "<0>Requerir clave de acceso</0> - El destinatario debe tener una cuenta
|
||||
msgid "<0>Sender:</0> All"
|
||||
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
|
||||
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?"
|
||||
@ -898,6 +910,16 @@ msgstr "Todo el Tiempo"
|
||||
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"
|
||||
|
||||
#: 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
|
||||
msgid "Allows authenticating using biometrics, password managers, hardware keys, etc."
|
||||
msgstr "Permite autenticarse usando biometría, administradores de contraseñas, claves de hardware, etc."
|
||||
@ -1237,6 +1259,13 @@ msgstr ""
|
||||
msgid "Assisting"
|
||||
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
|
||||
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."
|
||||
@ -1309,11 +1338,11 @@ msgstr "Antes de comenzar, por favor confirma tu dirección de correo electróni
|
||||
msgid "Billing"
|
||||
msgstr "Facturación"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
|
||||
msgid "Black"
|
||||
msgstr "Negro"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
|
||||
msgid "Blue"
|
||||
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."
|
||||
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
|
||||
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."
|
||||
@ -1436,6 +1469,8 @@ msgstr ""
|
||||
#: apps/remix/app/components/dialogs/document-move-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/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/field-item-advanced-settings.tsx
|
||||
msgid "Cancel"
|
||||
@ -1522,7 +1557,7 @@ msgstr "Limpiar archivo"
|
||||
msgid "Clear filters"
|
||||
msgstr "Limpiar filtros"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
|
||||
msgid "Clear Signature"
|
||||
msgstr "Limpiar firma"
|
||||
|
||||
@ -1697,6 +1732,7 @@ msgstr "Contenido"
|
||||
#: 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/passkey-create-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
|
||||
#: packages/ui/primitives/document-flow/document-flow-root.tsx
|
||||
msgid "Continue"
|
||||
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."
|
||||
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
|
||||
msgid "Controls whether the recipients can sign the documents using a typed signature. Enable or disable the typed signature globally."
|
||||
msgstr "Controla si los destinatarios pueden firmar los documentos utilizando una firma mecanografiada. Habilitar o deshabilitar la firma mecanografiada globalmente."
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx
|
||||
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 ""
|
||||
|
||||
#: 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."
|
||||
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
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx
|
||||
msgid "Copied"
|
||||
@ -1975,6 +2015,10 @@ msgstr "Idioma predeterminado del documento"
|
||||
msgid "Default Document Visibility"
|
||||
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
|
||||
msgid "delete"
|
||||
msgstr "eliminar"
|
||||
@ -2201,6 +2245,11 @@ msgstr "Documento \"{0}\" - Rechazado por {1}"
|
||||
msgid "Document \"{0}\" - Rejection Confirmed"
|
||||
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/document-flow/add-settings.tsx
|
||||
#: packages/ui/components/document/document-global-auth-access-select.tsx
|
||||
@ -2349,6 +2398,10 @@ msgstr "Preferencias del documento actualizadas"
|
||||
msgid "Document re-sent"
|
||||
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/components/embed/embed-document-rejected.tsx
|
||||
#: packages/email/template-components/template-document-rejected.tsx
|
||||
@ -2486,6 +2539,10 @@ msgstr "Documentos redactados"
|
||||
msgid "Drag & drop your PDF here."
|
||||
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/document-flow/add-fields.tsx
|
||||
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-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-complete-dialog.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/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/team-email-update-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-fields.tsx
|
||||
@ -2646,15 +2705,6 @@ msgstr "Habilitar firma de enlace directo"
|
||||
msgid "Enable signing order"
|
||||
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.$id.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."
|
||||
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"
|
||||
msgstr "Verde"
|
||||
|
||||
@ -3483,10 +3533,12 @@ msgstr "Mis plantillas"
|
||||
#: apps/remix/app/components/tables/admin-dashboard-users-table.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-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/team-email-update-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-fields.tsx
|
||||
@ -3537,6 +3589,7 @@ msgstr "Nueva plantilla"
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx
|
||||
msgid "Next"
|
||||
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/forms/signup.tsx
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
msgid "Please enter a valid name."
|
||||
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"
|
||||
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
|
||||
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."
|
||||
@ -4063,6 +4113,10 @@ msgstr "Privado"
|
||||
msgid "Private templates can only be modified and viewed by you."
|
||||
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/components/general/settings-nav-mobile.tsx
|
||||
#: apps/remix/app/components/general/settings-nav-desktop.tsx
|
||||
@ -4140,6 +4194,10 @@ msgstr "Listo"
|
||||
msgid "Reason"
|
||||
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
|
||||
msgid "Reason for rejection: "
|
||||
msgstr ""
|
||||
@ -4222,7 +4280,7 @@ msgstr "Código de recuperación copiado"
|
||||
msgid "Recovery codes"
|
||||
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"
|
||||
msgstr "Rojo"
|
||||
|
||||
@ -4243,8 +4301,11 @@ msgstr "Registro exitoso"
|
||||
msgid "Reject Document"
|
||||
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/document/document-status.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
msgid "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-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
|
||||
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
|
||||
msgid "Save"
|
||||
@ -4740,7 +4799,6 @@ msgstr "Regístrate con OIDC"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.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-form.tsx
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
@ -4757,12 +4815,17 @@ msgstr "Firma"
|
||||
msgid "Signature ID"
|
||||
msgstr "ID de Firma"
|
||||
|
||||
#: 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/embed/embed-document-signing-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
msgid "Signature is too small. Please provide a more complete signature."
|
||||
msgstr "La firma es demasiado pequeña. Proporcione una firma más completa."
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
|
||||
msgid "Signature is too small"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
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
|
||||
msgid "Signatures Collected"
|
||||
@ -4987,6 +5050,7 @@ msgid "Subject <0>(Optional)</0>"
|
||||
msgstr "Asunto <0>(Opcional)</0>"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx
|
||||
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
|
||||
msgid "Submitting..."
|
||||
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"
|
||||
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+/settings+/webhooks.$id.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."
|
||||
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
|
||||
msgid "This document has been signed by all recipients"
|
||||
msgstr "Este documento ha sido firmado por todos los destinatarios"
|
||||
@ -5730,6 +5802,10 @@ msgstr "Zona horaria"
|
||||
msgid "Title"
|
||||
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
|
||||
msgid "To accept this invitation you must create an account."
|
||||
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/admin-document-recipient-item-table.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
msgid "Type"
|
||||
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/_authenticated+/t.$teamUrl+/settings.billing.tsx
|
||||
msgid "Unknown"
|
||||
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-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/dialogs/team-member-update-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/team-email-update-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
|
||||
msgid "Update"
|
||||
msgstr "Actualizar"
|
||||
|
||||
@ -6039,6 +6120,8 @@ msgid "Update profile"
|
||||
msgstr "Actualizar perfil"
|
||||
|
||||
#: 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"
|
||||
msgstr "Actualizar destinatario"
|
||||
|
||||
@ -6077,10 +6160,6 @@ msgstr "Actualizar webhook"
|
||||
msgid "Updating password..."
|
||||
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
|
||||
msgid "Updating Your Information"
|
||||
msgstr "Actualizando Su Información"
|
||||
@ -6089,6 +6168,10 @@ msgstr "Actualizando Su Información"
|
||||
msgid "Upgrade"
|
||||
msgstr "Actualizar"
|
||||
|
||||
#: packages/lib/constants/document.ts
|
||||
msgid "Upload"
|
||||
msgstr ""
|
||||
|
||||
#: 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."
|
||||
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"
|
||||
msgstr "Subir documento personalizado"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-upload.tsx
|
||||
msgid "Upload Signature"
|
||||
msgstr "Subir firma"
|
||||
|
||||
@ -6287,6 +6370,7 @@ msgstr "Ver documento"
|
||||
#: 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/email/template-components/template-document-rejected.tsx
|
||||
#: packages/email/template-components/template-document-invite.tsx
|
||||
msgid "View Document"
|
||||
@ -6653,6 +6737,11 @@ msgstr "¡Bienvenido a Documenso!"
|
||||
msgid "Were you trying to edit this document instead?"
|
||||
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
|
||||
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."
|
||||
|
||||
@ -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"
|
||||
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
|
||||
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."
|
||||
@ -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"
|
||||
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
|
||||
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 ?"
|
||||
@ -898,6 +910,16 @@ msgstr "Depuis toujours"
|
||||
msgid "Allow document recipients to reply directly to this email address"
|
||||
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
|
||||
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."
|
||||
@ -1237,6 +1259,13 @@ msgstr ""
|
||||
msgid "Assisting"
|
||||
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
|
||||
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é."
|
||||
@ -1309,11 +1338,11 @@ msgstr "Avant de commencer, veuillez confirmer votre adresse e-mail en cliquant
|
||||
msgid "Billing"
|
||||
msgstr "Facturation"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
|
||||
msgid "Black"
|
||||
msgstr "Noir"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
|
||||
msgid "Blue"
|
||||
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."
|
||||
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
|
||||
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."
|
||||
@ -1436,6 +1469,8 @@ msgstr ""
|
||||
#: apps/remix/app/components/dialogs/document-move-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/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/field-item-advanced-settings.tsx
|
||||
msgid "Cancel"
|
||||
@ -1522,7 +1557,7 @@ msgstr "Effacer le fichier"
|
||||
msgid "Clear filters"
|
||||
msgstr "Effacer les filtres"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
|
||||
msgid "Clear 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/dialogs/public-profile-template-manage-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
|
||||
msgid "Continue"
|
||||
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."
|
||||
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
|
||||
msgid "Controls whether the recipients can sign the documents using a typed signature. Enable or disable the typed signature globally."
|
||||
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."
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx
|
||||
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 ""
|
||||
|
||||
#: 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."
|
||||
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
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx
|
||||
msgid "Copied"
|
||||
@ -1975,6 +2015,10 @@ msgstr "Langue par défaut du document"
|
||||
msgid "Default Document Visibility"
|
||||
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
|
||||
msgid "delete"
|
||||
msgstr "supprimer"
|
||||
@ -2201,6 +2245,11 @@ msgstr "Document \"{0}\" - Rejeté par {1}"
|
||||
msgid "Document \"{0}\" - Rejection Confirmed"
|
||||
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/document-flow/add-settings.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"
|
||||
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/components/embed/embed-document-rejected.tsx
|
||||
#: packages/email/template-components/template-document-rejected.tsx
|
||||
@ -2486,6 +2539,10 @@ msgstr "Documents brouillon"
|
||||
msgid "Drag & drop your PDF here."
|
||||
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/document-flow/add-fields.tsx
|
||||
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-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-complete-dialog.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/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/team-email-update-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-fields.tsx
|
||||
@ -2646,15 +2705,6 @@ msgstr "Activer la signature de lien direct"
|
||||
msgid "Enable signing order"
|
||||
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.$id.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."
|
||||
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"
|
||||
msgstr "Vert"
|
||||
|
||||
@ -3483,10 +3533,12 @@ msgstr "Mes modèles"
|
||||
#: apps/remix/app/components/tables/admin-dashboard-users-table.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-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/team-email-update-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-fields.tsx
|
||||
@ -3537,6 +3589,7 @@ msgstr "Nouveau modèle"
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx
|
||||
msgid "Next"
|
||||
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/forms/signup.tsx
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
msgid "Please enter a valid name."
|
||||
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"
|
||||
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
|
||||
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."
|
||||
@ -4063,6 +4113,10 @@ msgstr "Privé"
|
||||
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."
|
||||
|
||||
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
|
||||
msgid "Proceed"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/profile.tsx
|
||||
#: apps/remix/app/components/general/settings-nav-mobile.tsx
|
||||
#: apps/remix/app/components/general/settings-nav-desktop.tsx
|
||||
@ -4140,6 +4194,10 @@ msgstr "Prêt"
|
||||
msgid "Reason"
|
||||
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
|
||||
msgid "Reason for rejection: "
|
||||
msgstr ""
|
||||
@ -4222,7 +4280,7 @@ msgstr "Code de récupération copié"
|
||||
msgid "Recovery codes"
|
||||
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"
|
||||
msgstr "Rouge"
|
||||
|
||||
@ -4243,8 +4301,11 @@ msgstr "Inscription réussie"
|
||||
msgid "Reject 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/document/document-status.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
msgid "Rejected"
|
||||
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-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
|
||||
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
|
||||
msgid "Save"
|
||||
@ -4740,7 +4799,6 @@ msgstr "S'inscrire avec OIDC"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.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-form.tsx
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
@ -4757,12 +4815,17 @@ msgstr "Signature"
|
||||
msgid "Signature ID"
|
||||
msgstr "ID de signature"
|
||||
|
||||
#: 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/embed/embed-document-signing-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
msgid "Signature is too small. Please provide a more complete signature."
|
||||
msgstr "La signature est trop petite. Veuillez fournir une signature plus grande."
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
|
||||
msgid "Signature is too small"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
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
|
||||
msgid "Signatures Collected"
|
||||
@ -4987,6 +5050,7 @@ msgid "Subject <0>(Optional)</0>"
|
||||
msgstr "Objet <0>(Optionnel)</0>"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx
|
||||
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
|
||||
msgid "Submitting..."
|
||||
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"
|
||||
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+/settings+/webhooks.$id.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."
|
||||
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
|
||||
msgid "This document has been signed by all recipients"
|
||||
msgstr "Ce document a été signé par tous les destinataires"
|
||||
@ -5730,6 +5802,10 @@ msgstr "Fuseau horaire"
|
||||
msgid "Title"
|
||||
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
|
||||
msgid "To accept this invitation you must create an account."
|
||||
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/admin-document-recipient-item-table.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
msgid "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/_authenticated+/t.$teamUrl+/settings.billing.tsx
|
||||
msgid "Unknown"
|
||||
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-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/dialogs/team-member-update-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/team-email-update-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
|
||||
msgid "Update"
|
||||
msgstr "Mettre à jour"
|
||||
|
||||
@ -6039,6 +6120,8 @@ msgid "Update profile"
|
||||
msgstr "Mettre à jour le profil"
|
||||
|
||||
#: 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"
|
||||
msgstr "Mettre à jour le destinataire"
|
||||
|
||||
@ -6077,10 +6160,6 @@ msgstr "Mettre à jour le webhook"
|
||||
msgid "Updating password..."
|
||||
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
|
||||
msgid "Updating Your Information"
|
||||
msgstr "Mise à jour de vos informations"
|
||||
@ -6089,6 +6168,10 @@ msgstr "Mise à jour de vos informations"
|
||||
msgid "Upgrade"
|
||||
msgstr "Améliorer"
|
||||
|
||||
#: packages/lib/constants/document.ts
|
||||
msgid "Upload"
|
||||
msgstr ""
|
||||
|
||||
#: 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."
|
||||
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"
|
||||
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"
|
||||
msgstr "Importer une signature"
|
||||
|
||||
@ -6287,6 +6370,7 @@ msgstr "Voir le document"
|
||||
#: 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/email/template-components/template-document-rejected.tsx
|
||||
#: packages/email/template-components/template-document-invite.tsx
|
||||
msgid "View Document"
|
||||
@ -6653,6 +6737,11 @@ msgstr "Bienvenue sur Documenso !"
|
||||
msgid "Were you trying to edit this document instead?"
|
||||
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
|
||||
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."
|
||||
|
||||
@ -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"
|
||||
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
|
||||
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."
|
||||
@ -465,6 +469,14 @@ msgstr "<0>Richiede passkey</0> - Il destinatario deve avere un account e una pa
|
||||
msgid "<0>Sender:</0> All"
|
||||
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
|
||||
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?"
|
||||
@ -898,6 +910,16 @@ msgstr "Tutto il tempo"
|
||||
msgid "Allow document recipients to reply directly to this email address"
|
||||
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
|
||||
msgid "Allows authenticating using biometrics, password managers, hardware keys, etc."
|
||||
msgstr "Consente di autenticare utilizzando biometria, gestori di password, chiavi hardware, ecc."
|
||||
@ -1237,6 +1259,13 @@ msgstr ""
|
||||
msgid "Assisting"
|
||||
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
|
||||
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."
|
||||
@ -1309,11 +1338,11 @@ msgstr "Prima di iniziare, conferma il tuo indirizzo email facendo clic sul puls
|
||||
msgid "Billing"
|
||||
msgstr "Fatturazione"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
|
||||
msgid "Black"
|
||||
msgstr "Nero"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
|
||||
msgid "Blue"
|
||||
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."
|
||||
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
|
||||
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."
|
||||
@ -1436,6 +1469,8 @@ msgstr ""
|
||||
#: apps/remix/app/components/dialogs/document-move-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/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/field-item-advanced-settings.tsx
|
||||
msgid "Cancel"
|
||||
@ -1522,7 +1557,7 @@ msgstr "Rimuovi file"
|
||||
msgid "Clear filters"
|
||||
msgstr "Cancella filtri"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
|
||||
msgid "Clear Signature"
|
||||
msgstr "Cancella firma"
|
||||
|
||||
@ -1697,6 +1732,7 @@ msgstr "Contenuto"
|
||||
#: 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/passkey-create-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
|
||||
#: packages/ui/primitives/document-flow/document-flow-root.tsx
|
||||
msgid "Continue"
|
||||
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."
|
||||
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
|
||||
msgid "Controls whether the recipients can sign the documents using a typed signature. Enable or disable the typed signature globally."
|
||||
msgstr "Controlla se i destinatari possono firmare i documenti utilizzando una firma digitata. Abilita o disabilita la firma digitata a livello globale."
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx
|
||||
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 ""
|
||||
|
||||
#: 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."
|
||||
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
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx
|
||||
msgid "Copied"
|
||||
@ -1975,6 +2015,10 @@ msgstr "Lingua predefinita del documento"
|
||||
msgid "Default Document Visibility"
|
||||
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
|
||||
msgid "delete"
|
||||
msgstr "elimina"
|
||||
@ -2201,6 +2245,11 @@ msgstr "Documento \"{0}\" - Rifiutato da {1}"
|
||||
msgid "Document \"{0}\" - Rejection Confirmed"
|
||||
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/document-flow/add-settings.tsx
|
||||
#: packages/ui/components/document/document-global-auth-access-select.tsx
|
||||
@ -2349,6 +2398,10 @@ msgstr "Preferenze del documento aggiornate"
|
||||
msgid "Document re-sent"
|
||||
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/components/embed/embed-document-rejected.tsx
|
||||
#: packages/email/template-components/template-document-rejected.tsx
|
||||
@ -2486,6 +2539,10 @@ msgstr "Documenti redatti"
|
||||
msgid "Drag & drop your PDF here."
|
||||
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/document-flow/add-fields.tsx
|
||||
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-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-complete-dialog.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/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/team-email-update-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-fields.tsx
|
||||
@ -2646,15 +2705,6 @@ msgstr "Abilita la firma di link diretto"
|
||||
msgid "Enable signing order"
|
||||
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.$id.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."
|
||||
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"
|
||||
msgstr "Verde"
|
||||
|
||||
@ -3483,10 +3533,12 @@ msgstr "I miei modelli"
|
||||
#: apps/remix/app/components/tables/admin-dashboard-users-table.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-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/team-email-update-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-fields.tsx
|
||||
@ -3537,6 +3589,7 @@ msgstr "Nuovo modello"
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx
|
||||
msgid "Next"
|
||||
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/forms/signup.tsx
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
msgid "Please enter a valid name."
|
||||
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"
|
||||
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
|
||||
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."
|
||||
@ -4063,6 +4113,10 @@ msgstr "Privato"
|
||||
msgid "Private templates can only be modified and viewed by you."
|
||||
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/components/general/settings-nav-mobile.tsx
|
||||
#: apps/remix/app/components/general/settings-nav-desktop.tsx
|
||||
@ -4140,6 +4194,10 @@ msgstr "Pronto"
|
||||
msgid "Reason"
|
||||
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
|
||||
msgid "Reason for rejection: "
|
||||
msgstr ""
|
||||
@ -4222,7 +4280,7 @@ msgstr "Codice di recupero copiato"
|
||||
msgid "Recovery codes"
|
||||
msgstr "Codici di recupero"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
|
||||
msgid "Red"
|
||||
msgstr "Rosso"
|
||||
|
||||
@ -4243,8 +4301,11 @@ msgstr "Registrazione avvenuta con successo"
|
||||
msgid "Reject Document"
|
||||
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/document/document-status.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
msgid "Rejected"
|
||||
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-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
|
||||
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
|
||||
msgid "Save"
|
||||
@ -4740,7 +4799,6 @@ msgstr "Iscriviti con OIDC"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.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-form.tsx
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
@ -4757,12 +4815,17 @@ msgstr "Firma"
|
||||
msgid "Signature ID"
|
||||
msgstr "ID Firma"
|
||||
|
||||
#: 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/embed/embed-document-signing-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
msgid "Signature is too small. Please provide a more complete signature."
|
||||
msgstr "La firma è troppo piccola. Si prega di fornire una firma più completa."
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
|
||||
msgid "Signature is too small"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
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
|
||||
msgid "Signatures Collected"
|
||||
@ -4987,6 +5050,7 @@ msgid "Subject <0>(Optional)</0>"
|
||||
msgstr "Oggetto <0>(Opzionale)</0>"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx
|
||||
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
|
||||
msgid "Submitting..."
|
||||
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"
|
||||
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+/settings+/webhooks.$id.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."
|
||||
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
|
||||
msgid "This document has been signed by all recipients"
|
||||
msgstr "Questo documento è stato firmato da tutti i destinatari"
|
||||
@ -5730,6 +5802,10 @@ msgstr "Fuso orario"
|
||||
msgid "Title"
|
||||
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
|
||||
msgid "To accept this invitation you must create an 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/admin-document-recipient-item-table.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
msgid "Type"
|
||||
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/_authenticated+/t.$teamUrl+/settings.billing.tsx
|
||||
msgid "Unknown"
|
||||
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-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/dialogs/team-member-update-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/team-email-update-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
|
||||
msgid "Update"
|
||||
msgstr "Aggiorna"
|
||||
|
||||
@ -6039,6 +6120,8 @@ msgid "Update profile"
|
||||
msgstr "Aggiorna profilo"
|
||||
|
||||
#: 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"
|
||||
msgstr "Aggiorna destinatario"
|
||||
|
||||
@ -6077,10 +6160,6 @@ msgstr "Aggiorna webhook"
|
||||
msgid "Updating 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
|
||||
msgid "Updating Your Information"
|
||||
msgstr "Aggiornamento delle tue informazioni"
|
||||
@ -6089,6 +6168,10 @@ msgstr "Aggiornamento delle tue informazioni"
|
||||
msgid "Upgrade"
|
||||
msgstr "Aggiorna"
|
||||
|
||||
#: packages/lib/constants/document.ts
|
||||
msgid "Upload"
|
||||
msgstr ""
|
||||
|
||||
#: 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."
|
||||
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"
|
||||
msgstr "Carica documento personalizzato"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-upload.tsx
|
||||
msgid "Upload Signature"
|
||||
msgstr "Carica Firma"
|
||||
|
||||
@ -6287,6 +6370,7 @@ msgstr "Visualizza documento"
|
||||
#: 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/email/template-components/template-document-rejected.tsx
|
||||
#: packages/email/template-components/template-document-invite.tsx
|
||||
msgid "View Document"
|
||||
@ -6653,6 +6737,11 @@ msgstr "Benvenuto su Documenso!"
|
||||
msgid "Were you trying to edit this document instead?"
|
||||
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
|
||||
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."
|
||||
|
||||
@ -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"
|
||||
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
|
||||
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."
|
||||
@ -465,6 +469,14 @@ msgstr "<0>Wymagana passkey</0> - Odbiorca musi mieć konto i skonfigurowaną pa
|
||||
msgid "<0>Sender:</0> All"
|
||||
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
|
||||
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?"
|
||||
@ -898,6 +910,16 @@ msgstr "Cały czas"
|
||||
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"
|
||||
|
||||
#: 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
|
||||
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."
|
||||
@ -1237,6 +1259,13 @@ msgstr ""
|
||||
msgid "Assisting"
|
||||
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
|
||||
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."
|
||||
@ -1309,11 +1338,11 @@ msgstr "Zanim zaczniesz, proszę potwierdź swój adres e-mail, klikając przyci
|
||||
msgid "Billing"
|
||||
msgstr "Fakturowanie"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
|
||||
msgid "Black"
|
||||
msgstr "Czarny"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
|
||||
msgid "Blue"
|
||||
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."
|
||||
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
|
||||
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."
|
||||
@ -1436,6 +1469,8 @@ msgstr ""
|
||||
#: apps/remix/app/components/dialogs/document-move-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/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/field-item-advanced-settings.tsx
|
||||
msgid "Cancel"
|
||||
@ -1522,7 +1557,7 @@ msgstr "Wyczyść plik"
|
||||
msgid "Clear filters"
|
||||
msgstr "Wyczyść filtry"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
|
||||
msgid "Clear Signature"
|
||||
msgstr "Wyczyść podpis"
|
||||
|
||||
@ -1697,6 +1732,7 @@ msgstr "Treść"
|
||||
#: 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/passkey-create-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
|
||||
#: packages/ui/primitives/document-flow/document-flow-root.tsx
|
||||
msgid "Continue"
|
||||
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."
|
||||
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
|
||||
msgid "Controls whether the recipients can sign the documents using a typed signature. Enable or disable the typed signature globally."
|
||||
msgstr "Kontroluje, czy odbiorcy mogą podpisywać dokumenty za pomocą pisanych podpisów. Włącz lub wyłącz podpis pisany globalnie."
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx
|
||||
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 ""
|
||||
|
||||
#: 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."
|
||||
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
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx
|
||||
msgid "Copied"
|
||||
@ -1975,6 +2015,10 @@ msgstr "Domyślny język dokumentu"
|
||||
msgid "Default Document Visibility"
|
||||
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
|
||||
msgid "delete"
|
||||
msgstr "usuń"
|
||||
@ -2201,6 +2245,11 @@ msgstr "Dokument \"{0}\" - Odrzucony przez {1}"
|
||||
msgid "Document \"{0}\" - Rejection Confirmed"
|
||||
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/document-flow/add-settings.tsx
|
||||
#: packages/ui/components/document/document-global-auth-access-select.tsx
|
||||
@ -2349,6 +2398,10 @@ msgstr "Preferencje dokumentu zaktualizowane"
|
||||
msgid "Document re-sent"
|
||||
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/components/embed/embed-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."
|
||||
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/document-flow/add-fields.tsx
|
||||
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-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-complete-dialog.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/profile.tsx
|
||||
@ -2553,6 +2611,7 @@ msgstr "Ujawnienie podpisu elektronicznego"
|
||||
#: 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-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-fields.tsx
|
||||
@ -2646,15 +2705,6 @@ msgstr "Włącz podpisywanie linku bezpośredniego"
|
||||
msgid "Enable signing order"
|
||||
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.$id.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."
|
||||
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"
|
||||
msgstr "Zielony"
|
||||
|
||||
@ -3483,10 +3533,12 @@ msgstr "Moje szablony"
|
||||
#: apps/remix/app/components/tables/admin-dashboard-users-table.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-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/team-email-update-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-fields.tsx
|
||||
@ -3537,6 +3589,7 @@ msgstr "Nowy szablon"
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx
|
||||
msgid "Next"
|
||||
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/forms/signup.tsx
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
msgid "Please enter a valid name."
|
||||
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"
|
||||
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
|
||||
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ą."
|
||||
@ -4063,6 +4113,10 @@ msgstr "Prywatne"
|
||||
msgid "Private templates can only be modified and viewed by you."
|
||||
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/components/general/settings-nav-mobile.tsx
|
||||
#: apps/remix/app/components/general/settings-nav-desktop.tsx
|
||||
@ -4140,6 +4194,10 @@ msgstr "Gotowy"
|
||||
msgid "Reason"
|
||||
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
|
||||
msgid "Reason for rejection: "
|
||||
msgstr ""
|
||||
@ -4222,7 +4280,7 @@ msgstr "Kod odzyskiwania skopiowany"
|
||||
msgid "Recovery codes"
|
||||
msgstr "Kody odzyskiwania"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx
|
||||
msgid "Red"
|
||||
msgstr "Czerwony"
|
||||
|
||||
@ -4243,8 +4301,11 @@ msgstr "Rejestracja zakończona sukcesem"
|
||||
msgid "Reject Document"
|
||||
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/document/document-status.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
msgid "Rejected"
|
||||
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-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
|
||||
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
|
||||
msgid "Save"
|
||||
@ -4740,7 +4799,6 @@ msgstr "Zarejestruj się za pomocą OIDC"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.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-form.tsx
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
@ -4757,12 +4815,17 @@ msgstr "Podpis"
|
||||
msgid "Signature ID"
|
||||
msgstr "Identyfikator podpisu"
|
||||
|
||||
#: 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/embed/embed-document-signing-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
msgid "Signature is too small. Please provide a more complete signature."
|
||||
msgstr "Podpis jest zbyt mały. Proszę podać bardziej kompletny podpis."
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
|
||||
msgid "Signature is too small"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
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
|
||||
msgid "Signatures Collected"
|
||||
@ -4987,6 +5050,7 @@ msgid "Subject <0>(Optional)</0>"
|
||||
msgstr "Temat <0>(Opcjonalnie)</0>"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx
|
||||
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
|
||||
msgid "Submitting..."
|
||||
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"
|
||||
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+/settings+/webhooks.$id.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."
|
||||
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
|
||||
msgid "This document has been signed by all recipients"
|
||||
msgstr "Ten dokument został podpisany przez wszystkich odbiorców"
|
||||
@ -5730,6 +5802,10 @@ msgstr "Strefa czasowa"
|
||||
msgid "Title"
|
||||
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
|
||||
msgid "To accept this invitation you must create an account."
|
||||
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/admin-document-recipient-item-table.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
msgid "Type"
|
||||
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/_authenticated+/t.$teamUrl+/settings.billing.tsx
|
||||
msgid "Unknown"
|
||||
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-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/dialogs/team-member-update-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/team-email-update-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
|
||||
msgid "Update"
|
||||
msgstr "Zaktualizuj"
|
||||
|
||||
@ -6039,6 +6120,8 @@ msgid "Update profile"
|
||||
msgstr "Zaktualizuj profil"
|
||||
|
||||
#: 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"
|
||||
msgstr "Zaktualizuj odbiorcę"
|
||||
|
||||
@ -6077,10 +6160,6 @@ msgstr "Zaktualizuj webhook"
|
||||
msgid "Updating password..."
|
||||
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
|
||||
msgid "Updating Your Information"
|
||||
msgstr "Aktualizacja Twoich informacji"
|
||||
@ -6089,6 +6168,10 @@ msgstr "Aktualizacja Twoich informacji"
|
||||
msgid "Upgrade"
|
||||
msgstr "Ulepsz"
|
||||
|
||||
#: packages/lib/constants/document.ts
|
||||
msgid "Upload"
|
||||
msgstr ""
|
||||
|
||||
#: 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."
|
||||
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"
|
||||
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"
|
||||
msgstr "Prześlij podpis"
|
||||
|
||||
@ -6287,6 +6370,7 @@ msgstr "Zobacz dokument"
|
||||
#: 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/email/template-components/template-document-rejected.tsx
|
||||
#: packages/email/template-components/template-document-invite.tsx
|
||||
msgid "View Document"
|
||||
@ -6653,6 +6737,11 @@ msgstr "Witamy w Documenso!"
|
||||
msgid "Were you trying to edit this document instead?"
|
||||
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
|
||||
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."
|
||||
|
||||
53
packages/lib/types/document-meta.ts
Normal file
53
packages/lib/types/document-meta.ts
Normal 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>;
|
||||
@ -51,6 +51,8 @@ export const ZDocumentSchema = DocumentSchema.pick({
|
||||
documentId: true,
|
||||
redirectUrl: true,
|
||||
typedSignatureEnabled: true,
|
||||
uploadSignatureEnabled: true,
|
||||
drawSignatureEnabled: true,
|
||||
allowDictateNextSigner: true,
|
||||
language: true,
|
||||
emailSettings: true,
|
||||
|
||||
@ -45,6 +45,8 @@ export const ZTemplateSchema = TemplateSchema.pick({
|
||||
dateFormat: true,
|
||||
signingOrder: true,
|
||||
typedSignatureEnabled: true,
|
||||
uploadSignatureEnabled: true,
|
||||
drawSignatureEnabled: true,
|
||||
allowDictateNextSigner: true,
|
||||
distributionMethod: true,
|
||||
templateId: true,
|
||||
|
||||
@ -48,6 +48,8 @@ export const ZWebhookDocumentMetaSchema = z.object({
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder),
|
||||
allowDictateNextSigner: z.boolean(),
|
||||
typedSignatureEnabled: z.boolean(),
|
||||
uploadSignatureEnabled: z.boolean(),
|
||||
drawSignatureEnabled: z.boolean(),
|
||||
language: z.string(),
|
||||
distributionMethod: z.nativeEnum(DocumentDistributionMethod),
|
||||
emailSettings: z.any().nullable(),
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../constants/app';
|
||||
import { DocumentSignatureType } from '../constants/document';
|
||||
import type { TEAM_MEMBER_ROLE_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);
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
@ -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;
|
||||
25
packages/prisma/prisma-middleware.ts
Normal file
25
packages/prisma/prisma-middleware.ts
Normal 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;
|
||||
}
|
||||
@ -390,21 +390,25 @@ enum DocumentDistributionMethod {
|
||||
|
||||
/// @zod.import(["import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';"])
|
||||
model DocumentMeta {
|
||||
id String @id @default(cuid())
|
||||
id String @id @default(cuid())
|
||||
subject String?
|
||||
message String?
|
||||
timezone String? @default("Etc/UTC") @db.Text
|
||||
timezone String? @default("Etc/UTC") @db.Text
|
||||
password String?
|
||||
dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text
|
||||
documentId Int @unique
|
||||
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||||
dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text
|
||||
documentId Int @unique
|
||||
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||||
redirectUrl String?
|
||||
signingOrder DocumentSigningOrder @default(PARALLEL)
|
||||
allowDictateNextSigner Boolean @default(false)
|
||||
typedSignatureEnabled Boolean @default(true)
|
||||
language String @default("en")
|
||||
distributionMethod DocumentDistributionMethod @default(EMAIL)
|
||||
emailSettings Json? /// [DocumentEmailSettings] @zod.custom.use(ZDocumentEmailSettingsSchema)
|
||||
signingOrder DocumentSigningOrder @default(PARALLEL)
|
||||
allowDictateNextSigner Boolean @default(false)
|
||||
|
||||
typedSignatureEnabled Boolean @default(true)
|
||||
uploadSignatureEnabled Boolean @default(true)
|
||||
drawSignatureEnabled Boolean @default(true)
|
||||
|
||||
language String @default("en")
|
||||
distributionMethod DocumentDistributionMethod @default(EMAIL)
|
||||
emailSettings Json? /// [DocumentEmailSettings] @zod.custom.use(ZDocumentEmailSettingsSchema)
|
||||
}
|
||||
|
||||
enum ReadStatus {
|
||||
@ -545,9 +549,12 @@ model TeamGlobalSettings {
|
||||
documentVisibility DocumentVisibility @default(EVERYONE)
|
||||
documentLanguage String @default("en")
|
||||
includeSenderDetails Boolean @default(true)
|
||||
typedSignatureEnabled Boolean @default(true)
|
||||
includeSigningCertificate Boolean @default(true)
|
||||
|
||||
typedSignatureEnabled Boolean @default(true)
|
||||
uploadSignatureEnabled Boolean @default(true)
|
||||
drawSignatureEnabled Boolean @default(true)
|
||||
|
||||
brandingEnabled Boolean @default(false)
|
||||
brandingLogo String @default("")
|
||||
brandingUrl String @default("")
|
||||
@ -669,9 +676,12 @@ model TemplateMeta {
|
||||
dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text
|
||||
signingOrder DocumentSigningOrder? @default(PARALLEL)
|
||||
allowDictateNextSigner Boolean @default(false)
|
||||
typedSignatureEnabled Boolean @default(true)
|
||||
distributionMethod DocumentDistributionMethod @default(EMAIL)
|
||||
|
||||
typedSignatureEnabled Boolean @default(true)
|
||||
uploadSignatureEnabled Boolean @default(true)
|
||||
drawSignatureEnabled Boolean @default(true)
|
||||
|
||||
templateId Int @unique
|
||||
template Template @relation(fields: [templateId], references: [id], onDelete: Cascade)
|
||||
redirectUrl String?
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
import type { Document, User } from '@prisma/client';
|
||||
import type { Document, Team, User } from '@prisma/client';
|
||||
import { nanoid } from 'nanoid';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
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 {
|
||||
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 (
|
||||
sender: User,
|
||||
recipients: (User | string)[],
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const { fontFamily } = require('tailwindcss/defaultTheme');
|
||||
const { default: flattenColorPalette } = require('tailwindcss/lib/util/flattenColorPalette');
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
@ -14,6 +15,9 @@ module.exports = {
|
||||
zIndex: {
|
||||
9999: '9999',
|
||||
},
|
||||
aspectRatio: {
|
||||
'signature-pad': '16 / 7',
|
||||
},
|
||||
colors: {
|
||||
border: 'hsl(var(--border))',
|
||||
'field-border': 'hsl(var(--field-border))',
|
||||
@ -147,5 +151,17 @@ module.exports = {
|
||||
require('tailwindcss-animate'),
|
||||
require('@tailwindcss/typography'),
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
@ -56,9 +56,12 @@ import {
|
||||
ZSearchDocumentsMutationSchema,
|
||||
ZSetSigningOrderForDocumentMutationSchema,
|
||||
ZSuccessResponseSchema,
|
||||
} from './schema';
|
||||
import { updateDocumentRoute } from './update-document';
|
||||
import {
|
||||
ZUpdateDocumentRequestSchema,
|
||||
ZUpdateDocumentResponseSchema,
|
||||
} from './schema';
|
||||
} from './update-document.types';
|
||||
|
||||
export const documentRouter = router({
|
||||
/**
|
||||
@ -335,20 +338,12 @@ export const documentRouter = router({
|
||||
});
|
||||
}),
|
||||
|
||||
updateDocument: updateDocumentRoute,
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* Todo: Refactor to updateDocument.
|
||||
* @deprecated Delete this after updateDocument endpoint is deployed
|
||||
*/
|
||||
setSettingsForDocument: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/update',
|
||||
summary: 'Update document',
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZUpdateDocumentRequestSchema)
|
||||
.output(ZUpdateDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
@ -368,6 +363,8 @@ export const documentRouter = router({
|
||||
dateFormat: meta.dateFormat,
|
||||
language: meta.language,
|
||||
typedSignatureEnabled: meta.typedSignatureEnabled,
|
||||
uploadSignatureEnabled: meta.uploadSignatureEnabled,
|
||||
drawSignatureEnabled: meta.drawSignatureEnabled,
|
||||
redirectUrl: meta.redirectUrl,
|
||||
distributionMethod: meta.distributionMethod,
|
||||
signingOrder: meta.signingOrder,
|
||||
|
||||
@ -109,6 +109,14 @@ export const ZDocumentMetaTypedSignatureEnabledSchema = z
|
||||
.boolean()
|
||||
.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({
|
||||
templateId: z
|
||||
.number()
|
||||
@ -233,6 +241,8 @@ export const ZCreateDocumentV2RequestSchema = z.object({
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
|
||||
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
@ -249,36 +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(),
|
||||
allowDictateNextSigner: z.boolean().optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const ZUpdateDocumentResponseSchema = ZDocumentLiteSchema;
|
||||
|
||||
export const ZSetFieldsForDocumentMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
fields: z.array(
|
||||
|
||||
52
packages/trpc/server/document-router/update-document.ts
Normal file
52
packages/trpc/server/document-router/update-document.ts
Normal 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,
|
||||
});
|
||||
});
|
||||
@ -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;
|
||||
@ -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 { updateTeam } from '@documenso/lib/server-only/team/update-team';
|
||||
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 { updateTeamMember } from '@documenso/lib/server-only/team/update-team-member';
|
||||
import { updateTeamPublicProfile } from '@documenso/lib/server-only/team/update-team-public-profile';
|
||||
@ -66,12 +65,12 @@ import {
|
||||
ZResendTeamEmailVerificationMutationSchema,
|
||||
ZResendTeamMemberInvitationMutationSchema,
|
||||
ZUpdateTeamBrandingSettingsMutationSchema,
|
||||
ZUpdateTeamDocumentSettingsMutationSchema,
|
||||
ZUpdateTeamEmailMutationSchema,
|
||||
ZUpdateTeamMemberMutationSchema,
|
||||
ZUpdateTeamMutationSchema,
|
||||
ZUpdateTeamPublicProfileMutationSchema,
|
||||
} from './schema';
|
||||
import { updateTeamDocumentSettingsRoute } from './update-team-document-settings';
|
||||
|
||||
export const teamRouter = router({
|
||||
// Internal endpoint for now.
|
||||
@ -571,18 +570,7 @@ export const teamRouter = router({
|
||||
return await getTeamPrices();
|
||||
}),
|
||||
|
||||
// Internal endpoint. Use updateTeam instead.
|
||||
updateTeamDocumentSettings: authenticatedProcedure
|
||||
.input(ZUpdateTeamDocumentSettingsMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { teamId, settings } = input;
|
||||
|
||||
return await updateTeamDocumentSettings({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
settings,
|
||||
});
|
||||
}),
|
||||
updateTeamDocumentSettings: updateTeamDocumentSettingsRoute,
|
||||
|
||||
// Internal endpoint for now.
|
||||
acceptTeamInvitation: authenticatedProcedure
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { DocumentVisibility, TeamMemberRole } from '@prisma/client';
|
||||
import { TeamMemberRole } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||
import { PROTECTED_TEAM_URLS } from '@documenso/lib/constants/teams';
|
||||
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 TCreateTeamEmailVerificationMutationSchema = z.infer<
|
||||
typeof ZCreateTeamEmailVerificationMutationSchema
|
||||
@ -247,6 +232,3 @@ export type TResendTeamMemberInvitationMutationSchema = z.infer<
|
||||
export type TUpdateTeamBrandingSettingsMutationSchema = z.infer<
|
||||
typeof ZUpdateTeamBrandingSettingsMutationSchema
|
||||
>;
|
||||
export type TUpdateTeamDocumentSettingsMutationSchema = z.infer<
|
||||
typeof ZUpdateTeamDocumentSettingsMutationSchema
|
||||
>;
|
||||
|
||||
@ -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,
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -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;
|
||||
@ -19,12 +19,14 @@ import { TemplateDirectLinkSchema } from '@documenso/prisma/generated/zod/modelS
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
ZDocumentMetaDrawSignatureEnabledSchema,
|
||||
ZDocumentMetaLanguageSchema,
|
||||
ZDocumentMetaMessageSchema,
|
||||
ZDocumentMetaRedirectUrlSchema,
|
||||
ZDocumentMetaSubjectSchema,
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
ZDocumentMetaTypedSignatureEnabledSchema,
|
||||
ZDocumentMetaUploadSignatureEnabledSchema,
|
||||
} from '../document-router/schema';
|
||||
import { ZSignFieldWithTokenMutationSchema } from '../field-router/schema';
|
||||
|
||||
@ -164,6 +166,8 @@ export const ZUpdateTemplateRequestSchema = z.object({
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
|
||||
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||
allowDictateNextSigner: z.boolean().optional(),
|
||||
})
|
||||
|
||||
@ -9,8 +9,8 @@ import { isAdmin } from '@documenso/lib/utils/is-admin';
|
||||
|
||||
import type { TrpcContext } from './context';
|
||||
|
||||
// Can't import type from trpc-to-openapi because it breaks nextjs build, not sure why.
|
||||
type OpenApiMeta = {
|
||||
// Can't import type from trpc-to-openapi because it breaks build, not sure why.
|
||||
export type TrpcRouteMeta = {
|
||||
openapi?: {
|
||||
enabled?: boolean;
|
||||
method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
|
||||
@ -30,7 +30,7 @@ type OpenApiMeta = {
|
||||
} & Record<string, unknown>;
|
||||
|
||||
const t = initTRPC
|
||||
.meta<OpenApiMeta>()
|
||||
.meta<TrpcRouteMeta>()
|
||||
.context<TrpcContext>()
|
||||
.create({
|
||||
transformer: SuperJSON,
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -47,9 +47,8 @@ import { cn } from '../../lib/utils';
|
||||
import { Alert, AlertDescription } from '../alert';
|
||||
import { Button } from '../button';
|
||||
import { Card, CardContent } from '../card';
|
||||
import { Checkbox } from '../checkbox';
|
||||
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 { useStep } from '../stepper';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip';
|
||||
@ -94,7 +93,6 @@ export type AddFieldsFormProps = {
|
||||
onSubmit: (_data: TAddFieldsFormSchema) => void;
|
||||
canGoBack?: boolean;
|
||||
isDocumentPdfLoaded: boolean;
|
||||
typedSignatureEnabled?: boolean;
|
||||
teamId?: number;
|
||||
};
|
||||
|
||||
@ -106,7 +104,6 @@ export const AddFieldsFormPartial = ({
|
||||
onSubmit,
|
||||
canGoBack = false,
|
||||
isDocumentPdfLoaded,
|
||||
typedSignatureEnabled,
|
||||
teamId,
|
||||
}: AddFieldsFormProps) => {
|
||||
const { toast } = useToast();
|
||||
@ -137,7 +134,6 @@ export const AddFieldsFormPartial = ({
|
||||
recipients.find((recipient) => recipient.id === field.recipientId)?.email ?? '',
|
||||
fieldMeta: field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : undefined,
|
||||
})),
|
||||
typedSignatureEnabled: typedSignatureEnabled ?? false,
|
||||
},
|
||||
});
|
||||
|
||||
@ -787,31 +783,6 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
|
||||
<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">
|
||||
<fieldset disabled={isFieldsDisabled} className="my-2 grid grid-cols-3 gap-4">
|
||||
<button
|
||||
|
||||
@ -18,7 +18,6 @@ export const ZAddFieldsFormSchema = z.object({
|
||||
fieldMeta: ZFieldMetaSchema,
|
||||
}),
|
||||
),
|
||||
typedSignatureEnabled: z.boolean(),
|
||||
});
|
||||
|
||||
export type TAddFieldsFormSchema = z.infer<typeof ZAddFieldsFormSchema>;
|
||||
|
||||
@ -2,6 +2,7 @@ import { useEffect } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { DocumentVisibility, TeamMemberRole } from '@prisma/client';
|
||||
import { DocumentStatus, type Field, type Recipient, SendStatus } from '@prisma/client';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
@ -9,10 +10,12 @@ import { useForm } from 'react-hook-form';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
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 { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
import type { TDocument } from '@documenso/lib/types/document';
|
||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||
import { extractTeamSignatureSettings } from '@documenso/lib/utils/teams';
|
||||
import {
|
||||
DocumentGlobalAuthAccessSelect,
|
||||
DocumentGlobalAuthAccessTooltip,
|
||||
@ -39,7 +42,9 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} 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 { Input } from '../input';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../select';
|
||||
@ -78,6 +83,8 @@ export const AddSettingsFormPartial = ({
|
||||
currentTeamMemberRole,
|
||||
onSubmit,
|
||||
}: AddSettingsFormProps) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const { documentAuthOption } = extractDocumentAuthMethods({
|
||||
documentAuth: document.authOptions,
|
||||
});
|
||||
@ -90,6 +97,7 @@ export const AddSettingsFormPartial = ({
|
||||
visibility: document.visibility || '',
|
||||
globalAccessAuth: documentAuthOption?.globalAccessAuth || undefined,
|
||||
globalActionAuth: documentAuthOption?.globalActionAuth || undefined,
|
||||
|
||||
meta: {
|
||||
timezone:
|
||||
TIME_ZONES.find((timezone) => timezone === document.documentMeta?.timezone) ??
|
||||
@ -99,6 +107,7 @@ export const AddSettingsFormPartial = ({
|
||||
?.value ?? DEFAULT_DOCUMENT_DATE_FORMAT,
|
||||
redirectUrl: document.documentMeta?.redirectUrl ?? '',
|
||||
language: document.documentMeta?.language ?? 'en',
|
||||
signatureTypes: extractTeamSignatureSettings(document.documentMeta),
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -189,9 +198,11 @@ export const AddSettingsFormPartial = ({
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-foreground max-w-md space-y-2 p-4">
|
||||
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.
|
||||
<Trans>
|
||||
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.
|
||||
</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</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
|
||||
control={form.control}
|
||||
name="meta.dateFormat"
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { DocumentVisibility } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
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 { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||
import {
|
||||
@ -26,7 +28,10 @@ export const ZMapNegativeOneToUndefinedSchema = z
|
||||
});
|
||||
|
||||
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(),
|
||||
visibility: z.nativeEnum(DocumentVisibility).optional(),
|
||||
globalAccessAuth: ZMapNegativeOneToUndefinedSchema.pipe(
|
||||
@ -49,6 +54,9 @@ export const ZAddSettingsFormSchema = z.object({
|
||||
.union([z.string(), z.enum(SUPPORTED_LANGUAGE_CODES)])
|
||||
.optional()
|
||||
.default('en'),
|
||||
signatureTypes: z.array(z.nativeEnum(DocumentSignatureType)).min(1, {
|
||||
message: msg`At least one signature type must be enabled`.id,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@ -24,11 +24,14 @@ type ComboBoxOption<T = OptionValue> = {
|
||||
type MultiSelectComboboxProps<T = OptionValue> = {
|
||||
emptySelectionPlaceholder?: React.ReactElement | string;
|
||||
enableClearAllButton?: boolean;
|
||||
enableSearch?: boolean;
|
||||
className?: string;
|
||||
loading?: boolean;
|
||||
inputPlaceholder?: MessageDescriptor;
|
||||
onChange: (_values: T[]) => void;
|
||||
options: ComboBoxOption<T>[];
|
||||
selectedValues: T[];
|
||||
testId?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -41,11 +44,14 @@ type MultiSelectComboboxProps<T = OptionValue> = {
|
||||
export function MultiSelectCombobox<T = OptionValue>({
|
||||
emptySelectionPlaceholder = 'Select values...',
|
||||
enableClearAllButton,
|
||||
enableSearch = true,
|
||||
className,
|
||||
inputPlaceholder,
|
||||
loading,
|
||||
onChange,
|
||||
options,
|
||||
selectedValues,
|
||||
testId,
|
||||
}: MultiSelectComboboxProps<T>) {
|
||||
const { _ } = useLingui();
|
||||
|
||||
@ -59,8 +65,6 @@ export function MultiSelectCombobox<T = OptionValue>({
|
||||
}
|
||||
|
||||
onChange(newSelectedOptions);
|
||||
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const selectedOptions = React.useMemo(() => {
|
||||
@ -107,7 +111,8 @@ export function MultiSelectCombobox<T = OptionValue>({
|
||||
role="combobox"
|
||||
disabled={loading}
|
||||
aria-expanded={open}
|
||||
className="w-[200px] px-3"
|
||||
className={cn('w-[200px] px-3', className)}
|
||||
data-testid={testId}
|
||||
>
|
||||
<AnimatePresence>
|
||||
{loading ? (
|
||||
@ -146,7 +151,7 @@ export function MultiSelectCombobox<T = OptionValue>({
|
||||
|
||||
<PopoverContent className="w-[200px] p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder={inputPlaceholder && _(inputPlaceholder)} />
|
||||
{enableSearch && <CommandInput placeholder={inputPlaceholder && _(inputPlaceholder)} />}
|
||||
<CommandEmpty>
|
||||
<Trans>No value found.</Trans>
|
||||
</CommandEmpty>
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { SIGNATURE_CANVAS_DPI } from '@documenso/lib/constants/signatures';
|
||||
|
||||
import { Point } from './point';
|
||||
|
||||
export class Canvas {
|
||||
@ -14,7 +16,7 @@ export class Canvas {
|
||||
private lastVelocity = 0;
|
||||
|
||||
private readonly VELOCITY_FILTER_WEIGHT = 0.5;
|
||||
private readonly DPI = 2;
|
||||
private readonly DPI = SIGNATURE_CANVAS_DPI;
|
||||
|
||||
constructor(canvas: HTMLCanvasElement) {
|
||||
this.$canvas = canvas;
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
150
packages/ui/primitives/signature-pad/signature-pad-dialog.tsx
Normal file
150
packages/ui/primitives/signature-pad/signature-pad-dialog.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
327
packages/ui/primitives/signature-pad/signature-pad-draw.tsx
Normal file
327
packages/ui/primitives/signature-pad/signature-pad-draw.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
29
packages/ui/primitives/signature-pad/signature-pad-type.tsx
Normal file
29
packages/ui/primitives/signature-pad/signature-pad-type.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
166
packages/ui/primitives/signature-pad/signature-pad-upload.tsx
Normal file
166
packages/ui/primitives/signature-pad/signature-pad-upload.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@ -1,591 +1,199 @@
|
||||
import type { HTMLAttributes, MouseEvent, PointerEvent, TouchEvent } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Undo2, Upload } from 'lucide-react';
|
||||
import type { StrokeOptions } from 'perfect-freehand';
|
||||
import { getStroke } from 'perfect-freehand';
|
||||
import { KeyboardIcon, UploadCloudIcon } from 'lucide-react';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { DocumentSignatureType } from '@documenso/lib/constants/document';
|
||||
import { isBase64Image } from '@documenso/lib/constants/signatures';
|
||||
|
||||
import { SignatureIcon } from '../../icons/signature';
|
||||
import { cn } from '../../lib/utils';
|
||||
import { getSvgPathFromStroke } from './helper';
|
||||
import { Point } from './point';
|
||||
import { SignaturePadDraw } from './signature-pad-draw';
|
||||
import { SignaturePadType } from './signature-pad-type';
|
||||
import { SignaturePadUpload } from './signature-pad-upload';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from './signature-tabs';
|
||||
|
||||
const DPI = 2;
|
||||
|
||||
const isBase64Image = (value: string) => value.startsWith('data:image/png;base64,');
|
||||
|
||||
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 SignaturePadValue = {
|
||||
type: DocumentSignatureType;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type SignaturePadProps = Omit<HTMLAttributes<HTMLCanvasElement>, 'onChange'> & {
|
||||
onChange?: (_signatureDataUrl: string | null) => void;
|
||||
containerClassName?: string;
|
||||
value?: string;
|
||||
onChange?: (_value: SignaturePadValue) => void;
|
||||
|
||||
disabled?: boolean;
|
||||
allowTypedSignature?: boolean;
|
||||
defaultValue?: string;
|
||||
|
||||
typedSignatureEnabled?: boolean;
|
||||
uploadSignatureEnabled?: boolean;
|
||||
drawSignatureEnabled?: boolean;
|
||||
|
||||
onValidityChange?: (isValid: boolean) => void;
|
||||
minCoverageThreshold?: number;
|
||||
};
|
||||
|
||||
export const SignaturePad = ({
|
||||
className,
|
||||
containerClassName,
|
||||
defaultValue,
|
||||
value = '',
|
||||
onChange,
|
||||
disabled = false,
|
||||
allowTypedSignature,
|
||||
onValidityChange,
|
||||
minCoverageThreshold = 0.01,
|
||||
...props
|
||||
typedSignatureEnabled = true,
|
||||
uploadSignatureEnabled = true,
|
||||
drawSignatureEnabled = true,
|
||||
}: SignaturePadProps) => {
|
||||
const $el = useRef<HTMLCanvasElement>(null);
|
||||
const $imageData = useRef<ImageData | null>(null);
|
||||
const $fileInput = useRef<HTMLInputElement>(null);
|
||||
const [imageSignature, setImageSignature] = useState(isBase64Image(value) ? value : '');
|
||||
const [drawSignature, setDrawSignature] = useState(isBase64Image(value) ? value : '');
|
||||
const [typedSignature, setTypedSignature] = useState(isBase64Image(value) ? '' : value);
|
||||
|
||||
const [isPressed, setIsPressed] = useState(false);
|
||||
const [lines, setLines] = useState<Point[][]>([]);
|
||||
const [currentLine, setCurrentLine] = useState<Point[]>([]);
|
||||
const [selectedColor, setSelectedColor] = useState('black');
|
||||
const [typedSignature, setTypedSignature] = useState(
|
||||
defaultValue && !isBase64Image(defaultValue) ? defaultValue : '',
|
||||
/**
|
||||
* This is cooked.
|
||||
*
|
||||
* Get the first enabled tab that has a signature if possible, otherwise just get
|
||||
* the first enabled tab.
|
||||
*/
|
||||
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 size = $el.current ? Math.min($el.current.height, $el.current.width) * 0.03 : 10;
|
||||
const onImageSignatureChange = (value: string) => {
|
||||
setImageSignature(value);
|
||||
|
||||
return {
|
||||
size,
|
||||
thinning: 0.25,
|
||||
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;
|
||||
}
|
||||
}
|
||||
onChange?.({
|
||||
type: DocumentSignatureType.UPLOAD,
|
||||
value,
|
||||
});
|
||||
};
|
||||
|
||||
const onMouseDown = (event: MouseEvent | PointerEvent | TouchEvent) => {
|
||||
if (event.cancelable) {
|
||||
event.preventDefault();
|
||||
}
|
||||
const onDrawSignatureChange = (value: string) => {
|
||||
setDrawSignature(value);
|
||||
|
||||
setIsPressed(true);
|
||||
|
||||
if (typedSignature) {
|
||||
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]);
|
||||
onChange?.({
|
||||
type: DocumentSignatureType.DRAW,
|
||||
value,
|
||||
});
|
||||
};
|
||||
|
||||
const onMouseMove = (event: MouseEvent | PointerEvent | TouchEvent) => {
|
||||
if (event.cancelable) {
|
||||
event.preventDefault();
|
||||
}
|
||||
const onTypedSignatureChange = (value: string) => {
|
||||
setTypedSignature(value);
|
||||
|
||||
if (!isPressed) {
|
||||
onChange?.({
|
||||
type: DocumentSignatureType.TYPE,
|
||||
value,
|
||||
});
|
||||
};
|
||||
|
||||
const onTabChange = (value: 'draw' | 'text' | 'image') => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const point = Point.fromEvent(event, DPI, $el.current);
|
||||
const lastPoint = currentLine[currentLine.length - 1];
|
||||
setTab(value);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
match(value)
|
||||
.with('draw', () => {
|
||||
onDrawSignatureChange(drawSignature);
|
||||
})
|
||||
.with('text', () => {
|
||||
onTypedSignatureChange(typedSignature);
|
||||
})
|
||||
.with('image', () => {
|
||||
onImageSignatureChange(imageSignature);
|
||||
})
|
||||
.exhaustive();
|
||||
};
|
||||
|
||||
const onMouseUp = (event: MouseEvent | PointerEvent | TouchEvent, addLine = true) => {
|
||||
if (event.cancelable) {
|
||||
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;
|
||||
}
|
||||
});
|
||||
if (!drawSignatureEnabled && !typedSignatureEnabled && !uploadSignatureEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn('relative block select-none', containerClassName, {
|
||||
'pointer-events-none opacity-50': disabled,
|
||||
<Tabs
|
||||
defaultValue={tab}
|
||||
className={cn({
|
||||
'pointer-events-none': disabled,
|
||||
})}
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
onValueChange={(value) => onTabChange(value as 'draw' | 'text' | 'image')}
|
||||
>
|
||||
<canvas
|
||||
data-testid="signature-pad"
|
||||
ref={$el}
|
||||
className={cn(
|
||||
'relative block',
|
||||
{
|
||||
'dark:hue-rotate-180 dark:invert': selectedColor === 'black',
|
||||
},
|
||||
className,
|
||||
<TabsList>
|
||||
{drawSignatureEnabled && (
|
||||
<TabsTrigger value="draw">
|
||||
<SignatureIcon className="mr-2 size-4" />
|
||||
Draw
|
||||
</TabsTrigger>
|
||||
)}
|
||||
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
|
||||
placeholder="Type your signature"
|
||||
className="w-1/2 border-none p-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0"
|
||||
value={typedSignature}
|
||||
onChange={handleTypedSignatureChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{typedSignatureEnabled && (
|
||||
<TabsTrigger value="text">
|
||||
<KeyboardIcon className="mr-2 size-4" />
|
||||
Type
|
||||
</TabsTrigger>
|
||||
)}
|
||||
|
||||
<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>
|
||||
{uploadSignatureEnabled && (
|
||||
<TabsTrigger value="image">
|
||||
<UploadCloudIcon className="mr-2 size-4" />
|
||||
Upload
|
||||
</TabsTrigger>
|
||||
)}
|
||||
</TabsList>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<TabsContent
|
||||
value="image"
|
||||
className={cn(
|
||||
'border-border aspect-signature-pad dark:bg-background relative rounded-md border bg-neutral-50',
|
||||
{
|
||||
'bg-white': imageSignature,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<SignaturePadUpload value={imageSignature} onChange={onImageSignatureChange} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
128
packages/ui/primitives/signature-pad/signature-render.tsx
Normal file
128
packages/ui/primitives/signature-pad/signature-render.tsx
Normal 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' }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
147
packages/ui/primitives/signature-pad/signature-tabs.tsx
Normal file
147
packages/ui/primitives/signature-pad/signature-tabs.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -54,10 +54,9 @@ import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitive
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { getSignerColorStyles, useSignerColors } from '../../lib/signer-colors';
|
||||
import { Checkbox } from '../checkbox';
|
||||
import type { FieldFormType } from '../document-flow/add-fields';
|
||||
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 type { TAddTemplateFieldsFormSchema } from './add-template-fields.types';
|
||||
|
||||
@ -74,7 +73,6 @@ export type AddTemplateFieldsFormProps = {
|
||||
fields: Field[];
|
||||
onSubmit: (_data: TAddTemplateFieldsFormSchema) => void;
|
||||
teamId?: number;
|
||||
typedSignatureEnabled?: boolean;
|
||||
};
|
||||
|
||||
export const AddTemplateFieldsFormPartial = ({
|
||||
@ -84,7 +82,6 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
fields,
|
||||
onSubmit,
|
||||
teamId,
|
||||
typedSignatureEnabled,
|
||||
}: AddTemplateFieldsFormProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
@ -119,7 +116,6 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
recipients.find((recipient) => recipient.id === field.recipientId)?.token ?? '',
|
||||
fieldMeta: field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : undefined,
|
||||
})),
|
||||
typedSignatureEnabled: typedSignatureEnabled ?? false,
|
||||
},
|
||||
});
|
||||
|
||||
@ -483,12 +479,6 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
form.setValue('fields', updatedFields);
|
||||
};
|
||||
|
||||
const isTypedSignatureEnabled = form.watch('typedSignatureEnabled');
|
||||
|
||||
const handleTypedSignatureChange = (value: boolean) => {
|
||||
form.setValue('typedSignatureEnabled', value, { shouldDirty: true });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{showAdvancedSettings && currentField ? (
|
||||
@ -662,31 +652,6 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
|
||||
<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">
|
||||
<fieldset className="my-2 grid grid-cols-3 gap-4">
|
||||
<button
|
||||
|
||||
@ -20,7 +20,6 @@ export const ZAddTemplateFieldsFormSchema = z.object({
|
||||
fieldMeta: ZFieldMetaSchema,
|
||||
}),
|
||||
),
|
||||
typedSignatureEnabled: z.boolean(),
|
||||
});
|
||||
|
||||
export type TAddTemplateFieldsFormSchema = z.infer<typeof ZAddTemplateFieldsFormSchema>;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { DocumentVisibility, TeamMemberRole } from '@prisma/client';
|
||||
import { DocumentDistributionMethod, type Field, type Recipient } from '@prisma/client';
|
||||
@ -10,12 +10,16 @@ import { useForm } from 'react-hook-form';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { DOCUMENT_DISTRIBUTION_METHODS } from '@documenso/lib/constants/document';
|
||||
import {
|
||||
DOCUMENT_DISTRIBUTION_METHODS,
|
||||
DOCUMENT_SIGNATURE_TYPES,
|
||||
} from '@documenso/lib/constants/document';
|
||||
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import type { TTemplate } from '@documenso/lib/types/template';
|
||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||
import { extractTeamSignatureSettings } from '@documenso/lib/utils/teams';
|
||||
import type { TDocumentMetaDateFormat } from '@documenso/trpc/server/document-router/schema';
|
||||
import {
|
||||
DocumentGlobalAuthAccessSelect,
|
||||
@ -46,6 +50,7 @@ import {
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
|
||||
import { DocumentEmailCheckboxes } from '../../components/document/document-email-checkboxes';
|
||||
import { DocumentSignatureSettingsTooltip } from '../../components/document/document-signature-settings-tooltip';
|
||||
import { Combobox } from '../combobox';
|
||||
import {
|
||||
DocumentFlowFormContainerActions,
|
||||
@ -57,6 +62,7 @@ import {
|
||||
import { ShowFieldItem } from '../document-flow/show-field-item';
|
||||
import type { DocumentFlowStep } from '../document-flow/types';
|
||||
import { Input } from '../input';
|
||||
import { MultiSelectCombobox } from '../multi-select-combobox';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../select';
|
||||
import { useStep } from '../stepper';
|
||||
import { Textarea } from '../textarea';
|
||||
@ -85,7 +91,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
currentTeamMemberRole,
|
||||
onSubmit,
|
||||
}: AddTemplateSettingsFormProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { t, i18n } = useLingui();
|
||||
|
||||
const { documentAuthOption } = extractDocumentAuthMethods({
|
||||
documentAuth: template.authOptions,
|
||||
@ -111,6 +117,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
redirectUrl: template.templateMeta?.redirectUrl ?? '',
|
||||
language: template.templateMeta?.language ?? 'en',
|
||||
emailSettings: ZDocumentEmailSettingsSchema.parse(template?.templateMeta?.emailSettings),
|
||||
signatureTypes: extractTeamSignatureSettings(template?.templateMeta),
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -314,7 +321,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
{Object.values(DOCUMENT_DISTRIBUTION_METHODS).map(
|
||||
({ value, description }) => (
|
||||
<SelectItem key={value} value={value}>
|
||||
{_(description)}
|
||||
{i18n._(description)}
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
@ -325,6 +332,34 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<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>
|
||||
)}
|
||||
/>
|
||||
|
||||
{isEnterprise && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { DocumentDistributionMethod } from '@prisma/client';
|
||||
import { DocumentVisibility } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
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 { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||
import {
|
||||
@ -49,6 +51,9 @@ export const ZAddTemplateSettingsFormSchema = z.object({
|
||||
.optional()
|
||||
.default('en'),
|
||||
emailSettings: ZDocumentEmailSettingsSchema,
|
||||
signatureTypes: z.array(z.nativeEnum(DocumentSignatureType)).min(1, {
|
||||
message: msg`At least one signature type must be enabled`.id,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user