mirror of
https://github.com/documenso/documenso.git
synced 2025-11-16 01:32:06 +10:00
Merge branch 'main' into feat/delete-archive
This commit is contained in:
@ -27,9 +27,6 @@
|
|||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
"autoprefixer": "^10.0.1",
|
|
||||||
"postcss": "^8",
|
|
||||||
"tailwindcss": "^3.3.0",
|
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@documenso/marketing",
|
"name": "@documenso/marketing",
|
||||||
"version": "1.8.1-rc.0",
|
"version": "1.8.1-rc.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@documenso/web",
|
"name": "@documenso/web",
|
||||||
"version": "1.8.1-rc.0",
|
"version": "1.8.1-rc.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -28,6 +28,7 @@
|
|||||||
"@simplewebauthn/browser": "^9.0.1",
|
"@simplewebauthn/browser": "^9.0.1",
|
||||||
"@simplewebauthn/server": "^9.0.3",
|
"@simplewebauthn/server": "^9.0.3",
|
||||||
"@tanstack/react-query": "^4.29.5",
|
"@tanstack/react-query": "^4.29.5",
|
||||||
|
"colord": "^2.9.3",
|
||||||
"cookie-es": "^1.0.0",
|
"cookie-es": "^1.0.0",
|
||||||
"formidable": "^2.1.1",
|
"formidable": "^2.1.1",
|
||||||
"framer-motion": "^10.12.8",
|
"framer-motion": "^10.12.8",
|
||||||
@ -53,7 +54,7 @@
|
|||||||
"react-icons": "^4.11.0",
|
"react-icons": "^4.11.0",
|
||||||
"react-rnd": "^10.4.1",
|
"react-rnd": "^10.4.1",
|
||||||
"recharts": "^2.7.2",
|
"recharts": "^2.7.2",
|
||||||
"remeda": "^2.12.1",
|
"remeda": "^2.17.3",
|
||||||
"sharp": "0.32.6",
|
"sharp": "0.32.6",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"ua-parser-js": "^1.0.37",
|
"ua-parser-js": "^1.0.37",
|
||||||
|
|||||||
@ -146,7 +146,10 @@ export const DocumentPageView = async ({ params, team }: DocumentPageViewProps)
|
|||||||
|
|
||||||
<div className="flex flex-row justify-between truncate">
|
<div className="flex flex-row justify-between truncate">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="mt-4 truncate text-2xl font-semibold md:text-3xl" title={document.title}>
|
<h1
|
||||||
|
className="mt-4 block max-w-[20rem] truncate text-2xl font-semibold md:max-w-[30rem] md:text-3xl"
|
||||||
|
title={document.title}
|
||||||
|
>
|
||||||
{document.title}
|
{document.title}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@ -218,7 +221,7 @@ export const DocumentPageView = async ({ params, team }: DocumentPageViewProps)
|
|||||||
<DocumentPageViewDropdown document={documentWithRecipients} team={team} />
|
<DocumentPageViewDropdown document={documentWithRecipients} team={team} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-muted-foreground mt-2 px-4 text-sm ">
|
<p className="text-muted-foreground mt-2 px-4 text-sm">
|
||||||
{match(document.status)
|
{match(document.status)
|
||||||
.with(DocumentStatus.COMPLETED, () => (
|
.with(DocumentStatus.COMPLETED, () => (
|
||||||
<Trans>This document has been signed by all recipients</Trans>
|
<Trans>This document has been signed by all recipients</Trans>
|
||||||
|
|||||||
@ -109,7 +109,10 @@ export const DocumentEditPageView = async ({ params, team }: DocumentEditPageVie
|
|||||||
<Trans>Documents</Trans>
|
<Trans>Documents</Trans>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<h1 className="mt-4 truncate text-2xl font-semibold md:text-3xl" title={document.title}>
|
<h1
|
||||||
|
className="mt-4 block max-w-[20rem] truncate text-2xl font-semibold md:max-w-[30rem] md:text-3xl"
|
||||||
|
title={document.title}
|
||||||
|
>
|
||||||
{document.title}
|
{document.title}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
|||||||
@ -121,7 +121,10 @@ export const DocumentLogsPageView = async ({ params, team }: DocumentLogsPageVie
|
|||||||
|
|
||||||
<div className="flex flex-col justify-between truncate sm:flex-row">
|
<div className="flex flex-col justify-between truncate sm:flex-row">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="mt-4 truncate text-2xl font-semibold md:text-3xl" title={document.title}>
|
<h1
|
||||||
|
className="mt-4 block max-w-[20rem] truncate text-2xl font-semibold md:max-w-[30rem] md:text-3xl"
|
||||||
|
title={document.title}
|
||||||
|
>
|
||||||
{document.title}
|
{document.title}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
|||||||
@ -141,6 +141,23 @@ export const EditTemplateForm = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { mutateAsync: updateTypedSignature } =
|
||||||
|
trpc.template.updateTemplateTypedSignatureSettings.useMutation({
|
||||||
|
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||||
|
onSuccess: (newData) => {
|
||||||
|
utils.template.getTemplateWithDetailsById.setData(
|
||||||
|
{
|
||||||
|
id: initialTemplate.id,
|
||||||
|
},
|
||||||
|
(oldData) => ({
|
||||||
|
...(oldData || initialTemplate),
|
||||||
|
...newData,
|
||||||
|
id: Number(newData.id),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const onAddSettingsFormSubmit = async (data: TAddTemplateSettingsFormSchema) => {
|
const onAddSettingsFormSubmit = async (data: TAddTemplateSettingsFormSchema) => {
|
||||||
try {
|
try {
|
||||||
await updateTemplateSettings({
|
await updateTemplateSettings({
|
||||||
@ -211,6 +228,12 @@ export const EditTemplateForm = ({
|
|||||||
fields: data.fields,
|
fields: data.fields,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await updateTypedSignature({
|
||||||
|
templateId: template.id,
|
||||||
|
teamId: team?.id,
|
||||||
|
typedSignatureEnabled: data.typedSignatureEnabled,
|
||||||
|
});
|
||||||
|
|
||||||
// Clear all field data from localStorage
|
// Clear all field data from localStorage
|
||||||
for (let i = 0; i < localStorage.length; i++) {
|
for (let i = 0; i < localStorage.length; i++) {
|
||||||
const key = localStorage.key(i);
|
const key = localStorage.key(i);
|
||||||
@ -225,14 +248,13 @@ export const EditTemplateForm = ({
|
|||||||
duration: 5000,
|
duration: 5000,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Router refresh is here to clear the router cache for when navigating to /documents.
|
|
||||||
router.refresh();
|
|
||||||
|
|
||||||
router.push(templateRootPath);
|
router.push(templateRootPath);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Error`),
|
title: _(msg`Error`),
|
||||||
description: _(msg`An error occurred while adding signers.`),
|
description: _(msg`An error occurred while adding fields.`),
|
||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -301,6 +323,7 @@ export const EditTemplateForm = ({
|
|||||||
fields={fields}
|
fields={fields}
|
||||||
onSubmit={onAddFieldsFormSubmit}
|
onSubmit={onAddFieldsFormSubmit}
|
||||||
teamId={team?.id}
|
teamId={team?.id}
|
||||||
|
typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled}
|
||||||
/>
|
/>
|
||||||
</Stepper>
|
</Stepper>
|
||||||
</DocumentFlowFormContainer>
|
</DocumentFlowFormContainer>
|
||||||
|
|||||||
@ -63,7 +63,10 @@ export const TemplateEditPageView = async ({ params, team }: TemplateEditPageVie
|
|||||||
<Trans>Template</Trans>
|
<Trans>Template</Trans>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<h1 className="mt-4 truncate text-2xl font-semibold md:text-3xl" title={template.title}>
|
<h1
|
||||||
|
className="mt-4 block max-w-[20rem] truncate text-2xl font-semibold md:max-w-[30rem] md:text-3xl"
|
||||||
|
title={template.title}
|
||||||
|
>
|
||||||
{template.title}
|
{template.title}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
|||||||
@ -73,7 +73,6 @@ export const TemplatePageView = async ({ params, team }: TemplatePageViewProps)
|
|||||||
|
|
||||||
const mockedDocumentMeta = templateMeta
|
const mockedDocumentMeta = templateMeta
|
||||||
? {
|
? {
|
||||||
typedSignatureEnabled: false,
|
|
||||||
...templateMeta,
|
...templateMeta,
|
||||||
signingOrder: templateMeta.signingOrder || DocumentSigningOrder.SEQUENTIAL,
|
signingOrder: templateMeta.signingOrder || DocumentSigningOrder.SEQUENTIAL,
|
||||||
documentId: 0,
|
documentId: 0,
|
||||||
@ -89,7 +88,10 @@ export const TemplatePageView = async ({ params, team }: TemplatePageViewProps)
|
|||||||
|
|
||||||
<div className="flex flex-row justify-between truncate">
|
<div className="flex flex-row justify-between truncate">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="mt-4 truncate text-2xl font-semibold md:text-3xl" title={template.title}>
|
<h1
|
||||||
|
className="mt-4 block max-w-[20rem] truncate text-2xl font-semibold md:max-w-[30rem] md:text-3xl"
|
||||||
|
title={template.title}
|
||||||
|
>
|
||||||
{template.title}
|
{template.title}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@ -155,7 +157,7 @@ export const TemplatePageView = async ({ params, team }: TemplatePageViewProps)
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-muted-foreground mt-2 px-4 text-sm ">
|
<p className="text-muted-foreground mt-2 px-4 text-sm">
|
||||||
<Trans>Manage and view template</Trans>
|
<Trans>Manage and view template</Trans>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|||||||
@ -209,11 +209,19 @@ export default async function SigningCertificate({ searchParams }: SigningCertif
|
|||||||
boxShadow: `0px 0px 0px 4.88px rgba(122, 196, 85, 0.1), 0px 0px 0px 1.22px rgba(122, 196, 85, 0.6), 0px 0px 0px 0.61px rgba(122, 196, 85, 1)`,
|
boxShadow: `0px 0px 0px 4.88px rgba(122, 196, 85, 0.1), 0px 0px 0px 1.22px rgba(122, 196, 85, 0.6), 0px 0px 0px 0.61px rgba(122, 196, 85, 1)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
{signature.Signature?.signatureImageAsBase64 && (
|
||||||
src={`${signature.Signature?.signatureImageAsBase64}`}
|
<img
|
||||||
alt="Signature"
|
src={`${signature.Signature?.signatureImageAsBase64}`}
|
||||||
className="max-h-12 max-w-full"
|
alt="Signature"
|
||||||
/>
|
className="max-h-12 max-w-full"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{signature.Signature?.typedSignature && (
|
||||||
|
<p className="font-signature text-center text-sm">
|
||||||
|
{signature.Signature?.typedSignature}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-muted-foreground mt-2 text-sm print:text-xs">
|
<p className="text-muted-foreground mt-2 text-sm print:text-xs">
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
|||||||
|
|
||||||
import { DocumentAuthProvider } from '~/app/(signing)/sign/[token]/document-auth-provider';
|
import { DocumentAuthProvider } from '~/app/(signing)/sign/[token]/document-auth-provider';
|
||||||
import { SigningProvider } from '~/app/(signing)/sign/[token]/provider';
|
import { SigningProvider } from '~/app/(signing)/sign/[token]/provider';
|
||||||
import { truncateTitle } from '~/helpers/truncate-title';
|
|
||||||
|
|
||||||
import { DirectTemplatePageView } from './direct-template';
|
import { DirectTemplatePageView } from './direct-template';
|
||||||
import { DirectTemplateAuthPageView } from './signing-auth-page';
|
import { DirectTemplateAuthPageView } from './signing-auth-page';
|
||||||
@ -72,8 +71,11 @@ export default async function TemplatesDirectPage({ params }: TemplatesDirectPag
|
|||||||
user={user}
|
user={user}
|
||||||
>
|
>
|
||||||
<div className="mx-auto -mt-4 w-full max-w-screen-xl px-4 md:px-8">
|
<div className="mx-auto -mt-4 w-full max-w-screen-xl px-4 md:px-8">
|
||||||
<h1 className="mt-4 truncate text-2xl font-semibold md:text-3xl" title={template.title}>
|
<h1
|
||||||
{truncateTitle(template.title)}
|
className="mt-4 block max-w-[20rem] truncate text-2xl font-semibold md:max-w-[30rem] md:text-3xl"
|
||||||
|
title={template.title}
|
||||||
|
>
|
||||||
|
{template.title}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div className="text-muted-foreground mb-8 mt-2.5 flex items-center gap-x-2">
|
<div className="text-muted-foreground mb-8 mt-2.5 flex items-center gap-x-2">
|
||||||
|
|||||||
@ -102,9 +102,9 @@ export const SignDirectTemplateForm = ({
|
|||||||
created: new Date(),
|
created: new Date(),
|
||||||
recipientId: 1,
|
recipientId: 1,
|
||||||
fieldId: 1,
|
fieldId: 1,
|
||||||
signatureImageAsBase64: value.value,
|
signatureImageAsBase64: value.value.startsWith('data:') ? value.value : null,
|
||||||
typedSignature: null,
|
typedSignature: value.value.startsWith('data:') ? null : value.value,
|
||||||
};
|
} satisfies Signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === FieldType.DATE) {
|
if (field.type === FieldType.DATE) {
|
||||||
|
|||||||
@ -24,8 +24,6 @@ import { SigningCard3D } from '@documenso/ui/components/signing-card';
|
|||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
import { Badge } from '@documenso/ui/primitives/badge';
|
import { Badge } from '@documenso/ui/primitives/badge';
|
||||||
|
|
||||||
import { truncateTitle } from '~/helpers/truncate-title';
|
|
||||||
|
|
||||||
import { SigningAuthPageView } from '../signing-auth-page';
|
import { SigningAuthPageView } from '../signing-auth-page';
|
||||||
import { ClaimAccount } from './claim-account';
|
import { ClaimAccount } from './claim-account';
|
||||||
import { DocumentPreviewButton } from './document-preview-button';
|
import { DocumentPreviewButton } from './document-preview-button';
|
||||||
@ -61,8 +59,6 @@ export default async function CompletedSigningPage({
|
|||||||
return notFound();
|
return notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
const truncatedTitle = truncateTitle(document.title);
|
|
||||||
|
|
||||||
const { documentData } = document;
|
const { documentData } = document;
|
||||||
|
|
||||||
const [fields, recipient] = await Promise.all([
|
const [fields, recipient] = await Promise.all([
|
||||||
@ -118,7 +114,9 @@ export default async function CompletedSigningPage({
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Badge variant="neutral" size="default" className="mb-6 rounded-xl border bg-transparent">
|
<Badge variant="neutral" size="default" className="mb-6 rounded-xl border bg-transparent">
|
||||||
{truncatedTitle}
|
<span className="block max-w-[10rem] truncate font-medium hover:underline md:max-w-[20rem]">
|
||||||
|
{document.title}
|
||||||
|
</span>
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
||||||
{/* Card with recipient */}
|
{/* Card with recipient */}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { createContext, useContext, useState } from 'react';
|
import { createContext, useContext, useEffect, useState } from 'react';
|
||||||
|
|
||||||
export type SigningContextValue = {
|
export type SigningContextValue = {
|
||||||
fullName: string;
|
fullName: string;
|
||||||
@ -44,6 +44,12 @@ export const SigningProvider = ({
|
|||||||
const [email, setEmail] = useState(initialEmail || '');
|
const [email, setEmail] = useState(initialEmail || '');
|
||||||
const [signature, setSignature] = useState(initialSignature || null);
|
const [signature, setSignature] = useState(initialSignature || null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (initialSignature) {
|
||||||
|
setSignature(initialSignature);
|
||||||
|
}
|
||||||
|
}, [initialSignature]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SigningContext.Provider
|
<SigningContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import {
|
|||||||
} from '@documenso/ui/primitives/dialog';
|
} from '@documenso/ui/primitives/dialog';
|
||||||
|
|
||||||
import { SigningDisclosure } from '~/components/general/signing-disclosure';
|
import { SigningDisclosure } from '~/components/general/signing-disclosure';
|
||||||
import { truncateTitle } from '~/helpers/truncate-title';
|
|
||||||
|
|
||||||
export type SignDialogProps = {
|
export type SignDialogProps = {
|
||||||
isSubmitting: boolean;
|
isSubmitting: boolean;
|
||||||
@ -36,7 +35,7 @@ export const SignDialog = ({
|
|||||||
disabled = false,
|
disabled = false,
|
||||||
}: SignDialogProps) => {
|
}: SignDialogProps) => {
|
||||||
const [showDialog, setShowDialog] = useState(false);
|
const [showDialog, setShowDialog] = useState(false);
|
||||||
const truncatedTitle = truncateTitle(documentTitle);
|
|
||||||
const isComplete = fields.every((field) => field.inserted);
|
const isComplete = fields.every((field) => field.inserted);
|
||||||
|
|
||||||
const handleOpenChange = (open: boolean) => {
|
const handleOpenChange = (open: boolean) => {
|
||||||
@ -75,7 +74,13 @@ export const SignDialog = ({
|
|||||||
{role === RecipientRole.VIEWER && (
|
{role === RecipientRole.VIEWER && (
|
||||||
<span>
|
<span>
|
||||||
<Trans>
|
<Trans>
|
||||||
You are about to complete viewing "{truncatedTitle}".
|
<span className="inline-flex flex-wrap">
|
||||||
|
You are about to complete viewing "
|
||||||
|
<span className="inline-block max-w-[11rem] truncate align-baseline">
|
||||||
|
{documentTitle}
|
||||||
|
</span>
|
||||||
|
".
|
||||||
|
</span>
|
||||||
<br /> Are you sure?
|
<br /> Are you sure?
|
||||||
</Trans>
|
</Trans>
|
||||||
</span>
|
</span>
|
||||||
@ -83,7 +88,13 @@ export const SignDialog = ({
|
|||||||
{role === RecipientRole.SIGNER && (
|
{role === RecipientRole.SIGNER && (
|
||||||
<span>
|
<span>
|
||||||
<Trans>
|
<Trans>
|
||||||
You are about to complete signing "{truncatedTitle}".
|
<span className="inline-flex flex-wrap">
|
||||||
|
You are about to complete signing "
|
||||||
|
<span className="inline-block max-w-[11rem] truncate align-baseline">
|
||||||
|
{documentTitle}
|
||||||
|
</span>
|
||||||
|
".
|
||||||
|
</span>
|
||||||
<br /> Are you sure?
|
<br /> Are you sure?
|
||||||
</Trans>
|
</Trans>
|
||||||
</span>
|
</span>
|
||||||
@ -91,7 +102,13 @@ export const SignDialog = ({
|
|||||||
{role === RecipientRole.APPROVER && (
|
{role === RecipientRole.APPROVER && (
|
||||||
<span>
|
<span>
|
||||||
<Trans>
|
<Trans>
|
||||||
You are about to complete approving "{truncatedTitle}".
|
<span className="inline-flex flex-wrap">
|
||||||
|
You are about to complete approving{' '}
|
||||||
|
<span className="inline-block max-w-[11rem] truncate align-baseline">
|
||||||
|
"{documentTitle}"
|
||||||
|
</span>
|
||||||
|
.
|
||||||
|
</span>
|
||||||
<br /> Are you sure?
|
<br /> Are you sure?
|
||||||
</Trans>
|
</Trans>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useMemo, useState, useTransition } from 'react';
|
import { useLayoutEffect, useMemo, useRef, useState, useTransition } from 'react';
|
||||||
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
@ -51,6 +51,10 @@ export const SignatureField = ({
|
|||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const signatureRef = useRef<HTMLParagraphElement>(null);
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [fontSize, setFontSize] = useState(2);
|
||||||
|
|
||||||
const { signature: providedSignature, setSignature: setProvidedSignature } =
|
const { signature: providedSignature, setSignature: setProvidedSignature } =
|
||||||
useRequiredSigningContext();
|
useRequiredSigningContext();
|
||||||
|
|
||||||
@ -108,6 +112,7 @@ export const SignatureField = ({
|
|||||||
actionTarget: field.type,
|
actionTarget: field.type,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSign = async (authOptions?: TRecipientActionAuth, signature?: string) => {
|
const onSign = async (authOptions?: TRecipientActionAuth, signature?: string) => {
|
||||||
try {
|
try {
|
||||||
const value = signature || providedSignature;
|
const value = signature || providedSignature;
|
||||||
@ -117,11 +122,23 @@ export const SignatureField = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isTypedSignature = !value.startsWith('data:image');
|
||||||
|
|
||||||
|
if (isTypedSignature && !typedSignatureEnabled) {
|
||||||
|
toast({
|
||||||
|
title: _(msg`Error`),
|
||||||
|
description: _(msg`Typed signatures are not allowed. Please draw your signature.`),
|
||||||
|
variant: 'destructive',
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const payload: TSignFieldWithTokenMutationSchema = {
|
const payload: TSignFieldWithTokenMutationSchema = {
|
||||||
token: recipient.token,
|
token: recipient.token,
|
||||||
fieldId: field.id,
|
fieldId: field.id,
|
||||||
value,
|
value,
|
||||||
isBase64: true,
|
isBase64: !isTypedSignature,
|
||||||
authOptions,
|
authOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -176,6 +193,41 @@ export const SignatureField = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!signatureRef.current || !containerRef.current || !signature?.typedSignature) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const adjustTextSize = () => {
|
||||||
|
const container = containerRef.current;
|
||||||
|
const text = signatureRef.current;
|
||||||
|
|
||||||
|
if (!container || !text) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = 2;
|
||||||
|
text.style.fontSize = `${size}rem`;
|
||||||
|
|
||||||
|
while (
|
||||||
|
(text.scrollWidth > container.clientWidth || text.scrollHeight > container.clientHeight) &&
|
||||||
|
size > 0.8
|
||||||
|
) {
|
||||||
|
size -= 0.1;
|
||||||
|
text.style.fontSize = `${size}rem`;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontSize(size);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(adjustTextSize);
|
||||||
|
resizeObserver.observe(containerRef.current);
|
||||||
|
|
||||||
|
adjustTextSize();
|
||||||
|
|
||||||
|
return () => resizeObserver.disconnect();
|
||||||
|
}, [signature?.typedSignature]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SigningFieldContainer
|
<SigningFieldContainer
|
||||||
field={field}
|
field={field}
|
||||||
@ -205,10 +257,15 @@ export const SignatureField = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{state === 'signed-text' && (
|
{state === 'signed-text' && (
|
||||||
<p className="font-signature text-muted-foreground dark:text-background text-lg duration-200 sm:text-xl md:text-2xl lg:text-3xl">
|
<div ref={containerRef} className="flex h-full w-full items-center justify-center p-2">
|
||||||
{/* This optional chaining is intentional, we don't want to move the check into the condition above */}
|
<p
|
||||||
{signature?.typedSignature}
|
ref={signatureRef}
|
||||||
</p>
|
className="font-signature text-muted-foreground dark:text-background w-full overflow-hidden break-all text-center leading-tight duration-200"
|
||||||
|
style={{ fontSize: `${fontSize}rem` }}
|
||||||
|
>
|
||||||
|
{signature?.typedSignature}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Dialog open={showSignatureModal} onOpenChange={setShowSignatureModal}>
|
<Dialog open={showSignatureModal} onOpenChange={setShowSignatureModal}>
|
||||||
|
|||||||
@ -55,7 +55,10 @@ export const SigningPageView = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto w-full max-w-screen-xl">
|
<div className="mx-auto w-full max-w-screen-xl">
|
||||||
<h1 className="mt-4 truncate text-2xl font-semibold md:text-3xl" title={document.title}>
|
<h1
|
||||||
|
className="mt-4 block max-w-[20rem] truncate text-2xl font-semibold md:max-w-[30rem] md:text-3xl"
|
||||||
|
title={document.title}
|
||||||
|
>
|
||||||
{document.title}
|
{document.title}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
|||||||
@ -52,13 +52,7 @@ export default async function TeamsSettingsPage({ params }: TeamsSettingsPagePro
|
|||||||
|
|
||||||
<AvatarImageForm className="mb-8" team={team} user={session.user} />
|
<AvatarImageForm className="mb-8" team={team} user={session.user} />
|
||||||
|
|
||||||
<UpdateTeamForm
|
<UpdateTeamForm teamId={team.id} teamName={team.name} teamUrl={team.url} />
|
||||||
teamId={team.id}
|
|
||||||
teamName={team.name}
|
|
||||||
teamUrl={team.url}
|
|
||||||
documentVisibility={team.teamGlobalSettings?.documentVisibility}
|
|
||||||
includeSenderDetails={team.teamGlobalSettings?.includeSenderDetails}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<section className="mt-6 space-y-6">
|
<section className="mt-6 space-y-6">
|
||||||
{(team.teamEmail || team.emailVerification) && (
|
{(team.teamEmail || team.emailVerification) && (
|
||||||
|
|||||||
@ -39,6 +39,8 @@ const ZTeamDocumentPreferencesFormSchema = z.object({
|
|||||||
documentVisibility: z.nativeEnum(DocumentVisibility),
|
documentVisibility: z.nativeEnum(DocumentVisibility),
|
||||||
documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES),
|
documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES),
|
||||||
includeSenderDetails: z.boolean(),
|
includeSenderDetails: z.boolean(),
|
||||||
|
typedSignatureEnabled: z.boolean(),
|
||||||
|
includeSigningCertificate: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type TTeamDocumentPreferencesFormSchema = z.infer<typeof ZTeamDocumentPreferencesFormSchema>;
|
type TTeamDocumentPreferencesFormSchema = z.infer<typeof ZTeamDocumentPreferencesFormSchema>;
|
||||||
@ -68,6 +70,8 @@ export const TeamDocumentPreferencesForm = ({
|
|||||||
? settings?.documentLanguage
|
? settings?.documentLanguage
|
||||||
: 'en',
|
: 'en',
|
||||||
includeSenderDetails: settings?.includeSenderDetails ?? false,
|
includeSenderDetails: settings?.includeSenderDetails ?? false,
|
||||||
|
typedSignatureEnabled: settings?.typedSignatureEnabled ?? true,
|
||||||
|
includeSigningCertificate: settings?.includeSigningCertificate ?? true,
|
||||||
},
|
},
|
||||||
resolver: zodResolver(ZTeamDocumentPreferencesFormSchema),
|
resolver: zodResolver(ZTeamDocumentPreferencesFormSchema),
|
||||||
});
|
});
|
||||||
@ -76,7 +80,13 @@ export const TeamDocumentPreferencesForm = ({
|
|||||||
|
|
||||||
const onSubmit = async (data: TTeamDocumentPreferencesFormSchema) => {
|
const onSubmit = async (data: TTeamDocumentPreferencesFormSchema) => {
|
||||||
try {
|
try {
|
||||||
const { documentVisibility, documentLanguage, includeSenderDetails } = data;
|
const {
|
||||||
|
documentVisibility,
|
||||||
|
documentLanguage,
|
||||||
|
includeSenderDetails,
|
||||||
|
includeSigningCertificate,
|
||||||
|
typedSignatureEnabled,
|
||||||
|
} = data;
|
||||||
|
|
||||||
await updateTeamDocumentPreferences({
|
await updateTeamDocumentPreferences({
|
||||||
teamId: team.id,
|
teamId: team.id,
|
||||||
@ -84,6 +94,8 @@ export const TeamDocumentPreferencesForm = ({
|
|||||||
documentVisibility,
|
documentVisibility,
|
||||||
documentLanguage,
|
documentLanguage,
|
||||||
includeSenderDetails,
|
includeSenderDetails,
|
||||||
|
typedSignatureEnabled,
|
||||||
|
includeSigningCertificate,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -105,7 +117,7 @@ export const TeamDocumentPreferencesForm = ({
|
|||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||||
<fieldset
|
<fieldset
|
||||||
className="flex h-full max-w-xl flex-col gap-y-4"
|
className="flex h-full max-w-xl flex-col gap-y-6"
|
||||||
disabled={form.formState.isSubmitting}
|
disabled={form.formState.isSubmitting}
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
@ -227,6 +239,67 @@ export const TeamDocumentPreferencesForm = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="typedSignatureEnabled"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex-1">
|
||||||
|
<FormLabel>
|
||||||
|
<Trans>Enable Typed Signature</Trans>
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<FormControl className="block">
|
||||||
|
<Switch
|
||||||
|
ref={field.ref}
|
||||||
|
name={field.name}
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormDescription>
|
||||||
|
<Trans>
|
||||||
|
Controls whether the recipients can sign the documents using a typed signature.
|
||||||
|
Enable or disable the typed signature globally.
|
||||||
|
</Trans>
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="includeSigningCertificate"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex-1">
|
||||||
|
<FormLabel>
|
||||||
|
<Trans>Include the Signing Certificate in the Document</Trans>
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<FormControl className="block">
|
||||||
|
<Switch
|
||||||
|
ref={field.ref}
|
||||||
|
name={field.name}
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormDescription>
|
||||||
|
<Trans>
|
||||||
|
Controls whether the signing certificate will be included in the document when
|
||||||
|
it is downloaded. The signing certificate can still be downloaded from the logs
|
||||||
|
page separately.
|
||||||
|
</Trans>
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="flex flex-row justify-end space-x-4">
|
<div className="flex flex-row justify-end space-x-4">
|
||||||
<Button type="submit" loading={form.formState.isSubmitting}>
|
<Button type="submit" loading={form.formState.isSubmitting}>
|
||||||
<Trans>Save</Trans>
|
<Trans>Save</Trans>
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { ZCssVarsSchema } from './css-vars';
|
||||||
|
|
||||||
export const ZBaseEmbedDataSchema = z.object({
|
export const ZBaseEmbedDataSchema = z.object({
|
||||||
|
darkModeDisabled: z.boolean().optional().default(false),
|
||||||
css: z
|
css: z
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
.optional()
|
||||||
.transform((value) => value || undefined),
|
.transform((value) => value || undefined),
|
||||||
|
cssVars: ZCssVarsSchema.optional().default({}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export type EmbedDocumentCompletedPageProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const EmbedDocumentCompleted = ({ name, signature }: EmbedDocumentCompletedPageProps) => {
|
export const EmbedDocumentCompleted = ({ name, signature }: EmbedDocumentCompletedPageProps) => {
|
||||||
|
console.log({ signature });
|
||||||
return (
|
return (
|
||||||
<div className="relative mx-auto flex min-h-[100dvh] max-w-screen-lg flex-col items-center justify-center p-6">
|
<div className="relative mx-auto flex min-h-[100dvh] max-w-screen-lg flex-col items-center justify-center p-6">
|
||||||
<h3 className="text-foreground text-2xl font-semibold">
|
<h3 className="text-foreground text-2xl font-semibold">
|
||||||
|
|||||||
59
apps/web/src/app/embed/css-vars.ts
Normal file
59
apps/web/src/app/embed/css-vars.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { colord } from 'colord';
|
||||||
|
import { toSnakeCase } from 'remeda';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const ZCssVarsSchema = z
|
||||||
|
.object({
|
||||||
|
background: z.string().optional().describe('Base background color'),
|
||||||
|
foreground: z.string().optional().describe('Base text color'),
|
||||||
|
muted: z.string().optional().describe('Muted/subtle background color'),
|
||||||
|
mutedForeground: z.string().optional().describe('Muted/subtle text color'),
|
||||||
|
popover: z.string().optional().describe('Popover/dropdown background color'),
|
||||||
|
popoverForeground: z.string().optional().describe('Popover/dropdown text color'),
|
||||||
|
card: z.string().optional().describe('Card background color'),
|
||||||
|
cardBorder: z.string().optional().describe('Card border color'),
|
||||||
|
cardBorderTint: z.string().optional().describe('Card border tint/highlight color'),
|
||||||
|
cardForeground: z.string().optional().describe('Card text color'),
|
||||||
|
fieldCard: z.string().optional().describe('Field card background color'),
|
||||||
|
fieldCardBorder: z.string().optional().describe('Field card border color'),
|
||||||
|
fieldCardForeground: z.string().optional().describe('Field card text color'),
|
||||||
|
widget: z.string().optional().describe('Widget background color'),
|
||||||
|
widgetForeground: z.string().optional().describe('Widget text color'),
|
||||||
|
border: z.string().optional().describe('Default border color'),
|
||||||
|
input: z.string().optional().describe('Input field border color'),
|
||||||
|
primary: z.string().optional().describe('Primary action/button color'),
|
||||||
|
primaryForeground: z.string().optional().describe('Primary action/button text color'),
|
||||||
|
secondary: z.string().optional().describe('Secondary action/button color'),
|
||||||
|
secondaryForeground: z.string().optional().describe('Secondary action/button text color'),
|
||||||
|
accent: z.string().optional().describe('Accent/highlight color'),
|
||||||
|
accentForeground: z.string().optional().describe('Accent/highlight text color'),
|
||||||
|
destructive: z.string().optional().describe('Destructive/danger action color'),
|
||||||
|
destructiveForeground: z.string().optional().describe('Destructive/danger text color'),
|
||||||
|
ring: z.string().optional().describe('Focus ring color'),
|
||||||
|
radius: z.string().optional().describe('Border radius size in REM units'),
|
||||||
|
warning: z.string().optional().describe('Warning/alert color'),
|
||||||
|
})
|
||||||
|
.describe('Custom CSS variables for theming');
|
||||||
|
|
||||||
|
export type TCssVarsSchema = z.infer<typeof ZCssVarsSchema>;
|
||||||
|
|
||||||
|
export const toNativeCssVars = (vars: TCssVarsSchema) => {
|
||||||
|
const cssVars: Record<string, string> = {};
|
||||||
|
|
||||||
|
const { radius, ...colorVars } = vars;
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(colorVars)) {
|
||||||
|
if (value) {
|
||||||
|
const color = colord(value);
|
||||||
|
const { h, s, l } = color.toHsl();
|
||||||
|
|
||||||
|
cssVars[`--${toSnakeCase(key)}`] = `${h} ${s} ${l}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (radius) {
|
||||||
|
cssVars[`--radius`] = `${radius}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cssVars;
|
||||||
|
};
|
||||||
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||||
|
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-form
|
|||||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||||
import { validateFieldsInserted } from '@documenso/lib/utils/fields';
|
import { validateFieldsInserted } from '@documenso/lib/utils/fields';
|
||||||
import type { DocumentMeta, Recipient, TemplateMeta } from '@documenso/prisma/client';
|
import type { DocumentMeta, Recipient, Signature, TemplateMeta } from '@documenso/prisma/client';
|
||||||
import { type DocumentData, type Field, FieldType } from '@documenso/prisma/client';
|
import { type DocumentData, type Field, FieldType } from '@documenso/prisma/client';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import type {
|
import type {
|
||||||
@ -38,6 +38,7 @@ import { Logo } from '~/components/branding/logo';
|
|||||||
import { EmbedClientLoading } from '../../client-loading';
|
import { EmbedClientLoading } from '../../client-loading';
|
||||||
import { EmbedDocumentCompleted } from '../../completed';
|
import { EmbedDocumentCompleted } from '../../completed';
|
||||||
import { EmbedDocumentFields } from '../../document-fields';
|
import { EmbedDocumentFields } from '../../document-fields';
|
||||||
|
import { injectCss } from '../../util';
|
||||||
import { ZDirectTemplateEmbedDataSchema } from './schema';
|
import { ZDirectTemplateEmbedDataSchema } from './schema';
|
||||||
|
|
||||||
export type EmbedDirectTemplateClientPageProps = {
|
export type EmbedDirectTemplateClientPageProps = {
|
||||||
@ -47,6 +48,8 @@ export type EmbedDirectTemplateClientPageProps = {
|
|||||||
recipient: Recipient;
|
recipient: Recipient;
|
||||||
fields: Field[];
|
fields: Field[];
|
||||||
metadata?: DocumentMeta | TemplateMeta | null;
|
metadata?: DocumentMeta | TemplateMeta | null;
|
||||||
|
hidePoweredBy?: boolean;
|
||||||
|
isPlatformOrEnterprise?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EmbedDirectTemplateClientPage = ({
|
export const EmbedDirectTemplateClientPage = ({
|
||||||
@ -56,6 +59,8 @@ export const EmbedDirectTemplateClientPage = ({
|
|||||||
recipient,
|
recipient,
|
||||||
fields,
|
fields,
|
||||||
metadata,
|
metadata,
|
||||||
|
hidePoweredBy = false,
|
||||||
|
isPlatformOrEnterprise = false,
|
||||||
}: EmbedDirectTemplateClientPageProps) => {
|
}: EmbedDirectTemplateClientPageProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
@ -108,9 +113,9 @@ export const EmbedDirectTemplateClientPage = ({
|
|||||||
created: new Date(),
|
created: new Date(),
|
||||||
recipientId: 1,
|
recipientId: 1,
|
||||||
fieldId: 1,
|
fieldId: 1,
|
||||||
signatureImageAsBase64: payload.value,
|
signatureImageAsBase64: payload.value.startsWith('data:') ? payload.value : null,
|
||||||
typedSignature: null,
|
typedSignature: payload.value.startsWith('data:') ? null : payload.value,
|
||||||
};
|
} satisfies Signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === FieldType.DATE) {
|
if (field.type === FieldType.DATE) {
|
||||||
@ -249,7 +254,7 @@ export const EmbedDirectTemplateClientPage = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const hash = window.location.hash.slice(1);
|
const hash = window.location.hash.slice(1);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -264,6 +269,17 @@ export const EmbedDirectTemplateClientPage = ({
|
|||||||
setFullName(data.name);
|
setFullName(data.name);
|
||||||
setIsNameLocked(!!data.lockName);
|
setIsNameLocked(!!data.lockName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.darkModeDisabled) {
|
||||||
|
document.documentElement.classList.add('dark-mode-disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPlatformOrEnterprise) {
|
||||||
|
injectCss({
|
||||||
|
css: data.css,
|
||||||
|
cssVars: data.cssVars,
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
@ -296,8 +312,8 @@ export const EmbedDirectTemplateClientPage = ({
|
|||||||
fieldId: 1,
|
fieldId: 1,
|
||||||
recipientId: 1,
|
recipientId: 1,
|
||||||
created: new Date(),
|
created: new Date(),
|
||||||
typedSignature: null,
|
signatureImageAsBase64: signature?.startsWith('data:') ? signature : null,
|
||||||
signatureImageAsBase64: signature,
|
typedSignature: signature?.startsWith('data:') ? null : signature,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -452,10 +468,12 @@ export const EmbedDirectTemplateClientPage = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-primary text-primary-foreground fixed bottom-0 left-0 z-40 rounded-tr px-2 py-1 text-xs font-medium opacity-60 hover:opacity-100">
|
{!hidePoweredBy && (
|
||||||
<span>Powered by</span>
|
<div className="bg-primary text-primary-foreground fixed bottom-0 left-0 z-40 rounded-tr px-2 py-1 text-xs font-medium opacity-60 hover:opacity-100">
|
||||||
<Logo className="ml-2 inline-block h-[14px]" />
|
<span>Powered by</span>
|
||||||
</div>
|
<Logo className="ml-2 inline-block h-[14px]" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,8 +2,11 @@ import { notFound } from 'next/navigation';
|
|||||||
|
|
||||||
import { match } from 'ts-pattern';
|
import { match } from 'ts-pattern';
|
||||||
|
|
||||||
|
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||||
|
import { isDocumentPlatform } from '@documenso/ee/server-only/util/is-document-platform';
|
||||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||||
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
||||||
|
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
|
||||||
import { getTemplateByDirectLinkToken } from '@documenso/lib/server-only/template/get-template-by-direct-link-token';
|
import { getTemplateByDirectLinkToken } from '@documenso/lib/server-only/template/get-template-by-direct-link-token';
|
||||||
import { DocumentAccessAuth } from '@documenso/lib/types/document-auth';
|
import { DocumentAccessAuth } from '@documenso/lib/types/document-auth';
|
||||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||||
@ -51,6 +54,14 @@ export default async function EmbedDirectTemplatePage({ params }: EmbedDirectTem
|
|||||||
documentAuth: template.authOptions,
|
documentAuth: template.authOptions,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [isPlatformDocument, isEnterpriseDocument] = await Promise.all([
|
||||||
|
isDocumentPlatform(template),
|
||||||
|
isUserEnterprise({
|
||||||
|
userId: template.userId,
|
||||||
|
teamId: template.teamId ?? undefined,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
const isAccessAuthValid = match(derivedRecipientAccessAuth)
|
const isAccessAuthValid = match(derivedRecipientAccessAuth)
|
||||||
.with(DocumentAccessAuth.ACCOUNT, () => user !== null)
|
.with(DocumentAccessAuth.ACCOUNT, () => user !== null)
|
||||||
.with(null, () => true)
|
.with(null, () => true)
|
||||||
@ -72,6 +83,12 @@ export default async function EmbedDirectTemplatePage({ params }: EmbedDirectTem
|
|||||||
|
|
||||||
const fields = template.Field.filter((field) => field.recipientId === directTemplateRecipientId);
|
const fields = template.Field.filter((field) => field.recipientId === directTemplateRecipientId);
|
||||||
|
|
||||||
|
const team = template.teamId
|
||||||
|
? await getTeamById({ teamId: template.teamId, userId: template.userId }).catch(() => null)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const hidePoweredBy = team?.teamGlobalSettings?.brandingHidePoweredBy ?? false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SigningProvider email={user?.email} fullName={user?.name} signature={user?.signature}>
|
<SigningProvider email={user?.email} fullName={user?.name} signature={user?.signature}>
|
||||||
<DocumentAuthProvider
|
<DocumentAuthProvider
|
||||||
@ -86,6 +103,8 @@ export default async function EmbedDirectTemplatePage({ params }: EmbedDirectTem
|
|||||||
recipient={recipient}
|
recipient={recipient}
|
||||||
fields={fields}
|
fields={fields}
|
||||||
metadata={template.templateMeta}
|
metadata={template.templateMeta}
|
||||||
|
hidePoweredBy={isPlatformDocument || isEnterpriseDocument || hidePoweredBy}
|
||||||
|
isPlatformOrEnterprise={isPlatformDocument || isEnterpriseDocument}
|
||||||
/>
|
/>
|
||||||
</DocumentAuthProvider>
|
</DocumentAuthProvider>
|
||||||
</SigningProvider>
|
</SigningProvider>
|
||||||
|
|||||||
@ -58,6 +58,7 @@ export const EmbedDocumentFields = ({
|
|||||||
recipient={recipient}
|
recipient={recipient}
|
||||||
onSignField={onSignField}
|
onSignField={onSignField}
|
||||||
onUnsignField={onUnsignField}
|
onUnsignField={onUnsignField}
|
||||||
|
typedSignatureEnabled={metadata?.typedSignatureEnabled}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
.with(FieldType.INITIALS, () => (
|
.with(FieldType.INITIALS, () => (
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||||
|
|
||||||
import { Trans, msg } from '@lingui/macro';
|
import { Trans, msg } from '@lingui/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
@ -28,6 +28,7 @@ import { Logo } from '~/components/branding/logo';
|
|||||||
import { EmbedClientLoading } from '../../client-loading';
|
import { EmbedClientLoading } from '../../client-loading';
|
||||||
import { EmbedDocumentCompleted } from '../../completed';
|
import { EmbedDocumentCompleted } from '../../completed';
|
||||||
import { EmbedDocumentFields } from '../../document-fields';
|
import { EmbedDocumentFields } from '../../document-fields';
|
||||||
|
import { injectCss } from '../../util';
|
||||||
import { ZSignDocumentEmbedDataSchema } from './schema';
|
import { ZSignDocumentEmbedDataSchema } from './schema';
|
||||||
|
|
||||||
export type EmbedSignDocumentClientPageProps = {
|
export type EmbedSignDocumentClientPageProps = {
|
||||||
@ -38,6 +39,8 @@ export type EmbedSignDocumentClientPageProps = {
|
|||||||
fields: Field[];
|
fields: Field[];
|
||||||
metadata?: DocumentMeta | TemplateMeta | null;
|
metadata?: DocumentMeta | TemplateMeta | null;
|
||||||
isCompleted?: boolean;
|
isCompleted?: boolean;
|
||||||
|
hidePoweredBy?: boolean;
|
||||||
|
isPlatformOrEnterprise?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EmbedSignDocumentClientPage = ({
|
export const EmbedSignDocumentClientPage = ({
|
||||||
@ -48,6 +51,8 @@ export const EmbedSignDocumentClientPage = ({
|
|||||||
fields,
|
fields,
|
||||||
metadata,
|
metadata,
|
||||||
isCompleted,
|
isCompleted,
|
||||||
|
hidePoweredBy = false,
|
||||||
|
isPlatformOrEnterprise = false,
|
||||||
}: EmbedSignDocumentClientPageProps) => {
|
}: EmbedSignDocumentClientPageProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
@ -131,7 +136,7 @@ export const EmbedSignDocumentClientPage = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const hash = window.location.hash.slice(1);
|
const hash = window.location.hash.slice(1);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -144,6 +149,17 @@ export const EmbedSignDocumentClientPage = ({
|
|||||||
// Since a recipient can be provided a name we can lock it without requiring
|
// Since a recipient can be provided a name we can lock it without requiring
|
||||||
// a to be provided by the parent application, unlike direct templates.
|
// a to be provided by the parent application, unlike direct templates.
|
||||||
setIsNameLocked(!!data.lockName);
|
setIsNameLocked(!!data.lockName);
|
||||||
|
|
||||||
|
if (data.darkModeDisabled) {
|
||||||
|
document.documentElement.classList.add('dark-mode-disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPlatformOrEnterprise) {
|
||||||
|
injectCss({
|
||||||
|
css: data.css,
|
||||||
|
cssVars: data.cssVars,
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
@ -176,8 +192,8 @@ export const EmbedSignDocumentClientPage = ({
|
|||||||
fieldId: 1,
|
fieldId: 1,
|
||||||
recipientId: 1,
|
recipientId: 1,
|
||||||
created: new Date(),
|
created: new Date(),
|
||||||
typedSignature: null,
|
signatureImageAsBase64: signature?.startsWith('data:') ? signature : null,
|
||||||
signatureImageAsBase64: signature,
|
typedSignature: signature?.startsWith('data:') ? null : signature,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -202,7 +218,7 @@ export const EmbedSignDocumentClientPage = ({
|
|||||||
className="group/document-widget fixed bottom-8 left-0 z-50 h-fit w-full flex-shrink-0 px-6 md:sticky md:top-4 md:z-auto md:w-[350px] md:px-0"
|
className="group/document-widget fixed bottom-8 left-0 z-50 h-fit w-full flex-shrink-0 px-6 md:sticky md:top-4 md:z-auto md:w-[350px] md:px-0"
|
||||||
data-expanded={isExpanded || undefined}
|
data-expanded={isExpanded || undefined}
|
||||||
>
|
>
|
||||||
<div className="border-border bg-widget flex w-full flex-col rounded-xl border px-4 py-4 md:py-6">
|
<div className="border-border bg-widget flex w-full flex-col rounded-xl border px-4 py-4 md:py-6">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center justify-between gap-x-2">
|
<div className="flex items-center justify-between gap-x-2">
|
||||||
@ -325,10 +341,12 @@ export const EmbedSignDocumentClientPage = ({
|
|||||||
<EmbedDocumentFields recipient={recipient} fields={fields} metadata={metadata} />
|
<EmbedDocumentFields recipient={recipient} fields={fields} metadata={metadata} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-primary text-primary-foreground fixed bottom-0 left-0 z-40 rounded-tr px-2 py-1 text-xs font-medium opacity-60 hover:opacity-100">
|
{!hidePoweredBy && (
|
||||||
<span>Powered by</span>
|
<div className="bg-primary text-primary-foreground fixed bottom-0 left-0 z-40 rounded-tr px-2 py-1 text-xs font-medium opacity-60 hover:opacity-100">
|
||||||
<Logo className="ml-2 inline-block h-[14px]" />
|
<span>Powered by</span>
|
||||||
</div>
|
<Logo className="ml-2 inline-block h-[14px]" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,11 +2,14 @@ import { notFound } from 'next/navigation';
|
|||||||
|
|
||||||
import { match } from 'ts-pattern';
|
import { match } from 'ts-pattern';
|
||||||
|
|
||||||
|
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||||
|
import { isDocumentPlatform } from '@documenso/ee/server-only/util/is-document-platform';
|
||||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||||
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
||||||
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
||||||
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
|
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
|
||||||
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
|
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
|
||||||
|
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
|
||||||
import { DocumentAccessAuth } from '@documenso/lib/types/document-auth';
|
import { DocumentAccessAuth } from '@documenso/lib/types/document-auth';
|
||||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||||
import { DocumentStatus } from '@documenso/prisma/client';
|
import { DocumentStatus } from '@documenso/prisma/client';
|
||||||
@ -56,6 +59,14 @@ export default async function EmbedSignDocumentPage({ params }: EmbedSignDocumen
|
|||||||
return <EmbedPaywall />;
|
return <EmbedPaywall />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [isPlatformDocument, isEnterpriseDocument] = await Promise.all([
|
||||||
|
isDocumentPlatform(document),
|
||||||
|
isUserEnterprise({
|
||||||
|
userId: document.userId,
|
||||||
|
teamId: document.teamId ?? undefined,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
const { derivedRecipientAccessAuth } = extractDocumentAuthMethods({
|
const { derivedRecipientAccessAuth } = extractDocumentAuthMethods({
|
||||||
documentAuth: document.authOptions,
|
documentAuth: document.authOptions,
|
||||||
});
|
});
|
||||||
@ -74,6 +85,12 @@ export default async function EmbedSignDocumentPage({ params }: EmbedSignDocumen
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const team = document.teamId
|
||||||
|
? await getTeamById({ teamId: document.teamId, userId: document.userId }).catch(() => null)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const hidePoweredBy = team?.teamGlobalSettings?.brandingHidePoweredBy ?? false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SigningProvider
|
<SigningProvider
|
||||||
email={recipient.email}
|
email={recipient.email}
|
||||||
@ -93,6 +110,8 @@ export default async function EmbedSignDocumentPage({ params }: EmbedSignDocumen
|
|||||||
fields={fields}
|
fields={fields}
|
||||||
metadata={document.documentMeta}
|
metadata={document.documentMeta}
|
||||||
isCompleted={document.status === DocumentStatus.COMPLETED}
|
isCompleted={document.status === DocumentStatus.COMPLETED}
|
||||||
|
hidePoweredBy={isPlatformDocument || isEnterpriseDocument || hidePoweredBy}
|
||||||
|
isPlatformOrEnterprise={isPlatformDocument || isEnterpriseDocument}
|
||||||
/>
|
/>
|
||||||
</DocumentAuthProvider>
|
</DocumentAuthProvider>
|
||||||
</SigningProvider>
|
</SigningProvider>
|
||||||
|
|||||||
20
apps/web/src/app/embed/util.ts
Normal file
20
apps/web/src/app/embed/util.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { type TCssVarsSchema, toNativeCssVars } from './css-vars';
|
||||||
|
|
||||||
|
export const injectCss = (options: { css?: string; cssVars?: TCssVarsSchema }) => {
|
||||||
|
const { css, cssVars } = options;
|
||||||
|
|
||||||
|
if (css) {
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.innerHTML = css;
|
||||||
|
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cssVars) {
|
||||||
|
const nativeVars = toNativeCssVars(cssVars);
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(nativeVars)) {
|
||||||
|
document.documentElement.style.setProperty(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -6,22 +6,14 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|||||||
import { Trans, msg } from '@lingui/macro';
|
import { Trans, msg } from '@lingui/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { useSession } from 'next-auth/react';
|
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { match } from 'ts-pattern';
|
|
||||||
import type { z } from 'zod';
|
import type { z } from 'zod';
|
||||||
|
|
||||||
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
import { DocumentVisibility } from '@documenso/prisma/client';
|
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { ZUpdateTeamMutationSchema } from '@documenso/trpc/server/team-router/schema';
|
import { ZUpdateTeamMutationSchema } from '@documenso/trpc/server/team-router/schema';
|
||||||
import {
|
|
||||||
DocumentVisibilitySelect,
|
|
||||||
DocumentVisibilityTooltip,
|
|
||||||
} from '@documenso/ui/components/document/document-visibility-select';
|
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
import { Checkbox } from '@documenso/ui/primitives/checkbox';
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@ -37,29 +29,17 @@ export type UpdateTeamDialogProps = {
|
|||||||
teamId: number;
|
teamId: number;
|
||||||
teamName: string;
|
teamName: string;
|
||||||
teamUrl: string;
|
teamUrl: string;
|
||||||
documentVisibility?: DocumentVisibility;
|
|
||||||
includeSenderDetails?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ZUpdateTeamFormSchema = ZUpdateTeamMutationSchema.shape.data.pick({
|
const ZUpdateTeamFormSchema = ZUpdateTeamMutationSchema.shape.data.pick({
|
||||||
name: true,
|
name: true,
|
||||||
url: true,
|
url: true,
|
||||||
documentVisibility: true,
|
|
||||||
includeSenderDetails: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type TUpdateTeamFormSchema = z.infer<typeof ZUpdateTeamFormSchema>;
|
type TUpdateTeamFormSchema = z.infer<typeof ZUpdateTeamFormSchema>;
|
||||||
|
|
||||||
export const UpdateTeamForm = ({
|
export const UpdateTeamForm = ({ teamId, teamName, teamUrl }: UpdateTeamDialogProps) => {
|
||||||
teamId,
|
|
||||||
teamName,
|
|
||||||
teamUrl,
|
|
||||||
documentVisibility,
|
|
||||||
includeSenderDetails,
|
|
||||||
}: UpdateTeamDialogProps) => {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { data: session } = useSession();
|
|
||||||
const email = session?.user?.email;
|
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
@ -68,36 +48,17 @@ export const UpdateTeamForm = ({
|
|||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: teamName,
|
name: teamName,
|
||||||
url: teamUrl,
|
url: teamUrl,
|
||||||
documentVisibility,
|
|
||||||
includeSenderDetails,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutateAsync: updateTeam } = trpc.team.updateTeam.useMutation();
|
const { mutateAsync: updateTeam } = trpc.team.updateTeam.useMutation();
|
||||||
const includeSenderDetailsCheck = form.watch('includeSenderDetails');
|
|
||||||
|
|
||||||
const mapVisibilityToRole = (visibility: DocumentVisibility): DocumentVisibility =>
|
const onFormSubmit = async ({ name, url }: TUpdateTeamFormSchema) => {
|
||||||
match(visibility)
|
|
||||||
.with(DocumentVisibility.ADMIN, () => DocumentVisibility.ADMIN)
|
|
||||||
.with(DocumentVisibility.MANAGER_AND_ABOVE, () => DocumentVisibility.MANAGER_AND_ABOVE)
|
|
||||||
.otherwise(() => DocumentVisibility.EVERYONE);
|
|
||||||
|
|
||||||
const currentVisibilityRole = mapVisibilityToRole(
|
|
||||||
documentVisibility ?? DocumentVisibility.EVERYONE,
|
|
||||||
);
|
|
||||||
const onFormSubmit = async ({
|
|
||||||
name,
|
|
||||||
url,
|
|
||||||
documentVisibility,
|
|
||||||
includeSenderDetails,
|
|
||||||
}: TUpdateTeamFormSchema) => {
|
|
||||||
try {
|
try {
|
||||||
await updateTeam({
|
await updateTeam({
|
||||||
data: {
|
data: {
|
||||||
name,
|
name,
|
||||||
url,
|
url,
|
||||||
documentVisibility,
|
|
||||||
includeSenderDetails,
|
|
||||||
},
|
},
|
||||||
teamId,
|
teamId,
|
||||||
});
|
});
|
||||||
@ -111,8 +72,6 @@ export const UpdateTeamForm = ({
|
|||||||
form.reset({
|
form.reset({
|
||||||
name,
|
name,
|
||||||
url,
|
url,
|
||||||
documentVisibility,
|
|
||||||
includeSenderDetails,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (url !== teamUrl) {
|
if (url !== teamUrl) {
|
||||||
@ -186,68 +145,6 @@ export const UpdateTeamForm = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="documentVisibility"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel className="mt-4 flex flex-row items-center">
|
|
||||||
<Trans>Default Document Visibility</Trans>
|
|
||||||
<DocumentVisibilityTooltip />
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<DocumentVisibilitySelect
|
|
||||||
currentMemberRole={currentVisibilityRole}
|
|
||||||
isTeamSettings={true}
|
|
||||||
{...field}
|
|
||||||
onValueChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="mb-4">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="includeSenderDetails"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<div className="mt-6 flex flex-row items-center gap-4">
|
|
||||||
<FormLabel>
|
|
||||||
<Trans>Send on Behalf of Team</Trans>
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Checkbox
|
|
||||||
className="h-5 w-5"
|
|
||||||
checkClassName="text-white"
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{includeSenderDetailsCheck ? (
|
|
||||||
<blockquote className="text-foreground/50 text-xs italic">
|
|
||||||
<Trans>
|
|
||||||
"{email}" on behalf of "{teamName}" has invited you to sign "example
|
|
||||||
document".
|
|
||||||
</Trans>
|
|
||||||
</blockquote>
|
|
||||||
) : (
|
|
||||||
<blockquote className="text-foreground/50 text-xs italic">
|
|
||||||
<Trans>"{teamUrl}" has invited you to sign "example document".</Trans>
|
|
||||||
</blockquote>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-row justify-end space-x-4">
|
<div className="flex flex-row justify-end space-x-4">
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{form.formState.isDirty && (
|
{form.formState.isDirty && (
|
||||||
|
|||||||
@ -138,6 +138,7 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
|
|||||||
containerClassName={cn('rounded-lg border bg-background')}
|
containerClassName={cn('rounded-lg border bg-background')}
|
||||||
defaultValue={user.signature ?? undefined}
|
defaultValue={user.signature ?? undefined}
|
||||||
onChange={(v) => onChange(v ?? '')}
|
onChange={(v) => onChange(v ?? '')}
|
||||||
|
allowTypedSignature={true}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
773
package-lock.json
generated
773
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.8.1-rc.0",
|
"version": "1.8.1-rc.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "turbo run build",
|
"build": "turbo run build",
|
||||||
"build:web": "turbo run build --filter=@documenso/web",
|
"build:web": "turbo run build --filter=@documenso/web",
|
||||||
@ -52,7 +52,7 @@
|
|||||||
"husky": "^9.0.11",
|
"husky": "^9.0.11",
|
||||||
"lint-staged": "^15.2.2",
|
"lint-staged": "^15.2.2",
|
||||||
"playwright": "1.43.0",
|
"playwright": "1.43.0",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^3.3.3",
|
||||||
"rimraf": "^5.0.1",
|
"rimraf": "^5.0.1",
|
||||||
"turbo": "^1.9.3"
|
"turbo": "^1.9.3"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -302,6 +302,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
redirectUrl: body.meta.redirectUrl,
|
redirectUrl: body.meta.redirectUrl,
|
||||||
signingOrder: body.meta.signingOrder,
|
signingOrder: body.meta.signingOrder,
|
||||||
language: body.meta.language,
|
language: body.meta.language,
|
||||||
|
typedSignatureEnabled: body.meta.typedSignatureEnabled,
|
||||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
requestMetadata: extractNextApiRequestMetadata(args.req),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||||
import '@documenso/lib/constants/time-zones';
|
|
||||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||||
import { ZUrlSchema } from '@documenso/lib/schemas/common';
|
import { ZUrlSchema } from '@documenso/lib/schemas/common';
|
||||||
import {
|
import {
|
||||||
@ -14,6 +13,7 @@ import {
|
|||||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||||
import {
|
import {
|
||||||
DocumentDataType,
|
DocumentDataType,
|
||||||
|
DocumentDistributionMethod,
|
||||||
DocumentSigningOrder,
|
DocumentSigningOrder,
|
||||||
FieldType,
|
FieldType,
|
||||||
ReadStatus,
|
ReadStatus,
|
||||||
@ -132,6 +132,7 @@ export const ZCreateDocumentMutationSchema = z.object({
|
|||||||
redirectUrl: z.string(),
|
redirectUrl: z.string(),
|
||||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||||
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
|
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
|
||||||
|
typedSignatureEnabled: z.boolean().optional().default(true),
|
||||||
})
|
})
|
||||||
.partial(),
|
.partial(),
|
||||||
authOptions: z
|
authOptions: z
|
||||||
@ -226,14 +227,14 @@ export type TCreateDocumentFromTemplateMutationResponseSchema = z.infer<
|
|||||||
|
|
||||||
export const ZGenerateDocumentFromTemplateMutationSchema = z.object({
|
export const ZGenerateDocumentFromTemplateMutationSchema = z.object({
|
||||||
title: z.string().optional(),
|
title: z.string().optional(),
|
||||||
externalId: z.string().nullish(),
|
externalId: z.string().optional(),
|
||||||
recipients: z
|
recipients: z
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
id: z.number(),
|
id: z.number(),
|
||||||
|
email: z.string().email(),
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
email: z.string().email().min(1),
|
signingOrder: z.number().optional(),
|
||||||
signingOrder: z.number().nullish(),
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.refine(
|
.refine(
|
||||||
@ -252,8 +253,10 @@ export const ZGenerateDocumentFromTemplateMutationSchema = z.object({
|
|||||||
timezone: z.string(),
|
timezone: z.string(),
|
||||||
dateFormat: z.string(),
|
dateFormat: z.string(),
|
||||||
redirectUrl: ZUrlSchema,
|
redirectUrl: ZUrlSchema,
|
||||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
signingOrder: z.nativeEnum(DocumentSigningOrder),
|
||||||
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
|
language: z.enum(SUPPORTED_LANGUAGE_CODES),
|
||||||
|
distributionMethod: z.nativeEnum(DocumentDistributionMethod),
|
||||||
|
typedSignatureEnabled: z.boolean(),
|
||||||
})
|
})
|
||||||
.partial()
|
.partial()
|
||||||
.optional(),
|
.optional(),
|
||||||
|
|||||||
@ -0,0 +1,271 @@
|
|||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
import { PDFDocument } from 'pdf-lib';
|
||||||
|
|
||||||
|
import { getDocumentByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
||||||
|
import { getFile } from '@documenso/lib/universal/upload/get-file';
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
import { DocumentStatus, FieldType } from '@documenso/prisma/client';
|
||||||
|
import { seedPendingDocumentWithFullFields } from '@documenso/prisma/seed/documents';
|
||||||
|
import { seedTeam } from '@documenso/prisma/seed/teams';
|
||||||
|
import { seedUser } from '@documenso/prisma/seed/users';
|
||||||
|
|
||||||
|
import { apiSignin } from '../fixtures/authentication';
|
||||||
|
|
||||||
|
test.describe('Signing Certificate Tests', () => {
|
||||||
|
test('individual document should always include signing certificate', async ({ page }) => {
|
||||||
|
const user = await seedUser();
|
||||||
|
|
||||||
|
const { document, recipients } = await seedPendingDocumentWithFullFields({
|
||||||
|
owner: user,
|
||||||
|
recipients: ['signer@example.com'],
|
||||||
|
fields: [FieldType.SIGNATURE],
|
||||||
|
});
|
||||||
|
|
||||||
|
const documentData = await prisma.documentData
|
||||||
|
.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: document.documentDataId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(async (data) => getFile(data));
|
||||||
|
|
||||||
|
const originalPdf = await PDFDocument.load(documentData);
|
||||||
|
|
||||||
|
const recipient = recipients[0];
|
||||||
|
|
||||||
|
// Sign the document
|
||||||
|
await page.goto(`/sign/${recipient.token}`);
|
||||||
|
|
||||||
|
const canvas = page.locator('canvas');
|
||||||
|
const box = await canvas.boundingBox();
|
||||||
|
if (box) {
|
||||||
|
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
||||||
|
await page.mouse.down();
|
||||||
|
await page.mouse.move(box.x + box.width / 4, box.y + box.height / 4);
|
||||||
|
await page.mouse.up();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const field of recipient.Field) {
|
||||||
|
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
||||||
|
|
||||||
|
await expect(page.locator(`#field-${field.id}`)).toHaveAttribute('data-inserted', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Complete' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Sign' }).click();
|
||||||
|
await page.waitForURL(`/sign/${recipient.token}/complete`);
|
||||||
|
|
||||||
|
await expect(async () => {
|
||||||
|
const { status } = await getDocumentByToken({
|
||||||
|
token: recipient.token,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(status).toBe(DocumentStatus.COMPLETED);
|
||||||
|
}).toPass();
|
||||||
|
|
||||||
|
// Get the completed document
|
||||||
|
const completedDocument = await prisma.document.findFirstOrThrow({
|
||||||
|
where: { id: document.id },
|
||||||
|
include: { documentData: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const completedDocumentData = await getFile(completedDocument.documentData);
|
||||||
|
|
||||||
|
// Load the PDF and check number of pages
|
||||||
|
const pdfDoc = await PDFDocument.load(completedDocumentData);
|
||||||
|
|
||||||
|
expect(pdfDoc.getPageCount()).toBe(originalPdf.getPageCount() + 1); // Original + Certificate
|
||||||
|
});
|
||||||
|
|
||||||
|
test('team document with signing certificate enabled should include certificate', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const team = await seedTeam();
|
||||||
|
|
||||||
|
const { document, recipients } = await seedPendingDocumentWithFullFields({
|
||||||
|
owner: team.owner,
|
||||||
|
recipients: ['signer@example.com'],
|
||||||
|
fields: [FieldType.SIGNATURE],
|
||||||
|
updateDocumentOptions: {
|
||||||
|
teamId: team.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.teamGlobalSettings.create({
|
||||||
|
data: {
|
||||||
|
teamId: team.id,
|
||||||
|
includeSigningCertificate: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const documentData = await prisma.documentData
|
||||||
|
.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: document.documentDataId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(async (data) => getFile(data));
|
||||||
|
|
||||||
|
const originalPdf = await PDFDocument.load(documentData);
|
||||||
|
|
||||||
|
const recipient = recipients[0];
|
||||||
|
|
||||||
|
// Sign the document
|
||||||
|
await page.goto(`/sign/${recipient.token}`);
|
||||||
|
|
||||||
|
const canvas = page.locator('canvas');
|
||||||
|
const box = await canvas.boundingBox();
|
||||||
|
if (box) {
|
||||||
|
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
||||||
|
await page.mouse.down();
|
||||||
|
await page.mouse.move(box.x + box.width / 4, box.y + box.height / 4);
|
||||||
|
await page.mouse.up();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const field of recipient.Field) {
|
||||||
|
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
||||||
|
|
||||||
|
await expect(page.locator(`#field-${field.id}`)).toHaveAttribute('data-inserted', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Complete' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Sign' }).click();
|
||||||
|
await page.waitForURL(`/sign/${recipient.token}/complete`);
|
||||||
|
|
||||||
|
await expect(async () => {
|
||||||
|
const { status } = await getDocumentByToken({
|
||||||
|
token: recipient.token,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(status).toBe(DocumentStatus.COMPLETED);
|
||||||
|
}).toPass();
|
||||||
|
|
||||||
|
// Get the completed document
|
||||||
|
const completedDocument = await prisma.document.findFirstOrThrow({
|
||||||
|
where: { id: document.id },
|
||||||
|
include: { documentData: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const completedDocumentData = await getFile(completedDocument.documentData);
|
||||||
|
|
||||||
|
// Load the PDF and check number of pages
|
||||||
|
const completedPdf = await PDFDocument.load(completedDocumentData);
|
||||||
|
|
||||||
|
expect(completedPdf.getPageCount()).toBe(originalPdf.getPageCount() + 1); // Original + Certificate
|
||||||
|
});
|
||||||
|
|
||||||
|
test('team document with signing certificate disabled should not include certificate', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const team = await seedTeam();
|
||||||
|
|
||||||
|
const { document, recipients } = await seedPendingDocumentWithFullFields({
|
||||||
|
owner: team.owner,
|
||||||
|
recipients: ['signer@example.com'],
|
||||||
|
fields: [FieldType.SIGNATURE],
|
||||||
|
updateDocumentOptions: {
|
||||||
|
teamId: team.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.teamGlobalSettings.create({
|
||||||
|
data: {
|
||||||
|
teamId: team.id,
|
||||||
|
includeSigningCertificate: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const documentData = await prisma.documentData
|
||||||
|
.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: document.documentDataId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(async (data) => getFile(data));
|
||||||
|
|
||||||
|
const originalPdf = await PDFDocument.load(documentData);
|
||||||
|
|
||||||
|
const recipient = recipients[0];
|
||||||
|
|
||||||
|
// Sign the document
|
||||||
|
await page.goto(`/sign/${recipient.token}`);
|
||||||
|
|
||||||
|
const canvas = page.locator('canvas');
|
||||||
|
const box = await canvas.boundingBox();
|
||||||
|
if (box) {
|
||||||
|
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
||||||
|
await page.mouse.down();
|
||||||
|
await page.mouse.move(box.x + box.width / 4, box.y + box.height / 4);
|
||||||
|
await page.mouse.up();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const field of recipient.Field) {
|
||||||
|
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
||||||
|
|
||||||
|
await expect(page.locator(`#field-${field.id}`)).toHaveAttribute('data-inserted', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Complete' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Sign' }).click();
|
||||||
|
await page.waitForURL(`/sign/${recipient.token}/complete`);
|
||||||
|
|
||||||
|
await expect(async () => {
|
||||||
|
const { status } = await getDocumentByToken({
|
||||||
|
token: recipient.token,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(status).toBe(DocumentStatus.COMPLETED);
|
||||||
|
}).toPass();
|
||||||
|
|
||||||
|
// Get the completed document
|
||||||
|
const completedDocument = await prisma.document.findFirstOrThrow({
|
||||||
|
where: { id: document.id },
|
||||||
|
include: { documentData: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const completedDocumentData = await getFile(completedDocument.documentData);
|
||||||
|
|
||||||
|
// Load the PDF and check number of pages
|
||||||
|
const completedPdf = await PDFDocument.load(completedDocumentData);
|
||||||
|
|
||||||
|
expect(completedPdf.getPageCount()).toBe(originalPdf.getPageCount());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('team can toggle signing certificate setting', async ({ page }) => {
|
||||||
|
const team = await seedTeam();
|
||||||
|
|
||||||
|
await apiSignin({
|
||||||
|
page,
|
||||||
|
email: team.owner.email,
|
||||||
|
redirectPath: `/t/${team.url}/settings/preferences`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggle signing certificate setting
|
||||||
|
await page.getByLabel('Include the Signing Certificate in the Document').click();
|
||||||
|
await page.getByRole('button', { name: /Save/ }).first().click();
|
||||||
|
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
|
// Verify the setting was saved
|
||||||
|
const updatedTeam = await prisma.team.findFirstOrThrow({
|
||||||
|
where: { id: team.id },
|
||||||
|
include: { teamGlobalSettings: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(updatedTeam.teamGlobalSettings?.includeSigningCertificate).toBe(false);
|
||||||
|
|
||||||
|
// Toggle the setting back to true
|
||||||
|
await page.getByLabel('Include the Signing Certificate in the Document').click();
|
||||||
|
await page.getByRole('button', { name: /Save/ }).first().click();
|
||||||
|
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
|
// Verify the setting was saved
|
||||||
|
const updatedTeam2 = await prisma.team.findFirstOrThrow({
|
||||||
|
where: { id: team.id },
|
||||||
|
include: { teamGlobalSettings: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(updatedTeam2.teamGlobalSettings?.includeSigningCertificate).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -17,19 +17,17 @@ test('[TEAMS]: update the default document visibility in the team global setting
|
|||||||
page,
|
page,
|
||||||
email: team.owner.email,
|
email: team.owner.email,
|
||||||
password: 'password',
|
password: 'password',
|
||||||
redirectPath: `/t/${team.url}/settings`,
|
redirectPath: `/t/${team.url}/settings/preferences`,
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.getByRole('combobox').click();
|
// !: Brittle selector
|
||||||
|
await page.getByRole('combobox').first().click();
|
||||||
await page.getByRole('option', { name: 'Admin' }).click();
|
await page.getByRole('option', { name: 'Admin' }).click();
|
||||||
await page.getByRole('button', { name: 'Update team' }).click();
|
await page.getByRole('button', { name: 'Save' }).first().click();
|
||||||
|
|
||||||
const toast = page.locator('li[role="status"][data-state="open"]').first();
|
const toast = page.locator('li[role="status"][data-state="open"]').first();
|
||||||
await expect(toast).toBeVisible();
|
await expect(toast).toBeVisible();
|
||||||
await expect(toast.getByText('Success', { exact: true })).toBeVisible();
|
await expect(toast.getByText('Document preferences updated', { exact: true })).toBeVisible();
|
||||||
await expect(
|
|
||||||
toast.getByText('Your team has been successfully updated.', { exact: true }),
|
|
||||||
).toBeVisible();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('[TEAMS]: update the sender details in the team global settings', async ({ page }) => {
|
test('[TEAMS]: update the sender details in the team global settings', async ({ page }) => {
|
||||||
@ -41,7 +39,7 @@ test('[TEAMS]: update the sender details in the team global settings', async ({
|
|||||||
page,
|
page,
|
||||||
email: team.owner.email,
|
email: team.owner.email,
|
||||||
password: 'password',
|
password: 'password',
|
||||||
redirectPath: `/t/${team.url}/settings`,
|
redirectPath: `/t/${team.url}/settings/preferences`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const checkbox = page.getByLabel('Send on Behalf of Team');
|
const checkbox = page.getByLabel('Send on Behalf of Team');
|
||||||
@ -49,14 +47,11 @@ test('[TEAMS]: update the sender details in the team global settings', async ({
|
|||||||
|
|
||||||
await expect(checkbox).toBeChecked();
|
await expect(checkbox).toBeChecked();
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Update team' }).click();
|
await page.getByRole('button', { name: 'Save' }).first().click();
|
||||||
|
|
||||||
const toast = page.locator('li[role="status"][data-state="open"]').first();
|
const toast = page.locator('li[role="status"][data-state="open"]').first();
|
||||||
await expect(toast).toBeVisible();
|
await expect(toast).toBeVisible();
|
||||||
await expect(toast.getByText('Success', { exact: true })).toBeVisible();
|
await expect(toast.getByText('Document preferences updated', { exact: true })).toBeVisible();
|
||||||
await expect(
|
|
||||||
toast.getByText('Your team has been successfully updated.', { exact: true }),
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await expect(checkbox).toBeChecked();
|
await expect(checkbox).toBeChecked();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,15 +7,17 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"test:dev": "NODE_OPTIONS=--experimental-require-module playwright test",
|
"test:dev": "NODE_OPTIONS=--experimental-require-module playwright test",
|
||||||
"test-ui:dev": "NODE_OPTIONS=--experimental-require-module playwright test --ui",
|
"test-ui:dev": "NODE_OPTIONS=--experimental-require-module playwright test --ui",
|
||||||
"test:e2e": "NODE_OPTIONS=--experimental-require-module start-server-and-test \"npm run start -w @documenso/web\" http://localhost:3000 \"playwright test\""
|
"test:e2e": "NODE_OPTIONS=--experimental-require-module start-server-and-test \"npm run start -w @documenso/web\" http://localhost:3000 \"playwright test $E2E_TEST_PATH\""
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.18.1",
|
"@playwright/test": "^1.18.1",
|
||||||
"@types/node": "^20.8.2",
|
"@types/node": "^20.8.2",
|
||||||
|
"@documenso/lib": "*",
|
||||||
"@documenso/prisma": "*",
|
"@documenso/prisma": "*",
|
||||||
"@documenso/web": "*"
|
"@documenso/web": "*",
|
||||||
|
"pdf-lib": "^1.17.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"start-server-and-test": "^2.0.1"
|
"start-server-and-test": "^2.0.1"
|
||||||
|
|||||||
@ -9,6 +9,7 @@ export const getDocumentRelatedPrices = async () => {
|
|||||||
return await getPricesByPlan([
|
return await getPricesByPlan([
|
||||||
STRIPE_PLAN_TYPE.REGULAR,
|
STRIPE_PLAN_TYPE.REGULAR,
|
||||||
STRIPE_PLAN_TYPE.COMMUNITY,
|
STRIPE_PLAN_TYPE.COMMUNITY,
|
||||||
|
STRIPE_PLAN_TYPE.PLATFORM,
|
||||||
STRIPE_PLAN_TYPE.ENTERPRISE,
|
STRIPE_PLAN_TYPE.ENTERPRISE,
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|||||||
13
packages/ee/server-only/stripe/get-platform-plan-prices.ts
Normal file
13
packages/ee/server-only/stripe/get-platform-plan-prices.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { STRIPE_PLAN_TYPE } from '@documenso/lib/constants/billing';
|
||||||
|
|
||||||
|
import { getPricesByPlan } from './get-prices-by-plan';
|
||||||
|
|
||||||
|
export const getPlatformPlanPrices = async () => {
|
||||||
|
return await getPricesByPlan(STRIPE_PLAN_TYPE.PLATFORM);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPlatformPlanPriceIds = async () => {
|
||||||
|
const prices = await getPlatformPlanPrices();
|
||||||
|
|
||||||
|
return prices.map((price) => price.id);
|
||||||
|
};
|
||||||
@ -9,6 +9,7 @@ export const getPrimaryAccountPlanPrices = async () => {
|
|||||||
return await getPricesByPlan([
|
return await getPricesByPlan([
|
||||||
STRIPE_PLAN_TYPE.REGULAR,
|
STRIPE_PLAN_TYPE.REGULAR,
|
||||||
STRIPE_PLAN_TYPE.COMMUNITY,
|
STRIPE_PLAN_TYPE.COMMUNITY,
|
||||||
|
STRIPE_PLAN_TYPE.PLATFORM,
|
||||||
STRIPE_PLAN_TYPE.ENTERPRISE,
|
STRIPE_PLAN_TYPE.ENTERPRISE,
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,7 +6,11 @@ import { getPricesByPlan } from './get-prices-by-plan';
|
|||||||
* Returns the Stripe prices of items that affect the amount of teams a user can create.
|
* Returns the Stripe prices of items that affect the amount of teams a user can create.
|
||||||
*/
|
*/
|
||||||
export const getTeamRelatedPrices = async () => {
|
export const getTeamRelatedPrices = async () => {
|
||||||
return await getPricesByPlan([STRIPE_PLAN_TYPE.COMMUNITY, STRIPE_PLAN_TYPE.ENTERPRISE]);
|
return await getPricesByPlan([
|
||||||
|
STRIPE_PLAN_TYPE.COMMUNITY,
|
||||||
|
STRIPE_PLAN_TYPE.PLATFORM,
|
||||||
|
STRIPE_PLAN_TYPE.ENTERPRISE,
|
||||||
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
61
packages/ee/server-only/util/is-document-platform.ts
Normal file
61
packages/ee/server-only/util/is-document-platform.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||||
|
import { subscriptionsContainsActivePlan } from '@documenso/lib/utils/billing';
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
import type { Document, Subscription } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
import { getPlatformPlanPriceIds } from '../stripe/get-platform-plan-prices';
|
||||||
|
|
||||||
|
export type IsDocumentPlatformOptions = Pick<Document, 'id' | 'userId' | 'teamId'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the user is platform, or has permission to use platform features on
|
||||||
|
* behalf of their team.
|
||||||
|
*
|
||||||
|
* It is assumed that the provided user is part of the provided team.
|
||||||
|
*/
|
||||||
|
export const isDocumentPlatform = async ({
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
}: IsDocumentPlatformOptions): Promise<boolean> => {
|
||||||
|
let subscriptions: Subscription[] = [];
|
||||||
|
|
||||||
|
if (!IS_BILLING_ENABLED()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (teamId) {
|
||||||
|
subscriptions = await prisma.team
|
||||||
|
.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: teamId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
owner: {
|
||||||
|
include: {
|
||||||
|
Subscription: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((team) => team.owner.Subscription);
|
||||||
|
} else {
|
||||||
|
subscriptions = await prisma.user
|
||||||
|
.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
Subscription: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((user) => user.Subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subscriptions.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const platformPlanPriceIds = await getPlatformPlanPriceIds();
|
||||||
|
|
||||||
|
return subscriptionsContainsActivePlan(subscriptions, platformPlanPriceIds);
|
||||||
|
};
|
||||||
@ -7,5 +7,6 @@ export enum STRIPE_PLAN_TYPE {
|
|||||||
REGULAR = 'regular',
|
REGULAR = 'regular',
|
||||||
TEAM = 'team',
|
TEAM = 'team',
|
||||||
COMMUNITY = 'community',
|
COMMUNITY = 'community',
|
||||||
|
PLATFORM = 'platform',
|
||||||
ENTERPRISE = 'enterprise',
|
ENTERPRISE = 'enterprise',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,12 +17,14 @@ const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
|
|||||||
documentVisibility: z.nativeEnum(DocumentVisibility),
|
documentVisibility: z.nativeEnum(DocumentVisibility),
|
||||||
documentLanguage: z.string(),
|
documentLanguage: z.string(),
|
||||||
includeSenderDetails: z.boolean(),
|
includeSenderDetails: z.boolean(),
|
||||||
|
includeSigningCertificate: z.boolean(),
|
||||||
brandingEnabled: z.boolean(),
|
brandingEnabled: z.boolean(),
|
||||||
brandingLogo: z.string(),
|
brandingLogo: z.string(),
|
||||||
brandingUrl: z.string(),
|
brandingUrl: z.string(),
|
||||||
brandingCompanyDetails: z.string(),
|
brandingCompanyDetails: z.string(),
|
||||||
brandingHidePoweredBy: z.boolean(),
|
brandingHidePoweredBy: z.boolean(),
|
||||||
teamId: z.number(),
|
teamId: z.number(),
|
||||||
|
typedSignatureEnabled: z.boolean(),
|
||||||
})
|
})
|
||||||
.nullish(),
|
.nullish(),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -57,7 +57,17 @@ export const SEAL_DOCUMENT_JOB_DEFINITION = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
|
documentMeta: true,
|
||||||
Recipient: true,
|
Recipient: true,
|
||||||
|
team: {
|
||||||
|
select: {
|
||||||
|
teamGlobalSettings: {
|
||||||
|
select: {
|
||||||
|
includeSigningCertificate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -117,7 +127,13 @@ export const SEAL_DOCUMENT_JOB_DEFINITION = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pdfData = await getFile(documentData);
|
const pdfData = await getFile(documentData);
|
||||||
const certificateData = await getCertificatePdf({ documentId }).catch(() => null);
|
const certificateData =
|
||||||
|
(document.team?.teamGlobalSettings?.includeSigningCertificate ?? true)
|
||||||
|
? await getCertificatePdf({
|
||||||
|
documentId,
|
||||||
|
language: document.documentMeta?.language,
|
||||||
|
}).catch(() => null)
|
||||||
|
: null;
|
||||||
|
|
||||||
const newDataId = await io.runTask('decorate-and-sign-pdf', async () => {
|
const newDataId = await io.runTask('decorate-and-sign-pdf', async () => {
|
||||||
const pdfDoc = await PDFDocument.load(pdfData);
|
const pdfDoc = await PDFDocument.load(pdfData);
|
||||||
|
|||||||
@ -51,7 +51,7 @@
|
|||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
"playwright": "1.43.0",
|
"playwright": "1.43.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"remeda": "^2.12.1",
|
"remeda": "^2.17.3",
|
||||||
"sharp": "0.32.6",
|
"sharp": "0.32.6",
|
||||||
"stripe": "^12.7.0",
|
"stripe": "^12.7.0",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
|
|||||||
@ -112,6 +112,7 @@ export const createDocument = async ({
|
|||||||
documentMeta: {
|
documentMeta: {
|
||||||
create: {
|
create: {
|
||||||
language: team?.teamGlobalSettings?.documentLanguage,
|
language: team?.teamGlobalSettings?.documentLanguage,
|
||||||
|
typedSignatureEnabled: team?.teamGlobalSettings?.typedSignatureEnabled,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/
|
|||||||
import { WebhookTriggerEvents } from '@documenso/prisma/client';
|
import { WebhookTriggerEvents } from '@documenso/prisma/client';
|
||||||
import { signPdf } from '@documenso/signing';
|
import { signPdf } from '@documenso/signing';
|
||||||
|
|
||||||
import { ZSupportedLanguageCodeSchema } from '../../constants/i18n';
|
|
||||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||||
import { getFile } from '../../universal/upload/get-file';
|
import { getFile } from '../../universal/upload/get-file';
|
||||||
import { putPdfFile } from '../../universal/upload/put-file';
|
import { putPdfFile } from '../../universal/upload/put-file';
|
||||||
@ -48,6 +47,15 @@ export const sealDocument = async ({
|
|||||||
documentData: true,
|
documentData: true,
|
||||||
documentMeta: true,
|
documentMeta: true,
|
||||||
Recipient: true,
|
Recipient: true,
|
||||||
|
team: {
|
||||||
|
select: {
|
||||||
|
teamGlobalSettings: {
|
||||||
|
select: {
|
||||||
|
includeSigningCertificate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -92,11 +100,13 @@ export const sealDocument = async ({
|
|||||||
// !: Need to write the fields onto the document as a hard copy
|
// !: Need to write the fields onto the document as a hard copy
|
||||||
const pdfData = await getFile(documentData);
|
const pdfData = await getFile(documentData);
|
||||||
|
|
||||||
const documentLanguage = ZSupportedLanguageCodeSchema.parse(document.documentMeta?.language);
|
const certificateData =
|
||||||
|
(document.team?.teamGlobalSettings?.includeSigningCertificate ?? true)
|
||||||
const certificate = await getCertificatePdf({ documentId, language: documentLanguage })
|
? await getCertificatePdf({
|
||||||
.then(async (doc) => PDFDocument.load(doc))
|
documentId,
|
||||||
.catch(() => null);
|
language: document.documentMeta?.language,
|
||||||
|
}).catch(() => null)
|
||||||
|
: null;
|
||||||
|
|
||||||
const doc = await PDFDocument.load(pdfData);
|
const doc = await PDFDocument.load(pdfData);
|
||||||
|
|
||||||
@ -105,7 +115,9 @@ export const sealDocument = async ({
|
|||||||
flattenForm(doc);
|
flattenForm(doc);
|
||||||
flattenAnnotations(doc);
|
flattenAnnotations(doc);
|
||||||
|
|
||||||
if (certificate) {
|
if (certificateData) {
|
||||||
|
const certificate = await PDFDocument.load(certificateData);
|
||||||
|
|
||||||
const certificatePages = await doc.copyPages(certificate, certificate.getPageIndices());
|
const certificatePages = await doc.copyPages(certificate, certificate.getPageIndices());
|
||||||
|
|
||||||
certificatePages.forEach((page) => {
|
certificatePages.forEach((page) => {
|
||||||
|
|||||||
@ -177,6 +177,10 @@ export const signFieldWithToken = async ({
|
|||||||
throw new Error('Signature field must have a signature');
|
throw new Error('Signature field must have a signature');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isSignatureField && !documentMeta?.typedSignatureEnabled && typedSignature) {
|
||||||
|
throw new Error('Typed signatures are not allowed. Please draw your signature');
|
||||||
|
}
|
||||||
|
|
||||||
return await prisma.$transaction(async (tx) => {
|
return await prisma.$transaction(async (tx) => {
|
||||||
const updatedField = await tx.field.update({
|
const updatedField = await tx.field.update({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@ -2,12 +2,13 @@ import { DateTime } from 'luxon';
|
|||||||
import type { Browser } from 'playwright';
|
import type { Browser } from 'playwright';
|
||||||
|
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||||
import type { SupportedLanguageCodes } from '../../constants/i18n';
|
import { type SupportedLanguageCodes, isValidLanguageCode } from '../../constants/i18n';
|
||||||
import { encryptSecondaryData } from '../crypto/encrypt';
|
import { encryptSecondaryData } from '../crypto/encrypt';
|
||||||
|
|
||||||
export type GetCertificatePdfOptions = {
|
export type GetCertificatePdfOptions = {
|
||||||
documentId: number;
|
documentId: number;
|
||||||
language?: SupportedLanguageCodes;
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
language?: SupportedLanguageCodes | (string & {});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getCertificatePdf = async ({ documentId, language }: GetCertificatePdfOptions) => {
|
export const getCertificatePdf = async ({ documentId, language }: GetCertificatePdfOptions) => {
|
||||||
@ -38,15 +39,15 @@ export const getCertificatePdf = async ({ documentId, language }: GetCertificate
|
|||||||
|
|
||||||
const page = await browserContext.newPage();
|
const page = await browserContext.newPage();
|
||||||
|
|
||||||
if (language) {
|
const lang = isValidLanguageCode(language) ? language : 'en';
|
||||||
await page.context().addCookies([
|
|
||||||
{
|
await page.context().addCookies([
|
||||||
name: 'language',
|
{
|
||||||
value: language,
|
name: 'language',
|
||||||
url: NEXT_PUBLIC_WEBAPP_URL(),
|
value: lang,
|
||||||
},
|
url: NEXT_PUBLIC_WEBAPP_URL(),
|
||||||
]);
|
},
|
||||||
}
|
]);
|
||||||
|
|
||||||
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encryptedId}`, {
|
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encryptedId}`, {
|
||||||
waitUntil: 'networkidle',
|
waitUntil: 'networkidle',
|
||||||
|
|||||||
@ -82,7 +82,10 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
|||||||
const fieldX = pageWidth * (Number(field.positionX) / 100);
|
const fieldX = pageWidth * (Number(field.positionX) / 100);
|
||||||
const fieldY = pageHeight * (Number(field.positionY) / 100);
|
const fieldY = pageHeight * (Number(field.positionY) / 100);
|
||||||
|
|
||||||
const font = await pdf.embedFont(isSignatureField ? fontCaveat : fontNoto);
|
const font = await pdf.embedFont(
|
||||||
|
isSignatureField ? fontCaveat : fontNoto,
|
||||||
|
isSignatureField ? { features: { calt: false } } : undefined,
|
||||||
|
);
|
||||||
|
|
||||||
if (field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE) {
|
if (field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE) {
|
||||||
await pdf.embedFont(fontCaveat);
|
await pdf.embedFont(fontCaveat);
|
||||||
@ -92,45 +95,89 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
|||||||
.with(
|
.with(
|
||||||
{
|
{
|
||||||
type: P.union(FieldType.SIGNATURE, FieldType.FREE_SIGNATURE),
|
type: P.union(FieldType.SIGNATURE, FieldType.FREE_SIGNATURE),
|
||||||
Signature: { signatureImageAsBase64: P.string },
|
|
||||||
},
|
},
|
||||||
async (field) => {
|
async (field) => {
|
||||||
const image = await pdf.embedPng(field.Signature?.signatureImageAsBase64 ?? '');
|
if (field.Signature?.signatureImageAsBase64) {
|
||||||
|
const image = await pdf.embedPng(field.Signature?.signatureImageAsBase64 ?? '');
|
||||||
|
|
||||||
let imageWidth = image.width;
|
let imageWidth = image.width;
|
||||||
let imageHeight = image.height;
|
let imageHeight = image.height;
|
||||||
|
|
||||||
const scalingFactor = Math.min(fieldWidth / imageWidth, fieldHeight / imageHeight, 1);
|
const scalingFactor = Math.min(fieldWidth / imageWidth, fieldHeight / imageHeight, 1);
|
||||||
|
|
||||||
imageWidth = imageWidth * scalingFactor;
|
imageWidth = imageWidth * scalingFactor;
|
||||||
imageHeight = imageHeight * scalingFactor;
|
imageHeight = imageHeight * scalingFactor;
|
||||||
|
|
||||||
let imageX = fieldX + (fieldWidth - imageWidth) / 2;
|
let imageX = fieldX + (fieldWidth - imageWidth) / 2;
|
||||||
let imageY = fieldY + (fieldHeight - imageHeight) / 2;
|
let imageY = fieldY + (fieldHeight - imageHeight) / 2;
|
||||||
|
|
||||||
// Invert the Y axis since PDFs use a bottom-left coordinate system
|
// Invert the Y axis since PDFs use a bottom-left coordinate system
|
||||||
imageY = pageHeight - imageY - imageHeight;
|
imageY = pageHeight - imageY - imageHeight;
|
||||||
|
|
||||||
if (pageRotationInDegrees !== 0) {
|
if (pageRotationInDegrees !== 0) {
|
||||||
const adjustedPosition = adjustPositionForRotation(
|
const adjustedPosition = adjustPositionForRotation(
|
||||||
pageWidth,
|
pageWidth,
|
||||||
pageHeight,
|
pageHeight,
|
||||||
imageX,
|
imageX,
|
||||||
imageY,
|
imageY,
|
||||||
pageRotationInDegrees,
|
pageRotationInDegrees,
|
||||||
);
|
);
|
||||||
|
|
||||||
imageX = adjustedPosition.xPos;
|
imageX = adjustedPosition.xPos;
|
||||||
imageY = adjustedPosition.yPos;
|
imageY = adjustedPosition.yPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.drawImage(image, {
|
||||||
|
x: imageX,
|
||||||
|
y: imageY,
|
||||||
|
width: imageWidth,
|
||||||
|
height: imageHeight,
|
||||||
|
rotate: degrees(pageRotationInDegrees),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const signatureText = field.Signature?.typedSignature ?? '';
|
||||||
|
|
||||||
|
const longestLineInTextForWidth = signatureText
|
||||||
|
.split('\n')
|
||||||
|
.sort((a, b) => b.length - a.length)[0];
|
||||||
|
|
||||||
|
let fontSize = maxFontSize;
|
||||||
|
let textWidth = font.widthOfTextAtSize(longestLineInTextForWidth, fontSize);
|
||||||
|
let textHeight = font.heightAtSize(fontSize);
|
||||||
|
|
||||||
|
const scalingFactor = Math.min(fieldWidth / textWidth, fieldHeight / textHeight, 1);
|
||||||
|
fontSize = Math.max(Math.min(fontSize * scalingFactor, maxFontSize), minFontSize);
|
||||||
|
|
||||||
|
textWidth = font.widthOfTextAtSize(longestLineInTextForWidth, fontSize);
|
||||||
|
textHeight = font.heightAtSize(fontSize);
|
||||||
|
|
||||||
|
let textX = fieldX + (fieldWidth - textWidth) / 2;
|
||||||
|
let textY = fieldY + (fieldHeight - textHeight) / 2;
|
||||||
|
|
||||||
|
// Invert the Y axis since PDFs use a bottom-left coordinate system
|
||||||
|
textY = pageHeight - textY - textHeight;
|
||||||
|
|
||||||
|
if (pageRotationInDegrees !== 0) {
|
||||||
|
const adjustedPosition = adjustPositionForRotation(
|
||||||
|
pageWidth,
|
||||||
|
pageHeight,
|
||||||
|
textX,
|
||||||
|
textY,
|
||||||
|
pageRotationInDegrees,
|
||||||
|
);
|
||||||
|
|
||||||
|
textX = adjustedPosition.xPos;
|
||||||
|
textY = adjustedPosition.yPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.drawText(signatureText, {
|
||||||
|
x: textX,
|
||||||
|
y: textY,
|
||||||
|
size: fontSize,
|
||||||
|
font,
|
||||||
|
rotate: degrees(pageRotationInDegrees),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
page.drawImage(image, {
|
|
||||||
x: imageX,
|
|
||||||
y: imageY,
|
|
||||||
width: imageWidth,
|
|
||||||
height: imageHeight,
|
|
||||||
rotate: degrees(pageRotationInDegrees),
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.with({ type: FieldType.CHECKBOX }, (field) => {
|
.with({ type: FieldType.CHECKBOX }, (field) => {
|
||||||
|
|||||||
@ -12,6 +12,8 @@ export type UpdateTeamDocumentSettingsOptions = {
|
|||||||
documentVisibility: DocumentVisibility;
|
documentVisibility: DocumentVisibility;
|
||||||
documentLanguage: SupportedLanguageCodes;
|
documentLanguage: SupportedLanguageCodes;
|
||||||
includeSenderDetails: boolean;
|
includeSenderDetails: boolean;
|
||||||
|
typedSignatureEnabled: boolean;
|
||||||
|
includeSigningCertificate: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -20,7 +22,13 @@ export const updateTeamDocumentSettings = async ({
|
|||||||
teamId,
|
teamId,
|
||||||
settings,
|
settings,
|
||||||
}: UpdateTeamDocumentSettingsOptions) => {
|
}: UpdateTeamDocumentSettingsOptions) => {
|
||||||
const { documentVisibility, documentLanguage, includeSenderDetails } = settings;
|
const {
|
||||||
|
documentVisibility,
|
||||||
|
documentLanguage,
|
||||||
|
includeSenderDetails,
|
||||||
|
includeSigningCertificate,
|
||||||
|
typedSignatureEnabled,
|
||||||
|
} = settings;
|
||||||
|
|
||||||
const member = await prisma.teamMember.findFirst({
|
const member = await prisma.teamMember.findFirst({
|
||||||
where: {
|
where: {
|
||||||
@ -42,11 +50,15 @@ export const updateTeamDocumentSettings = async ({
|
|||||||
documentVisibility,
|
documentVisibility,
|
||||||
documentLanguage,
|
documentLanguage,
|
||||||
includeSenderDetails,
|
includeSenderDetails,
|
||||||
|
typedSignatureEnabled,
|
||||||
|
includeSigningCertificate,
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
documentVisibility,
|
documentVisibility,
|
||||||
documentLanguage,
|
documentLanguage,
|
||||||
includeSenderDetails,
|
includeSenderDetails,
|
||||||
|
typedSignatureEnabled,
|
||||||
|
includeSigningCertificate,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams
|
|||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { Prisma } from '@documenso/prisma/client';
|
import { Prisma } from '@documenso/prisma/client';
|
||||||
import type { DocumentVisibility } from '@documenso/prisma/client';
|
|
||||||
|
|
||||||
export type UpdateTeamOptions = {
|
export type UpdateTeamOptions = {
|
||||||
userId: number;
|
userId: number;
|
||||||
@ -12,8 +11,6 @@ export type UpdateTeamOptions = {
|
|||||||
data: {
|
data: {
|
||||||
name?: string;
|
name?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
documentVisibility?: DocumentVisibility;
|
|
||||||
includeSenderDetails?: boolean;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -45,18 +42,6 @@ export const updateTeam = async ({ userId, teamId, data }: UpdateTeamOptions) =>
|
|||||||
data: {
|
data: {
|
||||||
url: data.url,
|
url: data.url,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
teamGlobalSettings: {
|
|
||||||
upsert: {
|
|
||||||
create: {
|
|
||||||
documentVisibility: data.documentVisibility,
|
|
||||||
includeSenderDetails: data.includeSenderDetails,
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
documentVisibility: data.documentVisibility,
|
|
||||||
includeSenderDetails: data.includeSenderDetails,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -64,6 +64,7 @@ export type CreateDocumentFromTemplateOptions = {
|
|||||||
signingOrder?: DocumentSigningOrder;
|
signingOrder?: DocumentSigningOrder;
|
||||||
language?: SupportedLanguageCodes;
|
language?: SupportedLanguageCodes;
|
||||||
distributionMethod?: DocumentDistributionMethod;
|
distributionMethod?: DocumentDistributionMethod;
|
||||||
|
typedSignatureEnabled?: boolean;
|
||||||
};
|
};
|
||||||
requestMetadata?: RequestMetadata;
|
requestMetadata?: RequestMetadata;
|
||||||
};
|
};
|
||||||
@ -146,7 +147,7 @@ export const createDocumentFromTemplate = async ({
|
|||||||
return {
|
return {
|
||||||
templateRecipientId: templateRecipient.id,
|
templateRecipientId: templateRecipient.id,
|
||||||
fields: templateRecipient.Field,
|
fields: templateRecipient.Field,
|
||||||
name: foundRecipient ? foundRecipient.name ?? '' : templateRecipient.name,
|
name: foundRecipient ? (foundRecipient.name ?? '') : templateRecipient.name,
|
||||||
email: foundRecipient ? foundRecipient.email : templateRecipient.email,
|
email: foundRecipient ? foundRecipient.email : templateRecipient.email,
|
||||||
role: templateRecipient.role,
|
role: templateRecipient.role,
|
||||||
signingOrder: foundRecipient?.signingOrder ?? templateRecipient.signingOrder,
|
signingOrder: foundRecipient?.signingOrder ?? templateRecipient.signingOrder,
|
||||||
@ -196,6 +197,8 @@ export const createDocumentFromTemplate = async ({
|
|||||||
override?.language ||
|
override?.language ||
|
||||||
template.templateMeta?.language ||
|
template.templateMeta?.language ||
|
||||||
template.team?.teamGlobalSettings?.documentLanguage,
|
template.team?.teamGlobalSettings?.documentLanguage,
|
||||||
|
typedSignatureEnabled:
|
||||||
|
override?.typedSignatureEnabled ?? template.templateMeta?.typedSignatureEnabled,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Recipient: {
|
Recipient: {
|
||||||
|
|||||||
@ -448,7 +448,7 @@ msgid "Advanced Options"
|
|||||||
msgstr "Erweiterte Optionen"
|
msgstr "Erweiterte Optionen"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:576
|
#: packages/ui/primitives/document-flow/add-fields.tsx:576
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:409
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:414
|
||||||
msgid "Advanced settings"
|
msgid "Advanced settings"
|
||||||
msgstr "Erweiterte Einstellungen"
|
msgstr "Erweiterte Einstellungen"
|
||||||
|
|
||||||
@ -504,11 +504,11 @@ msgstr "Genehmigung"
|
|||||||
msgid "Before you get started, please confirm your email address by clicking the button below:"
|
msgid "Before you get started, please confirm your email address by clicking the button below:"
|
||||||
msgstr "Bitte bestätige vor dem Start deine E-Mail-Adresse, indem du auf den Button unten klickst:"
|
msgstr "Bitte bestätige vor dem Start deine E-Mail-Adresse, indem du auf den Button unten klickst:"
|
||||||
|
|
||||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:377
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:383
|
||||||
msgid "Black"
|
msgid "Black"
|
||||||
msgstr "Schwarz"
|
msgstr "Schwarz"
|
||||||
|
|
||||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:391
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:397
|
||||||
msgid "Blue"
|
msgid "Blue"
|
||||||
msgstr "Blau"
|
msgstr "Blau"
|
||||||
|
|
||||||
@ -566,7 +566,7 @@ msgstr "Checkbox-Werte"
|
|||||||
msgid "Clear filters"
|
msgid "Clear filters"
|
||||||
msgstr "Filter löschen"
|
msgstr "Filter löschen"
|
||||||
|
|
||||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:411
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:417
|
||||||
msgid "Clear Signature"
|
msgid "Clear Signature"
|
||||||
msgstr "Unterschrift löschen"
|
msgstr "Unterschrift löschen"
|
||||||
|
|
||||||
@ -594,7 +594,7 @@ msgid "Configure Direct Recipient"
|
|||||||
msgstr "Direkten Empfänger konfigurieren"
|
msgstr "Direkten Empfänger konfigurieren"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:577
|
#: packages/ui/primitives/document-flow/add-fields.tsx:577
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:410
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:415
|
||||||
msgid "Configure the {0} field"
|
msgid "Configure the {0} field"
|
||||||
msgstr "Konfigurieren Sie das Feld {0}"
|
msgstr "Konfigurieren Sie das Feld {0}"
|
||||||
|
|
||||||
@ -657,7 +657,7 @@ msgstr "Benutzerdefinierter Text"
|
|||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:934
|
#: packages/ui/primitives/document-flow/add-fields.tsx:934
|
||||||
#: packages/ui/primitives/document-flow/types.ts:53
|
#: packages/ui/primitives/document-flow/types.ts:53
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:697
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:729
|
||||||
msgid "Date"
|
msgid "Date"
|
||||||
msgstr "Datum"
|
msgstr "Datum"
|
||||||
|
|
||||||
@ -801,7 +801,7 @@ msgid "Drag & drop your PDF here."
|
|||||||
msgstr "Ziehen Sie Ihr PDF hierher."
|
msgstr "Ziehen Sie Ihr PDF hierher."
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:1065
|
#: packages/ui/primitives/document-flow/add-fields.tsx:1065
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:827
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:860
|
||||||
msgid "Dropdown"
|
msgid "Dropdown"
|
||||||
msgstr "Dropdown"
|
msgstr "Dropdown"
|
||||||
|
|
||||||
@ -815,7 +815,7 @@ msgstr "Dropdown-Optionen"
|
|||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:512
|
#: packages/ui/primitives/document-flow/add-signers.tsx:512
|
||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:519
|
#: packages/ui/primitives/document-flow/add-signers.tsx:519
|
||||||
#: packages/ui/primitives/document-flow/types.ts:54
|
#: packages/ui/primitives/document-flow/types.ts:54
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:645
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:677
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:471
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:471
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:478
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:478
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
@ -851,6 +851,7 @@ msgid "Enable signing order"
|
|||||||
msgstr "Aktiviere die Signaturreihenfolge"
|
msgstr "Aktiviere die Signaturreihenfolge"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:802
|
#: packages/ui/primitives/document-flow/add-fields.tsx:802
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:597
|
||||||
msgid "Enable Typed Signatures"
|
msgid "Enable Typed Signatures"
|
||||||
msgstr "Aktivieren Sie getippte Unterschriften"
|
msgstr "Aktivieren Sie getippte Unterschriften"
|
||||||
|
|
||||||
@ -938,7 +939,7 @@ msgstr "Globale Empfängerauthentifizierung"
|
|||||||
msgid "Go Back"
|
msgid "Go Back"
|
||||||
msgstr "Zurück"
|
msgstr "Zurück"
|
||||||
|
|
||||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:398
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:404
|
||||||
msgid "Green"
|
msgid "Green"
|
||||||
msgstr "Grün"
|
msgstr "Grün"
|
||||||
|
|
||||||
@ -1033,7 +1034,7 @@ msgstr "Min"
|
|||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:550
|
#: packages/ui/primitives/document-flow/add-signers.tsx:550
|
||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:556
|
#: packages/ui/primitives/document-flow/add-signers.tsx:556
|
||||||
#: packages/ui/primitives/document-flow/types.ts:55
|
#: packages/ui/primitives/document-flow/types.ts:55
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:671
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:703
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:506
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:506
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:512
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:512
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
@ -1052,7 +1053,7 @@ msgid "Needs to view"
|
|||||||
msgstr "Muss sehen"
|
msgstr "Muss sehen"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:693
|
#: packages/ui/primitives/document-flow/add-fields.tsx:693
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:511
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:516
|
||||||
msgid "No recipient matching this description was found."
|
msgid "No recipient matching this description was found."
|
||||||
msgstr "Kein passender Empfänger mit dieser Beschreibung gefunden."
|
msgstr "Kein passender Empfänger mit dieser Beschreibung gefunden."
|
||||||
|
|
||||||
@ -1061,7 +1062,7 @@ msgid "No recipients"
|
|||||||
msgstr "Keine Empfänger"
|
msgstr "Keine Empfänger"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:708
|
#: packages/ui/primitives/document-flow/add-fields.tsx:708
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:526
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:531
|
||||||
msgid "No recipients with this role"
|
msgid "No recipients with this role"
|
||||||
msgstr "Keine Empfänger mit dieser Rolle"
|
msgstr "Keine Empfänger mit dieser Rolle"
|
||||||
|
|
||||||
@ -1091,7 +1092,7 @@ msgstr "Keine"
|
|||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:986
|
#: packages/ui/primitives/document-flow/add-fields.tsx:986
|
||||||
#: packages/ui/primitives/document-flow/types.ts:56
|
#: packages/ui/primitives/document-flow/types.ts:56
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:749
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:781
|
||||||
msgid "Number"
|
msgid "Number"
|
||||||
msgstr "Nummer"
|
msgstr "Nummer"
|
||||||
|
|
||||||
@ -1183,7 +1184,6 @@ msgid "Please try again or contact our support."
|
|||||||
msgstr "Bitte versuchen Sie es erneut oder kontaktieren Sie unseren Support."
|
msgstr "Bitte versuchen Sie es erneut oder kontaktieren Sie unseren Support."
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/types.ts:57
|
#: packages/ui/primitives/document-flow/types.ts:57
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:775
|
|
||||||
msgid "Radio"
|
msgid "Radio"
|
||||||
msgstr "Radio"
|
msgstr "Radio"
|
||||||
|
|
||||||
@ -1226,7 +1226,7 @@ msgstr "E-Mail des entfernten Empfängers"
|
|||||||
msgid "Recipient signing request email"
|
msgid "Recipient signing request email"
|
||||||
msgstr "E-Mail zur Unterzeichnungsanfrage des Empfängers"
|
msgstr "E-Mail zur Unterzeichnungsanfrage des Empfängers"
|
||||||
|
|
||||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:384
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:390
|
||||||
msgid "Red"
|
msgid "Red"
|
||||||
msgstr "Rot"
|
msgstr "Rot"
|
||||||
|
|
||||||
@ -1295,7 +1295,7 @@ msgstr "Zeilen pro Seite"
|
|||||||
msgid "Save"
|
msgid "Save"
|
||||||
msgstr "Speichern"
|
msgstr "Speichern"
|
||||||
|
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:861
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:893
|
||||||
msgid "Save Template"
|
msgid "Save Template"
|
||||||
msgstr "Vorlage speichern"
|
msgstr "Vorlage speichern"
|
||||||
|
|
||||||
@ -1388,7 +1388,7 @@ msgstr "Anmelden"
|
|||||||
#: packages/ui/primitives/document-flow/add-signature.tsx:323
|
#: packages/ui/primitives/document-flow/add-signature.tsx:323
|
||||||
#: packages/ui/primitives/document-flow/field-icon.tsx:52
|
#: packages/ui/primitives/document-flow/field-icon.tsx:52
|
||||||
#: packages/ui/primitives/document-flow/types.ts:49
|
#: packages/ui/primitives/document-flow/types.ts:49
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:593
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:625
|
||||||
msgid "Signature"
|
msgid "Signature"
|
||||||
msgstr "Unterschrift"
|
msgstr "Unterschrift"
|
||||||
|
|
||||||
@ -1473,7 +1473,7 @@ msgstr "Vorlagentitel"
|
|||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:960
|
#: packages/ui/primitives/document-flow/add-fields.tsx:960
|
||||||
#: packages/ui/primitives/document-flow/types.ts:52
|
#: packages/ui/primitives/document-flow/types.ts:52
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:723
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:755
|
||||||
msgid "Text"
|
msgid "Text"
|
||||||
msgstr "Text"
|
msgstr "Text"
|
||||||
|
|
||||||
@ -1637,7 +1637,7 @@ msgid "Title"
|
|||||||
msgstr "Titel"
|
msgstr "Titel"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:1080
|
#: packages/ui/primitives/document-flow/add-fields.tsx:1080
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:841
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:873
|
||||||
msgid "To proceed further, please set at least one value for the {0} field."
|
msgid "To proceed further, please set at least one value for the {0} field."
|
||||||
msgstr "Um fortzufahren, legen Sie bitte mindestens einen Wert für das Feld {0} fest."
|
msgstr "Um fortzufahren, legen Sie bitte mindestens einen Wert für das Feld {0} fest."
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -443,7 +443,7 @@ msgid "Advanced Options"
|
|||||||
msgstr "Advanced Options"
|
msgstr "Advanced Options"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:576
|
#: packages/ui/primitives/document-flow/add-fields.tsx:576
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:409
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:414
|
||||||
msgid "Advanced settings"
|
msgid "Advanced settings"
|
||||||
msgstr "Advanced settings"
|
msgstr "Advanced settings"
|
||||||
|
|
||||||
@ -499,11 +499,11 @@ msgstr "Approving"
|
|||||||
msgid "Before you get started, please confirm your email address by clicking the button below:"
|
msgid "Before you get started, please confirm your email address by clicking the button below:"
|
||||||
msgstr "Before you get started, please confirm your email address by clicking the button below:"
|
msgstr "Before you get started, please confirm your email address by clicking the button below:"
|
||||||
|
|
||||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:377
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:383
|
||||||
msgid "Black"
|
msgid "Black"
|
||||||
msgstr "Black"
|
msgstr "Black"
|
||||||
|
|
||||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:391
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:397
|
||||||
msgid "Blue"
|
msgid "Blue"
|
||||||
msgstr "Blue"
|
msgstr "Blue"
|
||||||
|
|
||||||
@ -561,7 +561,7 @@ msgstr "Checkbox values"
|
|||||||
msgid "Clear filters"
|
msgid "Clear filters"
|
||||||
msgstr "Clear filters"
|
msgstr "Clear filters"
|
||||||
|
|
||||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:411
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:417
|
||||||
msgid "Clear Signature"
|
msgid "Clear Signature"
|
||||||
msgstr "Clear Signature"
|
msgstr "Clear Signature"
|
||||||
|
|
||||||
@ -589,7 +589,7 @@ msgid "Configure Direct Recipient"
|
|||||||
msgstr "Configure Direct Recipient"
|
msgstr "Configure Direct Recipient"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:577
|
#: packages/ui/primitives/document-flow/add-fields.tsx:577
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:410
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:415
|
||||||
msgid "Configure the {0} field"
|
msgid "Configure the {0} field"
|
||||||
msgstr "Configure the {0} field"
|
msgstr "Configure the {0} field"
|
||||||
|
|
||||||
@ -652,7 +652,7 @@ msgstr "Custom Text"
|
|||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:934
|
#: packages/ui/primitives/document-flow/add-fields.tsx:934
|
||||||
#: packages/ui/primitives/document-flow/types.ts:53
|
#: packages/ui/primitives/document-flow/types.ts:53
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:697
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:729
|
||||||
msgid "Date"
|
msgid "Date"
|
||||||
msgstr "Date"
|
msgstr "Date"
|
||||||
|
|
||||||
@ -796,7 +796,7 @@ msgid "Drag & drop your PDF here."
|
|||||||
msgstr "Drag & drop your PDF here."
|
msgstr "Drag & drop your PDF here."
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:1065
|
#: packages/ui/primitives/document-flow/add-fields.tsx:1065
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:827
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:860
|
||||||
msgid "Dropdown"
|
msgid "Dropdown"
|
||||||
msgstr "Dropdown"
|
msgstr "Dropdown"
|
||||||
|
|
||||||
@ -810,7 +810,7 @@ msgstr "Dropdown options"
|
|||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:512
|
#: packages/ui/primitives/document-flow/add-signers.tsx:512
|
||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:519
|
#: packages/ui/primitives/document-flow/add-signers.tsx:519
|
||||||
#: packages/ui/primitives/document-flow/types.ts:54
|
#: packages/ui/primitives/document-flow/types.ts:54
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:645
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:677
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:471
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:471
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:478
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:478
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
@ -846,6 +846,7 @@ msgid "Enable signing order"
|
|||||||
msgstr "Enable signing order"
|
msgstr "Enable signing order"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:802
|
#: packages/ui/primitives/document-flow/add-fields.tsx:802
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:597
|
||||||
msgid "Enable Typed Signatures"
|
msgid "Enable Typed Signatures"
|
||||||
msgstr "Enable Typed Signatures"
|
msgstr "Enable Typed Signatures"
|
||||||
|
|
||||||
@ -933,7 +934,7 @@ msgstr "Global recipient action authentication"
|
|||||||
msgid "Go Back"
|
msgid "Go Back"
|
||||||
msgstr "Go Back"
|
msgstr "Go Back"
|
||||||
|
|
||||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:398
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:404
|
||||||
msgid "Green"
|
msgid "Green"
|
||||||
msgstr "Green"
|
msgstr "Green"
|
||||||
|
|
||||||
@ -1028,7 +1029,7 @@ msgstr "Min"
|
|||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:550
|
#: packages/ui/primitives/document-flow/add-signers.tsx:550
|
||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:556
|
#: packages/ui/primitives/document-flow/add-signers.tsx:556
|
||||||
#: packages/ui/primitives/document-flow/types.ts:55
|
#: packages/ui/primitives/document-flow/types.ts:55
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:671
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:703
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:506
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:506
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:512
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:512
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
@ -1047,7 +1048,7 @@ msgid "Needs to view"
|
|||||||
msgstr "Needs to view"
|
msgstr "Needs to view"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:693
|
#: packages/ui/primitives/document-flow/add-fields.tsx:693
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:511
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:516
|
||||||
msgid "No recipient matching this description was found."
|
msgid "No recipient matching this description was found."
|
||||||
msgstr "No recipient matching this description was found."
|
msgstr "No recipient matching this description was found."
|
||||||
|
|
||||||
@ -1056,7 +1057,7 @@ msgid "No recipients"
|
|||||||
msgstr "No recipients"
|
msgstr "No recipients"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:708
|
#: packages/ui/primitives/document-flow/add-fields.tsx:708
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:526
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:531
|
||||||
msgid "No recipients with this role"
|
msgid "No recipients with this role"
|
||||||
msgstr "No recipients with this role"
|
msgstr "No recipients with this role"
|
||||||
|
|
||||||
@ -1086,7 +1087,7 @@ msgstr "None"
|
|||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:986
|
#: packages/ui/primitives/document-flow/add-fields.tsx:986
|
||||||
#: packages/ui/primitives/document-flow/types.ts:56
|
#: packages/ui/primitives/document-flow/types.ts:56
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:749
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:781
|
||||||
msgid "Number"
|
msgid "Number"
|
||||||
msgstr "Number"
|
msgstr "Number"
|
||||||
|
|
||||||
@ -1178,7 +1179,6 @@ msgid "Please try again or contact our support."
|
|||||||
msgstr "Please try again or contact our support."
|
msgstr "Please try again or contact our support."
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/types.ts:57
|
#: packages/ui/primitives/document-flow/types.ts:57
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:775
|
|
||||||
msgid "Radio"
|
msgid "Radio"
|
||||||
msgstr "Radio"
|
msgstr "Radio"
|
||||||
|
|
||||||
@ -1221,7 +1221,7 @@ msgstr "Recipient removed email"
|
|||||||
msgid "Recipient signing request email"
|
msgid "Recipient signing request email"
|
||||||
msgstr "Recipient signing request email"
|
msgstr "Recipient signing request email"
|
||||||
|
|
||||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:384
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:390
|
||||||
msgid "Red"
|
msgid "Red"
|
||||||
msgstr "Red"
|
msgstr "Red"
|
||||||
|
|
||||||
@ -1290,7 +1290,7 @@ msgstr "Rows per page"
|
|||||||
msgid "Save"
|
msgid "Save"
|
||||||
msgstr "Save"
|
msgstr "Save"
|
||||||
|
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:861
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:893
|
||||||
msgid "Save Template"
|
msgid "Save Template"
|
||||||
msgstr "Save Template"
|
msgstr "Save Template"
|
||||||
|
|
||||||
@ -1383,7 +1383,7 @@ msgstr "Sign In"
|
|||||||
#: packages/ui/primitives/document-flow/add-signature.tsx:323
|
#: packages/ui/primitives/document-flow/add-signature.tsx:323
|
||||||
#: packages/ui/primitives/document-flow/field-icon.tsx:52
|
#: packages/ui/primitives/document-flow/field-icon.tsx:52
|
||||||
#: packages/ui/primitives/document-flow/types.ts:49
|
#: packages/ui/primitives/document-flow/types.ts:49
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:593
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:625
|
||||||
msgid "Signature"
|
msgid "Signature"
|
||||||
msgstr "Signature"
|
msgstr "Signature"
|
||||||
|
|
||||||
@ -1468,7 +1468,7 @@ msgstr "Template title"
|
|||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:960
|
#: packages/ui/primitives/document-flow/add-fields.tsx:960
|
||||||
#: packages/ui/primitives/document-flow/types.ts:52
|
#: packages/ui/primitives/document-flow/types.ts:52
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:723
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:755
|
||||||
msgid "Text"
|
msgid "Text"
|
||||||
msgstr "Text"
|
msgstr "Text"
|
||||||
|
|
||||||
@ -1632,7 +1632,7 @@ msgid "Title"
|
|||||||
msgstr "Title"
|
msgstr "Title"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:1080
|
#: packages/ui/primitives/document-flow/add-fields.tsx:1080
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:841
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:873
|
||||||
msgid "To proceed further, please set at least one value for the {0} field."
|
msgid "To proceed further, please set at least one value for the {0} field."
|
||||||
msgstr "To proceed further, please set at least one value for the {0} field."
|
msgstr "To proceed further, please set at least one value for the {0} field."
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -448,7 +448,7 @@ msgid "Advanced Options"
|
|||||||
msgstr "Opciones avanzadas"
|
msgstr "Opciones avanzadas"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:576
|
#: packages/ui/primitives/document-flow/add-fields.tsx:576
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:409
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:414
|
||||||
msgid "Advanced settings"
|
msgid "Advanced settings"
|
||||||
msgstr "Configuraciones avanzadas"
|
msgstr "Configuraciones avanzadas"
|
||||||
|
|
||||||
@ -504,11 +504,11 @@ msgstr "Aprobando"
|
|||||||
msgid "Before you get started, please confirm your email address by clicking the button below:"
|
msgid "Before you get started, please confirm your email address by clicking the button below:"
|
||||||
msgstr "Antes de comenzar, por favor confirma tu dirección de correo electrónico haciendo clic en el botón de abajo:"
|
msgstr "Antes de comenzar, por favor confirma tu dirección de correo electrónico haciendo clic en el botón de abajo:"
|
||||||
|
|
||||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:377
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:383
|
||||||
msgid "Black"
|
msgid "Black"
|
||||||
msgstr "Negro"
|
msgstr "Negro"
|
||||||
|
|
||||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:391
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:397
|
||||||
msgid "Blue"
|
msgid "Blue"
|
||||||
msgstr "Azul"
|
msgstr "Azul"
|
||||||
|
|
||||||
@ -566,7 +566,7 @@ msgstr "Valores de Checkbox"
|
|||||||
msgid "Clear filters"
|
msgid "Clear filters"
|
||||||
msgstr "Limpiar filtros"
|
msgstr "Limpiar filtros"
|
||||||
|
|
||||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:411
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:417
|
||||||
msgid "Clear Signature"
|
msgid "Clear Signature"
|
||||||
msgstr "Limpiar firma"
|
msgstr "Limpiar firma"
|
||||||
|
|
||||||
@ -594,7 +594,7 @@ msgid "Configure Direct Recipient"
|
|||||||
msgstr "Configurar destinatario directo"
|
msgstr "Configurar destinatario directo"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:577
|
#: packages/ui/primitives/document-flow/add-fields.tsx:577
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:410
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:415
|
||||||
msgid "Configure the {0} field"
|
msgid "Configure the {0} field"
|
||||||
msgstr "Configurar el campo {0}"
|
msgstr "Configurar el campo {0}"
|
||||||
|
|
||||||
@ -657,7 +657,7 @@ msgstr "Texto personalizado"
|
|||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:934
|
#: packages/ui/primitives/document-flow/add-fields.tsx:934
|
||||||
#: packages/ui/primitives/document-flow/types.ts:53
|
#: packages/ui/primitives/document-flow/types.ts:53
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:697
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:729
|
||||||
msgid "Date"
|
msgid "Date"
|
||||||
msgstr "Fecha"
|
msgstr "Fecha"
|
||||||
|
|
||||||
@ -801,7 +801,7 @@ msgid "Drag & drop your PDF here."
|
|||||||
msgstr "Arrastre y suelte su PDF aquí."
|
msgstr "Arrastre y suelte su PDF aquí."
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:1065
|
#: packages/ui/primitives/document-flow/add-fields.tsx:1065
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:827
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:860
|
||||||
msgid "Dropdown"
|
msgid "Dropdown"
|
||||||
msgstr "Menú desplegable"
|
msgstr "Menú desplegable"
|
||||||
|
|
||||||
@ -815,7 +815,7 @@ msgstr "Opciones de menú desplegable"
|
|||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:512
|
#: packages/ui/primitives/document-flow/add-signers.tsx:512
|
||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:519
|
#: packages/ui/primitives/document-flow/add-signers.tsx:519
|
||||||
#: packages/ui/primitives/document-flow/types.ts:54
|
#: packages/ui/primitives/document-flow/types.ts:54
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:645
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:677
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:471
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:471
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:478
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:478
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
@ -851,6 +851,7 @@ msgid "Enable signing order"
|
|||||||
msgstr "Habilitar orden de firma"
|
msgstr "Habilitar orden de firma"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:802
|
#: packages/ui/primitives/document-flow/add-fields.tsx:802
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:597
|
||||||
msgid "Enable Typed Signatures"
|
msgid "Enable Typed Signatures"
|
||||||
msgstr "Habilitar firmas escritas"
|
msgstr "Habilitar firmas escritas"
|
||||||
|
|
||||||
@ -938,7 +939,7 @@ msgstr "Autenticación de acción de destinatario global"
|
|||||||
msgid "Go Back"
|
msgid "Go Back"
|
||||||
msgstr "Regresar"
|
msgstr "Regresar"
|
||||||
|
|
||||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:398
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:404
|
||||||
msgid "Green"
|
msgid "Green"
|
||||||
msgstr "Verde"
|
msgstr "Verde"
|
||||||
|
|
||||||
@ -1033,7 +1034,7 @@ msgstr "Mín"
|
|||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:550
|
#: packages/ui/primitives/document-flow/add-signers.tsx:550
|
||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:556
|
#: packages/ui/primitives/document-flow/add-signers.tsx:556
|
||||||
#: packages/ui/primitives/document-flow/types.ts:55
|
#: packages/ui/primitives/document-flow/types.ts:55
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:671
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:703
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:506
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:506
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:512
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:512
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
@ -1052,7 +1053,7 @@ msgid "Needs to view"
|
|||||||
msgstr "Necesita ver"
|
msgstr "Necesita ver"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:693
|
#: packages/ui/primitives/document-flow/add-fields.tsx:693
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:511
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:516
|
||||||
msgid "No recipient matching this description was found."
|
msgid "No recipient matching this description was found."
|
||||||
msgstr "No se encontró ningún destinatario que coincidiera con esta descripción."
|
msgstr "No se encontró ningún destinatario que coincidiera con esta descripción."
|
||||||
|
|
||||||
@ -1061,7 +1062,7 @@ msgid "No recipients"
|
|||||||
msgstr "Sin destinatarios"
|
msgstr "Sin destinatarios"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:708
|
#: packages/ui/primitives/document-flow/add-fields.tsx:708
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:526
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:531
|
||||||
msgid "No recipients with this role"
|
msgid "No recipients with this role"
|
||||||
msgstr "No hay destinatarios con este rol"
|
msgstr "No hay destinatarios con este rol"
|
||||||
|
|
||||||
@ -1091,7 +1092,7 @@ msgstr "Ninguno"
|
|||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:986
|
#: packages/ui/primitives/document-flow/add-fields.tsx:986
|
||||||
#: packages/ui/primitives/document-flow/types.ts:56
|
#: packages/ui/primitives/document-flow/types.ts:56
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:749
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:781
|
||||||
msgid "Number"
|
msgid "Number"
|
||||||
msgstr "Número"
|
msgstr "Número"
|
||||||
|
|
||||||
@ -1183,7 +1184,6 @@ msgid "Please try again or contact our support."
|
|||||||
msgstr "Por favor, inténtalo de nuevo o contacta a nuestro soporte."
|
msgstr "Por favor, inténtalo de nuevo o contacta a nuestro soporte."
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/types.ts:57
|
#: packages/ui/primitives/document-flow/types.ts:57
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:775
|
|
||||||
msgid "Radio"
|
msgid "Radio"
|
||||||
msgstr "Radio"
|
msgstr "Radio"
|
||||||
|
|
||||||
@ -1226,7 +1226,7 @@ msgstr "Correo electrónico de destinatario eliminado"
|
|||||||
msgid "Recipient signing request email"
|
msgid "Recipient signing request email"
|
||||||
msgstr "Correo electrónico de solicitud de firma de destinatario"
|
msgstr "Correo electrónico de solicitud de firma de destinatario"
|
||||||
|
|
||||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:384
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:390
|
||||||
msgid "Red"
|
msgid "Red"
|
||||||
msgstr "Rojo"
|
msgstr "Rojo"
|
||||||
|
|
||||||
@ -1295,7 +1295,7 @@ msgstr "Filas por página"
|
|||||||
msgid "Save"
|
msgid "Save"
|
||||||
msgstr "Guardar"
|
msgstr "Guardar"
|
||||||
|
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:861
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:893
|
||||||
msgid "Save Template"
|
msgid "Save Template"
|
||||||
msgstr "Guardar plantilla"
|
msgstr "Guardar plantilla"
|
||||||
|
|
||||||
@ -1388,7 +1388,7 @@ msgstr "Iniciar sesión"
|
|||||||
#: packages/ui/primitives/document-flow/add-signature.tsx:323
|
#: packages/ui/primitives/document-flow/add-signature.tsx:323
|
||||||
#: packages/ui/primitives/document-flow/field-icon.tsx:52
|
#: packages/ui/primitives/document-flow/field-icon.tsx:52
|
||||||
#: packages/ui/primitives/document-flow/types.ts:49
|
#: packages/ui/primitives/document-flow/types.ts:49
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:593
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:625
|
||||||
msgid "Signature"
|
msgid "Signature"
|
||||||
msgstr "Firma"
|
msgstr "Firma"
|
||||||
|
|
||||||
@ -1473,7 +1473,7 @@ msgstr "Título de plantilla"
|
|||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:960
|
#: packages/ui/primitives/document-flow/add-fields.tsx:960
|
||||||
#: packages/ui/primitives/document-flow/types.ts:52
|
#: packages/ui/primitives/document-flow/types.ts:52
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:723
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:755
|
||||||
msgid "Text"
|
msgid "Text"
|
||||||
msgstr "Texto"
|
msgstr "Texto"
|
||||||
|
|
||||||
@ -1637,7 +1637,7 @@ msgid "Title"
|
|||||||
msgstr "Título"
|
msgstr "Título"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:1080
|
#: packages/ui/primitives/document-flow/add-fields.tsx:1080
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:841
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:873
|
||||||
msgid "To proceed further, please set at least one value for the {0} field."
|
msgid "To proceed further, please set at least one value for the {0} field."
|
||||||
msgstr "Para continuar, por favor establezca al menos un valor para el campo {0}."
|
msgstr "Para continuar, por favor establezca al menos un valor para el campo {0}."
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -448,7 +448,7 @@ msgid "Advanced Options"
|
|||||||
msgstr "Options avancées"
|
msgstr "Options avancées"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:576
|
#: packages/ui/primitives/document-flow/add-fields.tsx:576
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:409
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:414
|
||||||
msgid "Advanced settings"
|
msgid "Advanced settings"
|
||||||
msgstr "Paramètres avancés"
|
msgstr "Paramètres avancés"
|
||||||
|
|
||||||
@ -504,11 +504,11 @@ msgstr "En attente d'approbation"
|
|||||||
msgid "Before you get started, please confirm your email address by clicking the button below:"
|
msgid "Before you get started, please confirm your email address by clicking the button below:"
|
||||||
msgstr "Avant de commencer, veuillez confirmer votre adresse email en cliquant sur le bouton ci-dessous :"
|
msgstr "Avant de commencer, veuillez confirmer votre adresse email en cliquant sur le bouton ci-dessous :"
|
||||||
|
|
||||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:377
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:383
|
||||||
msgid "Black"
|
msgid "Black"
|
||||||
msgstr "Noir"
|
msgstr "Noir"
|
||||||
|
|
||||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:391
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:397
|
||||||
msgid "Blue"
|
msgid "Blue"
|
||||||
msgstr "Bleu"
|
msgstr "Bleu"
|
||||||
|
|
||||||
@ -566,7 +566,7 @@ msgstr "Valeurs de case à cocher"
|
|||||||
msgid "Clear filters"
|
msgid "Clear filters"
|
||||||
msgstr "Effacer les filtres"
|
msgstr "Effacer les filtres"
|
||||||
|
|
||||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:411
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:417
|
||||||
msgid "Clear Signature"
|
msgid "Clear Signature"
|
||||||
msgstr "Effacer la signature"
|
msgstr "Effacer la signature"
|
||||||
|
|
||||||
@ -594,7 +594,7 @@ msgid "Configure Direct Recipient"
|
|||||||
msgstr "Configurer le destinataire direct"
|
msgstr "Configurer le destinataire direct"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:577
|
#: packages/ui/primitives/document-flow/add-fields.tsx:577
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:410
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:415
|
||||||
msgid "Configure the {0} field"
|
msgid "Configure the {0} field"
|
||||||
msgstr "Configurer le champ {0}"
|
msgstr "Configurer le champ {0}"
|
||||||
|
|
||||||
@ -657,7 +657,7 @@ msgstr "Texte personnalisé"
|
|||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:934
|
#: packages/ui/primitives/document-flow/add-fields.tsx:934
|
||||||
#: packages/ui/primitives/document-flow/types.ts:53
|
#: packages/ui/primitives/document-flow/types.ts:53
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:697
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:729
|
||||||
msgid "Date"
|
msgid "Date"
|
||||||
msgstr "Date"
|
msgstr "Date"
|
||||||
|
|
||||||
@ -801,7 +801,7 @@ msgid "Drag & drop your PDF here."
|
|||||||
msgstr "Faites glisser et déposez votre PDF ici."
|
msgstr "Faites glisser et déposez votre PDF ici."
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:1065
|
#: packages/ui/primitives/document-flow/add-fields.tsx:1065
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:827
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:860
|
||||||
msgid "Dropdown"
|
msgid "Dropdown"
|
||||||
msgstr "Liste déroulante"
|
msgstr "Liste déroulante"
|
||||||
|
|
||||||
@ -815,7 +815,7 @@ msgstr "Options de liste déroulante"
|
|||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:512
|
#: packages/ui/primitives/document-flow/add-signers.tsx:512
|
||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:519
|
#: packages/ui/primitives/document-flow/add-signers.tsx:519
|
||||||
#: packages/ui/primitives/document-flow/types.ts:54
|
#: packages/ui/primitives/document-flow/types.ts:54
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:645
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:677
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:471
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:471
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:478
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:478
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
@ -851,6 +851,7 @@ msgid "Enable signing order"
|
|||||||
msgstr "Activer l'ordre de signature"
|
msgstr "Activer l'ordre de signature"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:802
|
#: packages/ui/primitives/document-flow/add-fields.tsx:802
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:597
|
||||||
msgid "Enable Typed Signatures"
|
msgid "Enable Typed Signatures"
|
||||||
msgstr "Activer les signatures tapées"
|
msgstr "Activer les signatures tapées"
|
||||||
|
|
||||||
@ -938,7 +939,7 @@ msgstr "Authentification d'action de destinataire globale"
|
|||||||
msgid "Go Back"
|
msgid "Go Back"
|
||||||
msgstr "Retourner"
|
msgstr "Retourner"
|
||||||
|
|
||||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:398
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:404
|
||||||
msgid "Green"
|
msgid "Green"
|
||||||
msgstr "Vert"
|
msgstr "Vert"
|
||||||
|
|
||||||
@ -1033,7 +1034,7 @@ msgstr "Min"
|
|||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:550
|
#: packages/ui/primitives/document-flow/add-signers.tsx:550
|
||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:556
|
#: packages/ui/primitives/document-flow/add-signers.tsx:556
|
||||||
#: packages/ui/primitives/document-flow/types.ts:55
|
#: packages/ui/primitives/document-flow/types.ts:55
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:671
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:703
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:506
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:506
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:512
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:512
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
@ -1052,7 +1053,7 @@ msgid "Needs to view"
|
|||||||
msgstr "Nécessite une visualisation"
|
msgstr "Nécessite une visualisation"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:693
|
#: packages/ui/primitives/document-flow/add-fields.tsx:693
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:511
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:516
|
||||||
msgid "No recipient matching this description was found."
|
msgid "No recipient matching this description was found."
|
||||||
msgstr "Aucun destinataire correspondant à cette description n'a été trouvé."
|
msgstr "Aucun destinataire correspondant à cette description n'a été trouvé."
|
||||||
|
|
||||||
@ -1061,7 +1062,7 @@ msgid "No recipients"
|
|||||||
msgstr "Aucun destinataire"
|
msgstr "Aucun destinataire"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:708
|
#: packages/ui/primitives/document-flow/add-fields.tsx:708
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:526
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:531
|
||||||
msgid "No recipients with this role"
|
msgid "No recipients with this role"
|
||||||
msgstr "Aucun destinataire avec ce rôle"
|
msgstr "Aucun destinataire avec ce rôle"
|
||||||
|
|
||||||
@ -1091,7 +1092,7 @@ msgstr "Aucun"
|
|||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:986
|
#: packages/ui/primitives/document-flow/add-fields.tsx:986
|
||||||
#: packages/ui/primitives/document-flow/types.ts:56
|
#: packages/ui/primitives/document-flow/types.ts:56
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:749
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:781
|
||||||
msgid "Number"
|
msgid "Number"
|
||||||
msgstr "Numéro"
|
msgstr "Numéro"
|
||||||
|
|
||||||
@ -1183,7 +1184,6 @@ msgid "Please try again or contact our support."
|
|||||||
msgstr "Veuillez réessayer ou contacter notre support."
|
msgstr "Veuillez réessayer ou contacter notre support."
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/types.ts:57
|
#: packages/ui/primitives/document-flow/types.ts:57
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:775
|
|
||||||
msgid "Radio"
|
msgid "Radio"
|
||||||
msgstr "Radio"
|
msgstr "Radio"
|
||||||
|
|
||||||
@ -1226,7 +1226,7 @@ msgstr "E-mail de destinataire supprimé"
|
|||||||
msgid "Recipient signing request email"
|
msgid "Recipient signing request email"
|
||||||
msgstr "E-mail de demande de signature de destinataire"
|
msgstr "E-mail de demande de signature de destinataire"
|
||||||
|
|
||||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:384
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:390
|
||||||
msgid "Red"
|
msgid "Red"
|
||||||
msgstr "Rouge"
|
msgstr "Rouge"
|
||||||
|
|
||||||
@ -1295,7 +1295,7 @@ msgstr "Lignes par page"
|
|||||||
msgid "Save"
|
msgid "Save"
|
||||||
msgstr "Sauvegarder"
|
msgstr "Sauvegarder"
|
||||||
|
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:861
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:893
|
||||||
msgid "Save Template"
|
msgid "Save Template"
|
||||||
msgstr "Sauvegarder le modèle"
|
msgstr "Sauvegarder le modèle"
|
||||||
|
|
||||||
@ -1388,7 +1388,7 @@ msgstr "Se connecter"
|
|||||||
#: packages/ui/primitives/document-flow/add-signature.tsx:323
|
#: packages/ui/primitives/document-flow/add-signature.tsx:323
|
||||||
#: packages/ui/primitives/document-flow/field-icon.tsx:52
|
#: packages/ui/primitives/document-flow/field-icon.tsx:52
|
||||||
#: packages/ui/primitives/document-flow/types.ts:49
|
#: packages/ui/primitives/document-flow/types.ts:49
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:593
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:625
|
||||||
msgid "Signature"
|
msgid "Signature"
|
||||||
msgstr "Signature"
|
msgstr "Signature"
|
||||||
|
|
||||||
@ -1473,7 +1473,7 @@ msgstr "Titre du modèle"
|
|||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:960
|
#: packages/ui/primitives/document-flow/add-fields.tsx:960
|
||||||
#: packages/ui/primitives/document-flow/types.ts:52
|
#: packages/ui/primitives/document-flow/types.ts:52
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:723
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:755
|
||||||
msgid "Text"
|
msgid "Text"
|
||||||
msgstr "Texte"
|
msgstr "Texte"
|
||||||
|
|
||||||
@ -1637,7 +1637,7 @@ msgid "Title"
|
|||||||
msgstr "Titre"
|
msgstr "Titre"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:1080
|
#: packages/ui/primitives/document-flow/add-fields.tsx:1080
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:841
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:873
|
||||||
msgid "To proceed further, please set at least one value for the {0} field."
|
msgid "To proceed further, please set at least one value for the {0} field."
|
||||||
msgstr "Pour continuer, veuillez définir au moins une valeur pour le champ {0}."
|
msgstr "Pour continuer, veuillez définir au moins une valeur pour le champ {0}."
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -24,7 +24,8 @@ module.exports = {
|
|||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
'@trivago/prettier-plugin-sort-imports',
|
'@trivago/prettier-plugin-sort-imports',
|
||||||
'prettier-plugin-sql',
|
// !: Disabled until Prettier 3.x is supported.
|
||||||
|
// 'prettier-plugin-sql',
|
||||||
'prettier-plugin-tailwindcss',
|
'prettier-plugin-tailwindcss',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@ -7,10 +7,9 @@
|
|||||||
"clean": "rimraf node_modules"
|
"clean": "rimraf node_modules"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
|
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^3.3.3",
|
||||||
"prettier-plugin-sql": "^0.14.0",
|
"prettier-plugin-tailwindcss": "^0.6.9"
|
||||||
"prettier-plugin-tailwindcss": "^0.2.8"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {}
|
"devDependencies": {}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "DocumentMeta" ALTER COLUMN "typedSignatureEnabled" SET DEFAULT true;
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "TeamGlobalSettings" ADD COLUMN "typedSignatureEnabled" BOOLEAN NOT NULL DEFAULT true;
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "TeamGlobalSettings" ADD COLUMN "includeSigningCertificate" BOOLEAN NOT NULL DEFAULT true;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
-- Existing templates should not have this enabled by default.
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "TemplateMeta" ADD COLUMN "typedSignatureEnabled" BOOLEAN NOT NULL DEFAULT false;
|
||||||
|
|
||||||
|
-- New templates should have this enabled by default.
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "TemplateMeta" ALTER COLUMN "typedSignatureEnabled" SET DEFAULT true;
|
||||||
@ -374,7 +374,7 @@ model DocumentMeta {
|
|||||||
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||||||
redirectUrl String?
|
redirectUrl String?
|
||||||
signingOrder DocumentSigningOrder @default(PARALLEL)
|
signingOrder DocumentSigningOrder @default(PARALLEL)
|
||||||
typedSignatureEnabled Boolean @default(false)
|
typedSignatureEnabled Boolean @default(true)
|
||||||
language String @default("en")
|
language String @default("en")
|
||||||
distributionMethod DocumentDistributionMethod @default(EMAIL)
|
distributionMethod DocumentDistributionMethod @default(EMAIL)
|
||||||
emailSettings Json?
|
emailSettings Json?
|
||||||
@ -511,10 +511,12 @@ enum TeamMemberInviteStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model TeamGlobalSettings {
|
model TeamGlobalSettings {
|
||||||
teamId Int @unique
|
teamId Int @unique
|
||||||
documentVisibility DocumentVisibility @default(EVERYONE)
|
documentVisibility DocumentVisibility @default(EVERYONE)
|
||||||
documentLanguage String @default("en")
|
documentLanguage String @default("en")
|
||||||
includeSenderDetails Boolean @default(true)
|
includeSenderDetails Boolean @default(true)
|
||||||
|
typedSignatureEnabled Boolean @default(true)
|
||||||
|
includeSigningCertificate Boolean @default(true)
|
||||||
|
|
||||||
brandingEnabled Boolean @default(false)
|
brandingEnabled Boolean @default(false)
|
||||||
brandingLogo String @default("")
|
brandingLogo String @default("")
|
||||||
@ -628,19 +630,21 @@ enum TemplateType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model TemplateMeta {
|
model TemplateMeta {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
subject String?
|
subject String?
|
||||||
message String?
|
message String?
|
||||||
timezone String? @default("Etc/UTC") @db.Text
|
timezone String? @default("Etc/UTC") @db.Text
|
||||||
password String?
|
password String?
|
||||||
dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text
|
dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text
|
||||||
signingOrder DocumentSigningOrder? @default(PARALLEL)
|
signingOrder DocumentSigningOrder? @default(PARALLEL)
|
||||||
templateId Int @unique
|
typedSignatureEnabled Boolean @default(true)
|
||||||
template Template @relation(fields: [templateId], references: [id], onDelete: Cascade)
|
distributionMethod DocumentDistributionMethod @default(EMAIL)
|
||||||
redirectUrl String?
|
|
||||||
language String @default("en")
|
templateId Int @unique
|
||||||
distributionMethod DocumentDistributionMethod @default(EMAIL)
|
template Template @relation(fields: [templateId], references: [id], onDelete: Cascade)
|
||||||
emailSettings Json?
|
redirectUrl String?
|
||||||
|
language String @default("en")
|
||||||
|
emailSettings Json?
|
||||||
}
|
}
|
||||||
|
|
||||||
model Template {
|
model Template {
|
||||||
|
|||||||
@ -3,7 +3,7 @@ const { fontFamily } = require('tailwindcss/defaultTheme');
|
|||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
darkMode: ['class'],
|
darkMode: ['variant', '&:is(.dark:not(.dark-mode-disabled) *)'],
|
||||||
content: ['src/**/*.{ts,tsx}'],
|
content: ['src/**/*.{ts,tsx}'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
@ -108,6 +108,9 @@ module.exports = {
|
|||||||
'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
|
DEFAULT: 'calc(var(--radius) - 3px)',
|
||||||
|
'2xl': 'calc(var(--radius) + 4px)',
|
||||||
|
xl: 'calc(var(--radius) + 2px)',
|
||||||
lg: 'var(--radius)',
|
lg: 'var(--radius)',
|
||||||
md: 'calc(var(--radius) - 2px)',
|
md: 'calc(var(--radius) - 2px)',
|
||||||
sm: 'calc(var(--radius) - 4px)',
|
sm: 'calc(var(--radius) - 4px)',
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"postcss": "^8.4.32",
|
"postcss": "^8.4.32",
|
||||||
"tailwindcss": "3.3.2",
|
"tailwindcss": "3.4.15",
|
||||||
"tailwindcss-animate": "^1.0.5"
|
"tailwindcss-animate": "^1.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {},
|
"devDependencies": {},
|
||||||
|
|||||||
@ -151,8 +151,6 @@ export const ZUpdateTeamMutationSchema = z.object({
|
|||||||
data: z.object({
|
data: z.object({
|
||||||
name: ZTeamNameSchema,
|
name: ZTeamNameSchema,
|
||||||
url: ZTeamUrlSchema,
|
url: ZTeamUrlSchema,
|
||||||
documentVisibility: z.nativeEnum(DocumentVisibility).optional(),
|
|
||||||
includeSenderDetails: z.boolean().optional(),
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -212,6 +210,8 @@ export const ZUpdateTeamDocumentSettingsMutationSchema = z.object({
|
|||||||
.default(DocumentVisibility.EVERYONE),
|
.default(DocumentVisibility.EVERYONE),
|
||||||
documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES).optional().default('en'),
|
documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES).optional().default('en'),
|
||||||
includeSenderDetails: z.boolean().optional().default(false),
|
includeSenderDetails: z.boolean().optional().default(false),
|
||||||
|
typedSignatureEnabled: z.boolean().optional().default(true),
|
||||||
|
includeSigningCertificate: z.boolean().optional().default(true),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -35,6 +35,7 @@ import {
|
|||||||
ZSetSigningOrderForTemplateMutationSchema,
|
ZSetSigningOrderForTemplateMutationSchema,
|
||||||
ZToggleTemplateDirectLinkMutationSchema,
|
ZToggleTemplateDirectLinkMutationSchema,
|
||||||
ZUpdateTemplateSettingsMutationSchema,
|
ZUpdateTemplateSettingsMutationSchema,
|
||||||
|
ZUpdateTemplateTypedSignatureSettingsMutationSchema,
|
||||||
} from './schema';
|
} from './schema';
|
||||||
|
|
||||||
export const templateRouter = router({
|
export const templateRouter = router({
|
||||||
@ -359,4 +360,48 @@ export const templateRouter = router({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
updateTemplateTypedSignatureSettings: authenticatedProcedure
|
||||||
|
.input(ZUpdateTemplateTypedSignatureSettingsMutationSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
try {
|
||||||
|
const { templateId, teamId, typedSignatureEnabled } = input;
|
||||||
|
|
||||||
|
const template = await getTemplateById({
|
||||||
|
id: templateId,
|
||||||
|
teamId,
|
||||||
|
userId: ctx.user.id,
|
||||||
|
}).catch(() => null);
|
||||||
|
|
||||||
|
if (!template) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'NOT_FOUND',
|
||||||
|
message: 'Template not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return await updateTemplateSettings({
|
||||||
|
templateId,
|
||||||
|
teamId,
|
||||||
|
userId: ctx.user.id,
|
||||||
|
data: {},
|
||||||
|
meta: {
|
||||||
|
typedSignatureEnabled,
|
||||||
|
},
|
||||||
|
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
|
if (err instanceof TRPCError) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'BAD_REQUEST',
|
||||||
|
message:
|
||||||
|
'We were unable to update the settings for this template. Please try again later.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -114,6 +114,7 @@ export const ZUpdateTemplateSettingsMutationSchema = z.object({
|
|||||||
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
||||||
}),
|
}),
|
||||||
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
|
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
|
||||||
|
typedSignatureEnabled: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
});
|
});
|
||||||
@ -138,6 +139,12 @@ export const ZMoveTemplatesToTeamSchema = z.object({
|
|||||||
teamId: z.number(),
|
teamId: z.number(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const ZUpdateTemplateTypedSignatureSettingsMutationSchema = z.object({
|
||||||
|
templateId: z.number(),
|
||||||
|
teamId: z.number().optional(),
|
||||||
|
typedSignatureEnabled: z.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
export type TCreateTemplateMutationSchema = z.infer<typeof ZCreateTemplateMutationSchema>;
|
export type TCreateTemplateMutationSchema = z.infer<typeof ZCreateTemplateMutationSchema>;
|
||||||
export type TCreateDocumentFromTemplateMutationSchema = z.infer<
|
export type TCreateDocumentFromTemplateMutationSchema = z.infer<
|
||||||
typeof ZCreateDocumentFromTemplateMutationSchema
|
typeof ZCreateDocumentFromTemplateMutationSchema
|
||||||
|
|||||||
@ -34,7 +34,7 @@ const getCardClassNames = (
|
|||||||
const baseClasses = 'field-card-container relative z-20 h-full w-full transition-all';
|
const baseClasses = 'field-card-container relative z-20 h-full w-full transition-all';
|
||||||
|
|
||||||
const insertedClasses =
|
const insertedClasses =
|
||||||
'bg-documenso/20 border-documenso ring-documenso-200 ring-offset-documenso-200 ring-2 ring-offset-2 dark:shadow-none';
|
'bg-primary/20 border-primary ring-primary/20 ring-offset-primary/20 ring-2 ring-offset-2 dark:shadow-none';
|
||||||
const nonRequiredClasses =
|
const nonRequiredClasses =
|
||||||
'border-yellow-300 shadow-none ring-2 ring-yellow-100 ring-offset-2 ring-offset-yellow-100 dark:border-2';
|
'border-yellow-300 shadow-none ring-2 ring-yellow-100 ring-offset-2 ring-offset-yellow-100 dark:border-2';
|
||||||
const validatingClasses = 'border-orange-300 ring-1 ring-orange-300';
|
const validatingClasses = 'border-orange-300 ring-1 ring-orange-300';
|
||||||
|
|||||||
@ -74,7 +74,7 @@
|
|||||||
"react-hook-form": "^7.45.4",
|
"react-hook-form": "^7.45.4",
|
||||||
"react-pdf": "7.7.3",
|
"react-pdf": "7.7.3",
|
||||||
"react-rnd": "^10.4.1",
|
"react-rnd": "^10.4.1",
|
||||||
"remeda": "^1.27.1",
|
"remeda": "^2.17.3",
|
||||||
"tailwind-merge": "^1.12.0",
|
"tailwind-merge": "^1.12.0",
|
||||||
"tailwindcss-animate": "^1.0.5",
|
"tailwindcss-animate": "^1.0.5",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
|
|||||||
@ -36,11 +36,11 @@ const Card = React.forwardRef<HTMLDivElement, CardProps>(
|
|||||||
className={cn(
|
className={cn(
|
||||||
'bg-background text-foreground group relative rounded-lg border-2 backdrop-blur-[2px]',
|
'bg-background text-foreground group relative rounded-lg border-2 backdrop-blur-[2px]',
|
||||||
{
|
{
|
||||||
'gradient-border-mask before:pointer-events-none before:absolute before:-inset-[2px] before:rounded-lg before:p-[2px] before:[background:linear-gradient(var(--card-gradient-degrees),theme(colors.documenso.DEFAULT/50%)_5%,theme(colors.border/80%)_30%)]':
|
'gradient-border-mask before:pointer-events-none before:absolute before:-inset-[2px] before:rounded-lg before:p-[2px] before:[background:linear-gradient(var(--card-gradient-degrees),theme(colors.primary.DEFAULT/50%)_5%,theme(colors.border/80%)_30%)]':
|
||||||
gradient,
|
gradient,
|
||||||
'dark:gradient-border-mask before:pointer-events-none before:absolute before:-inset-[2px] before:rounded-lg before:p-[2px] before:[background:linear-gradient(var(--card-gradient-degrees),theme(colors.documenso.DEFAULT/70%)_5%,theme(colors.border/80%)_30%)]':
|
'dark:gradient-border-mask before:pointer-events-none before:absolute before:-inset-[2px] before:rounded-lg before:p-[2px] before:[background:linear-gradient(var(--card-gradient-degrees),theme(colors.primary.DEFAULT/70%)_5%,theme(colors.border/80%)_30%)]':
|
||||||
gradient,
|
gradient,
|
||||||
'shadow-[0_0_0_4px_theme(colors.gray.100/70%),0_0_0_1px_theme(colors.gray.100/70%),0_0_0_0.5px_theme(colors.primary.DEFAULT/70%)]':
|
'shadow-[0_0_0_4px_theme(colors.gray.100/70%),0_0_0_1px_theme(colors.gray.100/70%),0_0_0_0.5px_var(colors.primary.DEFAULT/70%)]':
|
||||||
true,
|
true,
|
||||||
'dark:shadow-[0]': true,
|
'dark:shadow-[0]': true,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -141,7 +141,7 @@ export const RadioFieldAdvancedSettings = ({
|
|||||||
{values.map((value) => (
|
{values.map((value) => (
|
||||||
<div key={value.id} className="mt-2 flex items-center gap-4">
|
<div key={value.id} className="mt-2 flex items-center gap-4">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
className="data-[state=checked]:bg-documenso border-foreground/30 data-[state=checked]:ring-documenso dark:data-[state=checked]:ring-offset-background h-5 w-5 rounded-full data-[state=checked]:ring-1 data-[state=checked]:ring-offset-2 data-[state=checked]:ring-offset-white"
|
className="data-[state=checked]:bg-documenso border-foreground/30 data-[state=checked]:ring-primary dark:data-[state=checked]:ring-offset-background h-5 w-5 rounded-full data-[state=checked]:ring-1 data-[state=checked]:ring-offset-2 data-[state=checked]:ring-offset-white"
|
||||||
checked={value.checked}
|
checked={value.checked}
|
||||||
onCheckedChange={(checked) => handleCheckedChange(Boolean(checked), value.id)}
|
onCheckedChange={(checked) => handleCheckedChange(Boolean(checked), value.id)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -38,6 +38,7 @@ export type SignaturePadProps = Omit<HTMLAttributes<HTMLCanvasElement>, 'onChang
|
|||||||
containerClassName?: string;
|
containerClassName?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
allowTypedSignature?: boolean;
|
allowTypedSignature?: boolean;
|
||||||
|
defaultValue?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SignaturePad = ({
|
export const SignaturePad = ({
|
||||||
@ -56,7 +57,7 @@ export const SignaturePad = ({
|
|||||||
const [lines, setLines] = useState<Point[][]>([]);
|
const [lines, setLines] = useState<Point[][]>([]);
|
||||||
const [currentLine, setCurrentLine] = useState<Point[]>([]);
|
const [currentLine, setCurrentLine] = useState<Point[]>([]);
|
||||||
const [selectedColor, setSelectedColor] = useState('black');
|
const [selectedColor, setSelectedColor] = useState('black');
|
||||||
const [typedSignature, setTypedSignature] = useState('');
|
const [typedSignature, setTypedSignature] = useState(defaultValue ?? '');
|
||||||
|
|
||||||
const perfectFreehandOptions = useMemo(() => {
|
const perfectFreehandOptions = useMemo(() => {
|
||||||
const size = $el.current ? Math.min($el.current.height, $el.current.width) * 0.03 : 10;
|
const size = $el.current ? Math.min($el.current.height, $el.current.width) * 0.03 : 10;
|
||||||
@ -206,6 +207,7 @@ export const SignaturePad = ({
|
|||||||
if (ctx) {
|
if (ctx) {
|
||||||
const canvasWidth = $el.current.width;
|
const canvasWidth = $el.current.width;
|
||||||
const canvasHeight = $el.current.height;
|
const canvasHeight = $el.current.height;
|
||||||
|
const fontFamily = String(fontCaveat.style.fontFamily);
|
||||||
|
|
||||||
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
@ -217,7 +219,7 @@ export const SignaturePad = ({
|
|||||||
|
|
||||||
// Start with a base font size
|
// Start with a base font size
|
||||||
let fontSize = 18;
|
let fontSize = 18;
|
||||||
ctx.font = `${fontSize}px ${fontCaveat.style.fontFamily}`;
|
ctx.font = `${fontSize}px ${fontFamily}`;
|
||||||
|
|
||||||
// Measure 10 characters and calculate scale factor
|
// Measure 10 characters and calculate scale factor
|
||||||
const characterWidth = ctx.measureText('m'.repeat(10)).width;
|
const characterWidth = ctx.measureText('m'.repeat(10)).width;
|
||||||
@ -227,7 +229,7 @@ export const SignaturePad = ({
|
|||||||
fontSize = fontSize * scaleFactor;
|
fontSize = fontSize * scaleFactor;
|
||||||
|
|
||||||
// Adjust font size if it exceeds canvas width
|
// Adjust font size if it exceeds canvas width
|
||||||
ctx.font = `${fontSize}px ${fontCaveat.style.fontFamily}`;
|
ctx.font = `${fontSize}px ${fontFamily}`;
|
||||||
|
|
||||||
const textWidth = ctx.measureText(typedSignature).width;
|
const textWidth = ctx.measureText(typedSignature).width;
|
||||||
|
|
||||||
@ -236,7 +238,7 @@ export const SignaturePad = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set final font and render text
|
// Set final font and render text
|
||||||
ctx.font = `${fontSize}px ${fontCaveat.style.fontFamily}`;
|
ctx.font = `${fontSize}px ${fontFamily}`;
|
||||||
ctx.fillText(typedSignature, canvasWidth / 2, canvasHeight / 2);
|
ctx.fillText(typedSignature, canvasWidth / 2, canvasHeight / 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -247,7 +249,7 @@ export const SignaturePad = ({
|
|||||||
setTypedSignature(newValue);
|
setTypedSignature(newValue);
|
||||||
|
|
||||||
if (newValue.trim() !== '') {
|
if (newValue.trim() !== '') {
|
||||||
onChange?.($el.current?.toDataURL() || null);
|
onChange?.(newValue);
|
||||||
} else {
|
} else {
|
||||||
onChange?.(null);
|
onChange?.(null);
|
||||||
}
|
}
|
||||||
@ -256,7 +258,7 @@ export const SignaturePad = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typedSignature.trim() !== '') {
|
if (typedSignature.trim() !== '') {
|
||||||
renderTypedSignature();
|
renderTypedSignature();
|
||||||
onChange?.($el.current?.toDataURL() || null);
|
onChange?.(typedSignature);
|
||||||
} else {
|
} else {
|
||||||
onClearClick();
|
onClearClick();
|
||||||
}
|
}
|
||||||
@ -303,6 +305,10 @@ export const SignaturePad = ({
|
|||||||
$el.current.width = $el.current.clientWidth * DPI;
|
$el.current.width = $el.current.clientWidth * DPI;
|
||||||
$el.current.height = $el.current.clientHeight * DPI;
|
$el.current.height = $el.current.clientHeight * DPI;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (defaultValue && typedSignature) {
|
||||||
|
renderTypedSignature();
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
unsafe_useEffectOnce(() => {
|
unsafe_useEffectOnce(() => {
|
||||||
|
|||||||
@ -55,8 +55,10 @@ import { FRIENDLY_FIELD_TYPE } from '@documenso/ui/primitives/document-flow/type
|
|||||||
import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover';
|
import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover';
|
||||||
|
|
||||||
import { getSignerColorStyles, useSignerColors } from '../../lib/signer-colors';
|
import { getSignerColorStyles, useSignerColors } from '../../lib/signer-colors';
|
||||||
|
import { Checkbox } from '../checkbox';
|
||||||
import type { FieldFormType } from '../document-flow/add-fields';
|
import type { FieldFormType } from '../document-flow/add-fields';
|
||||||
import { FieldAdvancedSettings } from '../document-flow/field-item-advanced-settings';
|
import { FieldAdvancedSettings } from '../document-flow/field-item-advanced-settings';
|
||||||
|
import { Form, FormControl, FormField, FormItem, FormLabel } from '../form/form';
|
||||||
import { useStep } from '../stepper';
|
import { useStep } from '../stepper';
|
||||||
import type { TAddTemplateFieldsFormSchema } from './add-template-fields.types';
|
import type { TAddTemplateFieldsFormSchema } from './add-template-fields.types';
|
||||||
|
|
||||||
@ -80,6 +82,7 @@ export type AddTemplateFieldsFormProps = {
|
|||||||
fields: Field[];
|
fields: Field[];
|
||||||
onSubmit: (_data: TAddTemplateFieldsFormSchema) => void;
|
onSubmit: (_data: TAddTemplateFieldsFormSchema) => void;
|
||||||
teamId?: number;
|
teamId?: number;
|
||||||
|
typedSignatureEnabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AddTemplateFieldsFormPartial = ({
|
export const AddTemplateFieldsFormPartial = ({
|
||||||
@ -89,6 +92,7 @@ export const AddTemplateFieldsFormPartial = ({
|
|||||||
fields,
|
fields,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
teamId,
|
teamId,
|
||||||
|
typedSignatureEnabled,
|
||||||
}: AddTemplateFieldsFormProps) => {
|
}: AddTemplateFieldsFormProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
|
|
||||||
@ -97,13 +101,7 @@ export const AddTemplateFieldsFormPartial = ({
|
|||||||
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
||||||
const [currentField, setCurrentField] = useState<FieldFormType>();
|
const [currentField, setCurrentField] = useState<FieldFormType>();
|
||||||
|
|
||||||
const {
|
const form = useForm<TAddTemplateFieldsFormSchema>({
|
||||||
control,
|
|
||||||
handleSubmit,
|
|
||||||
formState: { isSubmitting },
|
|
||||||
setValue,
|
|
||||||
getValues,
|
|
||||||
} = useForm<TAddTemplateFieldsFormSchema>({
|
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
fields: fields.map((field) => ({
|
fields: fields.map((field) => ({
|
||||||
nativeId: field.id,
|
nativeId: field.id,
|
||||||
@ -121,13 +119,14 @@ export const AddTemplateFieldsFormPartial = ({
|
|||||||
recipients.find((recipient) => recipient.id === field.recipientId)?.token ?? '',
|
recipients.find((recipient) => recipient.id === field.recipientId)?.token ?? '',
|
||||||
fieldMeta: field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : undefined,
|
fieldMeta: field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : undefined,
|
||||||
})),
|
})),
|
||||||
|
typedSignatureEnabled: typedSignatureEnabled ?? false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const onFormSubmit = handleSubmit(onSubmit);
|
const onFormSubmit = form.handleSubmit(onSubmit);
|
||||||
|
|
||||||
const handleSavedFieldSettings = (fieldState: FieldMeta) => {
|
const handleSavedFieldSettings = (fieldState: FieldMeta) => {
|
||||||
const initialValues = getValues();
|
const initialValues = form.getValues();
|
||||||
|
|
||||||
const updatedFields = initialValues.fields.map((field) => {
|
const updatedFields = initialValues.fields.map((field) => {
|
||||||
if (field.formId === currentField?.formId) {
|
if (field.formId === currentField?.formId) {
|
||||||
@ -142,7 +141,7 @@ export const AddTemplateFieldsFormPartial = ({
|
|||||||
return field;
|
return field;
|
||||||
});
|
});
|
||||||
|
|
||||||
setValue('fields', updatedFields);
|
form.setValue('fields', updatedFields);
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -151,7 +150,7 @@ export const AddTemplateFieldsFormPartial = ({
|
|||||||
update,
|
update,
|
||||||
fields: localFields,
|
fields: localFields,
|
||||||
} = useFieldArray({
|
} = useFieldArray({
|
||||||
control,
|
control: form.control,
|
||||||
name: 'fields',
|
name: 'fields',
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -402,6 +401,12 @@ export const AddTemplateFieldsFormPartial = ({
|
|||||||
setShowAdvancedSettings((prev) => !prev);
|
setShowAdvancedSettings((prev) => !prev);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isTypedSignatureEnabled = form.watch('typedSignatureEnabled');
|
||||||
|
|
||||||
|
const handleTypedSignatureChange = (value: boolean) => {
|
||||||
|
form.setValue('typedSignatureEnabled', value, { shouldDirty: true });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{showAdvancedSettings && currentField ? (
|
{showAdvancedSettings && currentField ? (
|
||||||
@ -568,305 +573,334 @@ export const AddTemplateFieldsFormPartial = ({
|
|||||||
</Popover>
|
</Popover>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="-mx-2 flex-1 overflow-y-auto px-2">
|
<Form {...form}>
|
||||||
<fieldset className="my-2 grid grid-cols-3 gap-4">
|
<FormField
|
||||||
<button
|
control={form.control}
|
||||||
type="button"
|
name="typedSignatureEnabled"
|
||||||
className="group h-full w-full"
|
render={({ field: { value, ...field } }) => (
|
||||||
onClick={() => setSelectedField(FieldType.SIGNATURE)}
|
<FormItem className="mb-6 flex flex-row items-center space-x-2 space-y-0">
|
||||||
onMouseDown={() => setSelectedField(FieldType.SIGNATURE)}
|
<FormControl>
|
||||||
data-selected={selectedField === FieldType.SIGNATURE ? true : undefined}
|
<Checkbox
|
||||||
>
|
{...field}
|
||||||
<Card
|
id="typedSignatureEnabled"
|
||||||
className={cn(
|
checkClassName="text-white"
|
||||||
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
|
checked={value}
|
||||||
// selectedSignerStyles.borderClass,
|
onCheckedChange={(checked) => field.onChange(checked)}
|
||||||
)}
|
disabled={form.formState.isSubmitting}
|
||||||
>
|
/>
|
||||||
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
|
</FormControl>
|
||||||
<p
|
|
||||||
className={cn(
|
|
||||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-lg font-normal',
|
|
||||||
fontCaveat.className,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Trans>Signature</Trans>
|
|
||||||
</p>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
<FormLabel
|
||||||
type="button"
|
htmlFor="typedSignatureEnabled"
|
||||||
className="group h-full w-full"
|
className="text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
onClick={() => setSelectedField(FieldType.INITIALS)}
|
>
|
||||||
onMouseDown={() => setSelectedField(FieldType.INITIALS)}
|
<Trans>Enable Typed Signatures</Trans>
|
||||||
data-selected={selectedField === FieldType.INITIALS ? true : undefined}
|
</FormLabel>
|
||||||
>
|
</FormItem>
|
||||||
<Card
|
)}
|
||||||
className={cn(
|
/>
|
||||||
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
|
|
||||||
// selectedSignerStyles.borderClass,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
|
|
||||||
<p
|
|
||||||
className={cn(
|
|
||||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Contact className="h-4 w-4" />
|
|
||||||
Initials
|
|
||||||
</p>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
<div className="-mx-2 flex-1 overflow-y-auto px-2">
|
||||||
type="button"
|
<fieldset className="my-2 grid grid-cols-3 gap-4">
|
||||||
className="group h-full w-full"
|
<button
|
||||||
onClick={() => setSelectedField(FieldType.EMAIL)}
|
type="button"
|
||||||
onMouseDown={() => setSelectedField(FieldType.EMAIL)}
|
className="group h-full w-full"
|
||||||
data-selected={selectedField === FieldType.EMAIL ? true : undefined}
|
onClick={() => setSelectedField(FieldType.SIGNATURE)}
|
||||||
>
|
onMouseDown={() => setSelectedField(FieldType.SIGNATURE)}
|
||||||
<Card
|
data-selected={selectedField === FieldType.SIGNATURE ? true : undefined}
|
||||||
className={cn(
|
|
||||||
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
|
|
||||||
// selectedSignerStyles.borderClass,
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<CardContent className="p-4">
|
<Card
|
||||||
<p
|
className={cn(
|
||||||
className={cn(
|
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
|
||||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
// selectedSignerStyles.borderClass,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Mail className="h-4 w-4" />
|
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
|
||||||
<Trans>Email</Trans>
|
<p
|
||||||
</p>
|
className={cn(
|
||||||
</CardContent>
|
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-lg font-normal',
|
||||||
</Card>
|
fontCaveat.className,
|
||||||
</button>
|
)}
|
||||||
|
>
|
||||||
|
<Trans>Signature</Trans>
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="group h-full w-full"
|
className="group h-full w-full"
|
||||||
onClick={() => setSelectedField(FieldType.NAME)}
|
onClick={() => setSelectedField(FieldType.INITIALS)}
|
||||||
onMouseDown={() => setSelectedField(FieldType.NAME)}
|
onMouseDown={() => setSelectedField(FieldType.INITIALS)}
|
||||||
data-selected={selectedField === FieldType.NAME ? true : undefined}
|
data-selected={selectedField === FieldType.INITIALS ? true : undefined}
|
||||||
>
|
|
||||||
<Card
|
|
||||||
className={cn(
|
|
||||||
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
|
|
||||||
// selectedSignerStyles.borderClass,
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<CardContent className="p-4">
|
<Card
|
||||||
<p
|
className={cn(
|
||||||
className={cn(
|
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
|
||||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
// selectedSignerStyles.borderClass,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<User className="h-4 w-4" />
|
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
|
||||||
<Trans>Name</Trans>
|
<p
|
||||||
</p>
|
className={cn(
|
||||||
</CardContent>
|
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||||
</Card>
|
)}
|
||||||
</button>
|
>
|
||||||
|
<Contact className="h-4 w-4" />
|
||||||
|
Initials
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="group h-full w-full"
|
className="group h-full w-full"
|
||||||
onClick={() => setSelectedField(FieldType.DATE)}
|
onClick={() => setSelectedField(FieldType.EMAIL)}
|
||||||
onMouseDown={() => setSelectedField(FieldType.DATE)}
|
onMouseDown={() => setSelectedField(FieldType.EMAIL)}
|
||||||
data-selected={selectedField === FieldType.DATE ? true : undefined}
|
data-selected={selectedField === FieldType.EMAIL ? true : undefined}
|
||||||
>
|
|
||||||
<Card
|
|
||||||
className={cn(
|
|
||||||
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
|
|
||||||
// selectedSignerStyles.borderClass,
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<CardContent className="p-4">
|
<Card
|
||||||
<p
|
className={cn(
|
||||||
className={cn(
|
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
|
||||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
// selectedSignerStyles.borderClass,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CalendarDays className="h-4 w-4" />
|
<CardContent className="p-4">
|
||||||
<Trans>Date</Trans>
|
<p
|
||||||
</p>
|
className={cn(
|
||||||
</CardContent>
|
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||||
</Card>
|
)}
|
||||||
</button>
|
>
|
||||||
|
<Mail className="h-4 w-4" />
|
||||||
|
<Trans>Email</Trans>
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="group h-full w-full"
|
className="group h-full w-full"
|
||||||
onClick={() => setSelectedField(FieldType.TEXT)}
|
onClick={() => setSelectedField(FieldType.NAME)}
|
||||||
onMouseDown={() => setSelectedField(FieldType.TEXT)}
|
onMouseDown={() => setSelectedField(FieldType.NAME)}
|
||||||
data-selected={selectedField === FieldType.TEXT ? true : undefined}
|
data-selected={selectedField === FieldType.NAME ? true : undefined}
|
||||||
>
|
|
||||||
<Card
|
|
||||||
className={cn(
|
|
||||||
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
|
|
||||||
// selectedSignerStyles.borderClass,
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<CardContent className="p-4">
|
<Card
|
||||||
<p
|
className={cn(
|
||||||
className={cn(
|
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
|
||||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
// selectedSignerStyles.borderClass,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Type className="h-4 w-4" />
|
<CardContent className="p-4">
|
||||||
<Trans>Text</Trans>
|
<p
|
||||||
</p>
|
className={cn(
|
||||||
</CardContent>
|
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||||
</Card>
|
)}
|
||||||
</button>
|
>
|
||||||
|
<User className="h-4 w-4" />
|
||||||
|
<Trans>Name</Trans>
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="group h-full w-full"
|
className="group h-full w-full"
|
||||||
onClick={() => setSelectedField(FieldType.NUMBER)}
|
onClick={() => setSelectedField(FieldType.DATE)}
|
||||||
onMouseDown={() => setSelectedField(FieldType.NUMBER)}
|
onMouseDown={() => setSelectedField(FieldType.DATE)}
|
||||||
data-selected={selectedField === FieldType.NUMBER ? true : undefined}
|
data-selected={selectedField === FieldType.DATE ? true : undefined}
|
||||||
>
|
|
||||||
<Card
|
|
||||||
className={cn(
|
|
||||||
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
|
|
||||||
// selectedSignerStyles.borderClass,
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<CardContent className="p-4">
|
<Card
|
||||||
<p
|
className={cn(
|
||||||
className={cn(
|
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
|
||||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
// selectedSignerStyles.borderClass,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Hash className="h-4 w-4" />
|
<CardContent className="p-4">
|
||||||
<Trans>Number</Trans>
|
<p
|
||||||
</p>
|
className={cn(
|
||||||
</CardContent>
|
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||||
</Card>
|
)}
|
||||||
</button>
|
>
|
||||||
|
<CalendarDays className="h-4 w-4" />
|
||||||
|
<Trans>Date</Trans>
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="group h-full w-full"
|
className="group h-full w-full"
|
||||||
onClick={() => setSelectedField(FieldType.RADIO)}
|
onClick={() => setSelectedField(FieldType.TEXT)}
|
||||||
onMouseDown={() => setSelectedField(FieldType.RADIO)}
|
onMouseDown={() => setSelectedField(FieldType.TEXT)}
|
||||||
data-selected={selectedField === FieldType.RADIO ? true : undefined}
|
data-selected={selectedField === FieldType.TEXT ? true : undefined}
|
||||||
>
|
|
||||||
<Card
|
|
||||||
className={cn(
|
|
||||||
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
|
|
||||||
// selectedSignerStyles.borderClass,
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<CardContent className="p-4">
|
<Card
|
||||||
<p
|
className={cn(
|
||||||
className={cn(
|
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
|
||||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
// selectedSignerStyles.borderClass,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Disc className="h-4 w-4" />
|
<CardContent className="p-4">
|
||||||
<Trans>Radio</Trans>
|
<p
|
||||||
</p>
|
className={cn(
|
||||||
</CardContent>
|
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||||
</Card>
|
)}
|
||||||
</button>
|
>
|
||||||
|
<Type className="h-4 w-4" />
|
||||||
|
<Trans>Text</Trans>
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="group h-full w-full"
|
className="group h-full w-full"
|
||||||
onClick={() => setSelectedField(FieldType.CHECKBOX)}
|
onClick={() => setSelectedField(FieldType.NUMBER)}
|
||||||
onMouseDown={() => setSelectedField(FieldType.CHECKBOX)}
|
onMouseDown={() => setSelectedField(FieldType.NUMBER)}
|
||||||
data-selected={selectedField === FieldType.CHECKBOX ? true : undefined}
|
data-selected={selectedField === FieldType.NUMBER ? true : undefined}
|
||||||
>
|
|
||||||
<Card
|
|
||||||
className={cn(
|
|
||||||
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
|
|
||||||
// selectedSignerStyles.borderClass,
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<CardContent className="p-4">
|
<Card
|
||||||
<p
|
className={cn(
|
||||||
className={cn(
|
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
|
||||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
// selectedSignerStyles.borderClass,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CheckSquare className="h-4 w-4" />
|
<CardContent className="p-4">
|
||||||
Checkbox
|
<p
|
||||||
</p>
|
className={cn(
|
||||||
</CardContent>
|
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||||
</Card>
|
)}
|
||||||
</button>
|
>
|
||||||
|
<Hash className="h-4 w-4" />
|
||||||
|
<Trans>Number</Trans>
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="group h-full w-full"
|
className="group h-full w-full"
|
||||||
onClick={() => setSelectedField(FieldType.DROPDOWN)}
|
onClick={() => setSelectedField(FieldType.RADIO)}
|
||||||
onMouseDown={() => setSelectedField(FieldType.DROPDOWN)}
|
onMouseDown={() => setSelectedField(FieldType.RADIO)}
|
||||||
data-selected={selectedField === FieldType.DROPDOWN ? true : undefined}
|
data-selected={selectedField === FieldType.RADIO ? true : undefined}
|
||||||
>
|
|
||||||
<Card
|
|
||||||
className={cn(
|
|
||||||
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
|
|
||||||
// selectedSignerStyles.borderClass,
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<CardContent className="p-4">
|
<Card
|
||||||
<p
|
className={cn(
|
||||||
className={cn(
|
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
|
||||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
// selectedSignerStyles.borderClass,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ChevronDown className="h-4 w-4" />
|
<CardContent className="p-4">
|
||||||
<Trans>Dropdown</Trans>
|
<p
|
||||||
</p>
|
className={cn(
|
||||||
</CardContent>
|
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||||
</Card>
|
)}
|
||||||
</button>
|
>
|
||||||
</fieldset>
|
<Disc className="h-4 w-4" />
|
||||||
</div>
|
Radio
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="group h-full w-full"
|
||||||
|
onClick={() => setSelectedField(FieldType.CHECKBOX)}
|
||||||
|
onMouseDown={() => setSelectedField(FieldType.CHECKBOX)}
|
||||||
|
data-selected={selectedField === FieldType.CHECKBOX ? true : undefined}
|
||||||
|
>
|
||||||
|
<Card
|
||||||
|
className={cn(
|
||||||
|
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
|
||||||
|
// selectedSignerStyles.borderClass,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<CardContent className="p-4">
|
||||||
|
<p
|
||||||
|
className={cn(
|
||||||
|
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<CheckSquare className="h-4 w-4" />
|
||||||
|
{/* Not translated on purpose. */}
|
||||||
|
Checkbox
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="group h-full w-full"
|
||||||
|
onClick={() => setSelectedField(FieldType.DROPDOWN)}
|
||||||
|
onMouseDown={() => setSelectedField(FieldType.DROPDOWN)}
|
||||||
|
data-selected={selectedField === FieldType.DROPDOWN ? true : undefined}
|
||||||
|
>
|
||||||
|
<Card
|
||||||
|
className={cn(
|
||||||
|
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
|
||||||
|
// selectedSignerStyles.borderClass,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<CardContent className="p-4">
|
||||||
|
<p
|
||||||
|
className={cn(
|
||||||
|
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ChevronDown className="h-4 w-4" />
|
||||||
|
<Trans>Dropdown</Trans>
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</button>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
{hasErrors && (
|
||||||
|
<div className="mt-4">
|
||||||
|
<ul>
|
||||||
|
<li className="text-sm text-red-500">
|
||||||
|
<Trans>
|
||||||
|
To proceed further, please set at least one value for the{' '}
|
||||||
|
{emptyCheckboxFields.length > 0
|
||||||
|
? 'Checkbox'
|
||||||
|
: emptyRadioFields.length > 0
|
||||||
|
? 'Radio'
|
||||||
|
: 'Select'}{' '}
|
||||||
|
field.
|
||||||
|
</Trans>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<DocumentFlowFormContainerFooter>
|
||||||
|
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||||
|
|
||||||
|
<DocumentFlowFormContainerActions
|
||||||
|
loading={form.formState.isSubmitting}
|
||||||
|
disabled={form.formState.isSubmitting}
|
||||||
|
goNextLabel={msg`Save Template`}
|
||||||
|
disableNextStep={hasErrors}
|
||||||
|
onGoBackClick={() => {
|
||||||
|
previousStep();
|
||||||
|
remove();
|
||||||
|
}}
|
||||||
|
onGoNextClick={() => void onFormSubmit()}
|
||||||
|
/>
|
||||||
|
</DocumentFlowFormContainerFooter>
|
||||||
</div>
|
</div>
|
||||||
</DocumentFlowFormContainerContent>
|
</DocumentFlowFormContainerContent>
|
||||||
|
|
||||||
{hasErrors && (
|
|
||||||
<div className="mt-4">
|
|
||||||
<ul>
|
|
||||||
<li className="text-sm text-red-500">
|
|
||||||
<Trans>
|
|
||||||
To proceed further, please set at least one value for the{' '}
|
|
||||||
{emptyCheckboxFields.length > 0
|
|
||||||
? 'Checkbox'
|
|
||||||
: emptyRadioFields.length > 0
|
|
||||||
? 'Radio'
|
|
||||||
: 'Select'}{' '}
|
|
||||||
field.
|
|
||||||
</Trans>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<DocumentFlowFormContainerFooter>
|
|
||||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
|
||||||
|
|
||||||
<DocumentFlowFormContainerActions
|
|
||||||
loading={isSubmitting}
|
|
||||||
disabled={isSubmitting}
|
|
||||||
goNextLabel={msg`Save Template`}
|
|
||||||
disableNextStep={hasErrors}
|
|
||||||
onGoBackClick={() => {
|
|
||||||
previousStep();
|
|
||||||
remove();
|
|
||||||
}}
|
|
||||||
onGoNextClick={() => void onFormSubmit()}
|
|
||||||
/>
|
|
||||||
</DocumentFlowFormContainerFooter>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -20,6 +20,7 @@ export const ZAddTemplateFieldsFormSchema = z.object({
|
|||||||
fieldMeta: ZFieldMetaSchema,
|
fieldMeta: ZFieldMetaSchema,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
typedSignatureEnabled: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TAddTemplateFieldsFormSchema = z.infer<typeof ZAddTemplateFieldsFormSchema>;
|
export type TAddTemplateFieldsFormSchema = z.infer<typeof ZAddTemplateFieldsFormSchema>;
|
||||||
|
|||||||
@ -138,7 +138,7 @@
|
|||||||
--new-surface-white: 0, 0%, 91%;
|
--new-surface-white: 0, 0%, 91%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark:not(.dark-mode-disabled) {
|
||||||
--background: 0 0% 14.9%;
|
--background: 0 0% 14.9%;
|
||||||
--foreground: 0 0% 97%;
|
--foreground: 0 0% 97%;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user