Compare commits
37 Commits
v1.8.1-rc.
...
feat/delet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5103477e7b | ||
|
|
b19b57dbc9 | ||
|
|
2b3ab9a3b7 | ||
|
|
cac262fcea | ||
|
|
e6d4005cd1 | ||
|
|
337bdb3553 | ||
|
|
ab654a63d8 | ||
|
|
dcb7c2436f | ||
|
|
fa33f83696 | ||
|
|
b15e1d6c47 | ||
|
|
0cd7c25718 | ||
|
|
79eec5f451 | ||
|
|
7ca0975650 | ||
|
|
870b3fb3d7 | ||
|
|
43ea76fae3 | ||
|
|
cd5adce7df | ||
|
|
11e483f1c4 | ||
|
|
2dd122aed3 | ||
|
|
d3872e86f1 | ||
|
|
171398ae2d | ||
|
|
492350612e | ||
|
|
f9935adb57 | ||
|
|
d2b99303f9 | ||
|
|
d33bbe71e7 | ||
|
|
6bf62e0ecb | ||
|
|
16527f01e7 | ||
|
|
7f25508c3c | ||
|
|
754e9e6428 | ||
|
|
2837b178fb | ||
|
|
26ccdc1b23 | ||
|
|
ea63b45a13 | ||
|
|
feef4b1a12 | ||
|
|
1a55f4253b | ||
|
|
8311e0cc29 | ||
|
|
a9adc36732 | ||
|
|
73e375938c | ||
|
|
c6393b7a9e |
@@ -27,9 +27,6 @@
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"autoprefixer": "^10.0.1",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,59 @@ Check out what's new in the latest version and read our thoughts on it. For more
|
||||
|
||||
---
|
||||
|
||||
# Documenso v1.8.0: Team Preferences, Signature Rejection, and Document Distribution
|
||||
|
||||
We're excited to announce the release of Documenso v1.8.0! This update brings powerful new features to enhance your document signing process. Here's what's new:
|
||||
|
||||
## 🌟 Key New Features
|
||||
|
||||
### 1. Team Preferences
|
||||
|
||||
Introducing **Team Preferences**, allowing administrators to configure settings and preferences that apply to documents across the entire team. This feature ensures consistency and simplifies management by letting you set default options, permissions, and preferences that automatically apply to all team members.
|
||||
|
||||

|
||||
|
||||
### 2. Signature Rejection
|
||||
|
||||
Recipients now have the option to **reject signatures**. This feature enhances communication by allowing recipients to decline signing, providing feedback or requesting changes before the document is finalized.
|
||||
|
||||
<video
|
||||
src="/changelog/v1_8_0/reject-document.mp4"
|
||||
className="aspect-video w-full"
|
||||
autoPlay
|
||||
loop
|
||||
controls
|
||||
/>
|
||||
|
||||
### 3. Document Distribution Settings
|
||||
|
||||
With the new **Document Distribution Settings**, you have greater control over how your documents are shared. Distribute communications via our automated emails and templates or take full control using our API and your own notifications infrastructure.
|
||||
|
||||
## 🔧 Other Improvements
|
||||
|
||||
- **Support for Gmail SMTP Service**: Adds support for using Gmail as your SMTP service provider.
|
||||
- **Certificate and Email Translations**: Added support for multiple languages in document certificates and emails, enhancing the experience for international users.
|
||||
- **Field Movement Fixes**: Resolved issues related to moving fields within documents, improving the document preparation experience.
|
||||
- **Docker Environment Update**: Improved Docker setup for smoother deployments and better environment consistency.
|
||||
- **Billing Access Improvements**: Users now have uninterrupted access to billing information, simplifying account management.
|
||||
- **Support Time Windows for 2FA Tokens**: Enhanced two-factor authentication by supporting time windows in 2FA tokens, improving flexibility.
|
||||
|
||||
## 💡 Recent Features
|
||||
|
||||
Don't forget to take advantage of these powerful features from our recent releases:
|
||||
|
||||
- **Signing Order**: Define the sequence in which recipients sign your documents for a structured signing process.
|
||||
- **Document Visibility Controls**: Manage who can view your documents and at what stages, offering greater privacy and control.
|
||||
- **Embedded Signing Experience**: Integrate the signing process directly into your own applications for a seamless user experience.
|
||||
|
||||
**👏 Thank You**
|
||||
|
||||
As always, we're grateful for the community's contributions and feedback. Your support helps us improve Documenso and deliver a top-notch open-source document signing solution.
|
||||
|
||||
We hope you enjoy the new features in Documenso v1.8.0. Happy signing!
|
||||
|
||||
---
|
||||
|
||||
# Documenso v1.7.1: Signing order and document visibility
|
||||
|
||||
We're excited to introduce Documenso v1.7.1, bringing you improved control over your document signing process. Here are the key updates:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@documenso/marketing",
|
||||
"version": "1.8.1-rc.0",
|
||||
"version": "1.8.1-rc.1",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
|
||||
4
apps/marketing/process-env.d.ts
vendored
4
apps/marketing/process-env.d.ts
vendored
@@ -2,8 +2,8 @@ declare namespace NodeJS {
|
||||
export interface ProcessEnv {
|
||||
NEXT_PUBLIC_WEBAPP_URL?: string;
|
||||
NEXT_PUBLIC_MARKETING_URL?: string;
|
||||
NEXT_PRIVATE_INTERNAL_WEBAPP_URL?:string;
|
||||
|
||||
NEXT_PRIVATE_INTERNAL_WEBAPP_URL?: string;
|
||||
|
||||
NEXT_PRIVATE_DATABASE_URL: string;
|
||||
|
||||
NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID: string;
|
||||
|
||||
BIN
apps/marketing/public/changelog/v1_8_0/reject-document.mp4
Normal file
BIN
apps/marketing/public/changelog/v1_8_0/reject-document.mp4
Normal file
Binary file not shown.
BIN
apps/marketing/public/changelog/v1_8_0/team-global-settings.jpeg
Normal file
BIN
apps/marketing/public/changelog/v1_8_0/team-global-settings.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 169 KiB |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@documenso/web",
|
||||
"version": "1.8.1-rc.0",
|
||||
"version": "1.8.1-rc.1",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
@@ -28,6 +28,7 @@
|
||||
"@simplewebauthn/browser": "^9.0.1",
|
||||
"@simplewebauthn/server": "^9.0.3",
|
||||
"@tanstack/react-query": "^4.29.5",
|
||||
"colord": "^2.9.3",
|
||||
"cookie-es": "^1.0.0",
|
||||
"formidable": "^2.1.1",
|
||||
"framer-motion": "^10.12.8",
|
||||
@@ -53,7 +54,7 @@
|
||||
"react-icons": "^4.11.0",
|
||||
"react-rnd": "^10.4.1",
|
||||
"recharts": "^2.7.2",
|
||||
"remeda": "^2.12.1",
|
||||
"remeda": "^2.17.3",
|
||||
"sharp": "0.32.6",
|
||||
"ts-pattern": "^5.0.5",
|
||||
"ua-parser-js": "^1.0.37",
|
||||
|
||||
2
apps/web/process-env.d.ts
vendored
2
apps/web/process-env.d.ts
vendored
@@ -2,7 +2,7 @@ declare namespace NodeJS {
|
||||
export interface ProcessEnv {
|
||||
NEXT_PUBLIC_WEBAPP_URL?: string;
|
||||
NEXT_PUBLIC_MARKETING_URL?: string;
|
||||
NEXT_PRIVATE_INTERNAL_WEBAPP_URL?:string;
|
||||
NEXT_PRIVATE_INTERNAL_WEBAPP_URL?: string;
|
||||
|
||||
NEXT_PRIVATE_DATABASE_URL: string;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ export const DocumentPageViewInformation = ({
|
||||
const { _, i18n } = useLingui();
|
||||
|
||||
const documentInformation = useMemo(() => {
|
||||
return [
|
||||
const info = [
|
||||
{
|
||||
description: msg`Uploaded by`,
|
||||
value: userId === document.userId ? _(msg`You`) : document.User.name ?? document.User.email,
|
||||
@@ -44,8 +44,20 @@ export const DocumentPageViewInformation = ({
|
||||
.toRelative(),
|
||||
},
|
||||
];
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isMounted, document, userId]);
|
||||
|
||||
if (document.deletedAt) {
|
||||
info.push({
|
||||
description: msg`Deleted`,
|
||||
value:
|
||||
document.deletedAt &&
|
||||
DateTime.fromJSDate(document.deletedAt)
|
||||
.setLocale(i18n.locales?.[0] || i18n.locale)
|
||||
.toFormat('MMMM d, yyyy'),
|
||||
});
|
||||
}
|
||||
|
||||
return info;
|
||||
}, [isMounted, document, i18n.locales?.[0] || i18n.locale, userId]);
|
||||
|
||||
return (
|
||||
<section className="dark:bg-background text-foreground border-border bg-widget flex flex-col rounded-xl border">
|
||||
|
||||
@@ -146,7 +146,10 @@ export const DocumentPageView = async ({ params, team }: DocumentPageViewProps)
|
||||
|
||||
<div className="flex flex-row justify-between truncate">
|
||||
<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}
|
||||
</h1>
|
||||
|
||||
@@ -218,7 +221,7 @@ export const DocumentPageView = async ({ params, team }: DocumentPageViewProps)
|
||||
<DocumentPageViewDropdown document={documentWithRecipients} team={team} />
|
||||
</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)
|
||||
.with(DocumentStatus.COMPLETED, () => (
|
||||
<Trans>This document has been signed by all recipients</Trans>
|
||||
|
||||
@@ -109,7 +109,10 @@ export const DocumentEditPageView = async ({ params, team }: DocumentEditPageVie
|
||||
<Trans>Documents</Trans>
|
||||
</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}
|
||||
</h1>
|
||||
|
||||
|
||||
@@ -121,7 +121,10 @@ export const DocumentLogsPageView = async ({ params, team }: DocumentLogsPageVie
|
||||
|
||||
<div className="flex flex-col justify-between truncate sm:flex-row">
|
||||
<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}
|
||||
</h1>
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import Link from 'next/link';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import {
|
||||
ArchiveRestore,
|
||||
CheckCircle,
|
||||
Copy,
|
||||
Download,
|
||||
@@ -23,8 +24,8 @@ import { useSession } from 'next-auth/react';
|
||||
|
||||
import { downloadPDF } from '@documenso/lib/client-only/download-pdf';
|
||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
||||
import { DocumentStatus, RecipientRole } from '@documenso/prisma/client';
|
||||
import type { Document, Recipient, Team, User } from '@documenso/prisma/client';
|
||||
import { DocumentStatus, RecipientRole } from '@documenso/prisma/client';
|
||||
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
|
||||
import { trpc as trpcClient } from '@documenso/trpc/client';
|
||||
import { DocumentShareButton } from '@documenso/ui/components/document/document-share-button';
|
||||
@@ -43,6 +44,7 @@ import { ResendDocumentActionItem } from './_action-items/resend-document';
|
||||
import { DeleteDocumentDialog } from './delete-document-dialog';
|
||||
import { DuplicateDocumentDialog } from './duplicate-document-dialog';
|
||||
import { MoveDocumentDialog } from './move-document-dialog';
|
||||
import { RestoreDocumentDialog } from './restore-document-dialog';
|
||||
|
||||
export type DataTableActionDropdownProps = {
|
||||
row: Document & {
|
||||
@@ -61,6 +63,7 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
|
||||
const [isDeleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const [isDuplicateDialogOpen, setDuplicateDialogOpen] = useState(false);
|
||||
const [isMoveDialogOpen, setMoveDialogOpen] = useState(false);
|
||||
const [isRestoreDialogOpen, setRestoreDialogOpen] = useState(false);
|
||||
|
||||
if (!session) {
|
||||
return null;
|
||||
@@ -76,6 +79,7 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
|
||||
// const isSigned = recipient?.signingStatus === SigningStatus.SIGNED;
|
||||
const isCurrentTeamDocument = team && row.team?.url === team.url;
|
||||
const canManageDocument = Boolean(isOwner || isCurrentTeamDocument);
|
||||
const isDeletedDocument = row.deletedAt !== null;
|
||||
|
||||
const documentsPath = formatDocumentsPath(team?.url);
|
||||
|
||||
@@ -181,13 +185,23 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
|
||||
Void
|
||||
</DropdownMenuItem> */}
|
||||
|
||||
<DropdownMenuItem
|
||||
onClick={() => setDeleteDialogOpen(true)}
|
||||
disabled={Boolean(!canManageDocument && team?.teamEmail)}
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
{canManageDocument ? _(msg`Delete`) : _(msg`Hide`)}
|
||||
</DropdownMenuItem>
|
||||
{isDeletedDocument ? (
|
||||
<DropdownMenuItem
|
||||
onClick={() => setRestoreDialogOpen(true)}
|
||||
disabled={Boolean(!canManageDocument)}
|
||||
>
|
||||
<ArchiveRestore className="mr-2 h-4 w-4" />
|
||||
Restore
|
||||
</DropdownMenuItem>
|
||||
) : (
|
||||
<DropdownMenuItem
|
||||
onClick={() => setDeleteDialogOpen(true)}
|
||||
disabled={Boolean(!canManageDocument && team?.teamEmail)}
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
{canManageDocument ? 'Delete' : 'Hide'}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
<DropdownMenuLabel>
|
||||
<Trans>Share</Trans>
|
||||
@@ -239,6 +253,16 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
|
||||
onOpenChange={setMoveDialogOpen}
|
||||
/>
|
||||
|
||||
<RestoreDocumentDialog
|
||||
id={row.id}
|
||||
status={row.status}
|
||||
documentTitle={row.title}
|
||||
open={isRestoreDialogOpen}
|
||||
onOpenChange={setRestoreDialogOpen}
|
||||
teamId={team?.id}
|
||||
canManageDocument={canManageDocument}
|
||||
/>
|
||||
|
||||
{isDuplicateDialogOpen && (
|
||||
<DuplicateDocumentDialog
|
||||
id={row.id}
|
||||
|
||||
@@ -47,6 +47,8 @@ export const DeleteDocumentDialog = ({
|
||||
const { refreshLimits } = useLimits();
|
||||
const { _ } = useLingui();
|
||||
|
||||
const deleteMessage = msg`delete`;
|
||||
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [isDeleteEnabled, setIsDeleteEnabled] = useState(status === DocumentStatus.DRAFT);
|
||||
|
||||
@@ -87,7 +89,7 @@ export const DeleteDocumentDialog = ({
|
||||
|
||||
const onInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setInputValue(event.target.value);
|
||||
setIsDeleteEnabled(event.target.value === _(msg`delete`));
|
||||
setIsDeleteEnabled(event.target.value === _(deleteMessage));
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -181,7 +183,7 @@ export const DeleteDocumentDialog = ({
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={onInputChange}
|
||||
placeholder={_(msg`Type 'delete' to confirm`)}
|
||||
placeholder={_(msg`Please type ${`'${_(deleteMessage)}'`} to confirm`)}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ import { Trans } from '@lingui/macro';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
||||
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
|
||||
import type { PeriodSelectorValue } from '@documenso/lib/server-only/document/find-documents';
|
||||
import type { GetStatsInput } from '@documenso/lib/server-only/document/get-stats';
|
||||
import { getStats } from '@documenso/lib/server-only/document/get-stats';
|
||||
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
|
||||
import type { GetStatsInput } from '@documenso/lib/server-only/document/get-stats-new';
|
||||
import { getStats } from '@documenso/lib/server-only/document/get-stats-new';
|
||||
import { parseToIntegerArray } from '@documenso/lib/utils/params';
|
||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
||||
import type { Team, TeamEmail, TeamMemberRole } from '@documenso/prisma/client';
|
||||
@@ -35,7 +35,7 @@ export interface DocumentsPageViewProps {
|
||||
senderIds?: string;
|
||||
search?: string;
|
||||
};
|
||||
team?: Team & { teamEmail?: TeamEmail | null } & { currentTeamMember?: { role: TeamMemberRole } };
|
||||
team?: Team & { teamEmail: TeamEmail | null } & { currentTeamMember?: { role: TeamMemberRole } };
|
||||
}
|
||||
|
||||
export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPageViewProps) => {
|
||||
@@ -50,25 +50,14 @@ export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPa
|
||||
const currentTeam = team
|
||||
? { id: team.id, url: team.url, teamEmail: team.teamEmail?.email }
|
||||
: undefined;
|
||||
const currentTeamMemberRole = team?.currentTeamMember?.role;
|
||||
|
||||
const getStatOptions: GetStatsInput = {
|
||||
user,
|
||||
period,
|
||||
team,
|
||||
search,
|
||||
};
|
||||
|
||||
if (team) {
|
||||
getStatOptions.team = {
|
||||
teamId: team.id,
|
||||
teamEmail: team.teamEmail?.email,
|
||||
senderIds,
|
||||
currentTeamMemberRole,
|
||||
currentUserEmail: user.email,
|
||||
userId: user.id,
|
||||
};
|
||||
}
|
||||
|
||||
const stats = await getStats(getStatOptions);
|
||||
|
||||
const results = await findDocuments({
|
||||
@@ -128,6 +117,7 @@ export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPa
|
||||
ExtendedDocumentStatus.PENDING,
|
||||
ExtendedDocumentStatus.COMPLETED,
|
||||
ExtendedDocumentStatus.DRAFT,
|
||||
ExtendedDocumentStatus.BIN,
|
||||
ExtendedDocumentStatus.ALL,
|
||||
].map((value) => (
|
||||
<TabsTrigger
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Bird, CheckCircle2 } from 'lucide-react';
|
||||
import { Bird, CheckCircle2, Trash } from 'lucide-react';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||
@@ -30,6 +30,11 @@ export const EmptyDocumentState = ({ status }: EmptyDocumentProps) => {
|
||||
message: msg`You have not yet created or received any documents. To create a document please upload one.`,
|
||||
icon: Bird,
|
||||
}))
|
||||
.with(ExtendedDocumentStatus.BIN, () => ({
|
||||
title: msg`No documents in the bin`,
|
||||
message: msg`There are no documents in the bin.`,
|
||||
icon: Trash,
|
||||
}))
|
||||
.otherwise(() => ({
|
||||
title: msg`Nothing to do`,
|
||||
message: msg`All documents have been processed. Any new documents that are sent or received will show here.`,
|
||||
@@ -42,7 +47,6 @@ export const EmptyDocumentState = ({ status }: EmptyDocumentProps) => {
|
||||
data-testid="empty-document-state"
|
||||
>
|
||||
<Icon className="h-12 w-12" strokeWidth={1.5} />
|
||||
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold">{_(title)}</h3>
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import type { DocumentStatus } from '@documenso/prisma/client';
|
||||
import { trpc as trpcReact } from '@documenso/trpc/react';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@documenso/ui/primitives/alert-dialog';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
type RestoreDocumentDialogProps = {
|
||||
id: number;
|
||||
open: boolean;
|
||||
onOpenChange: (_open: boolean) => void;
|
||||
status: DocumentStatus;
|
||||
documentTitle: string;
|
||||
teamId?: number;
|
||||
canManageDocument: boolean;
|
||||
};
|
||||
|
||||
export function RestoreDocumentDialog({
|
||||
id,
|
||||
teamId,
|
||||
open,
|
||||
onOpenChange,
|
||||
documentTitle,
|
||||
canManageDocument,
|
||||
}: RestoreDocumentDialogProps) {
|
||||
const router = useRouter();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { mutateAsync: restoreDocument, isLoading } =
|
||||
trpcReact.document.restoreDocument.useMutation({
|
||||
onSuccess: () => {
|
||||
router.refresh();
|
||||
|
||||
toast({
|
||||
title: 'Document restored',
|
||||
description: `"${documentTitle}" has been successfully restored`,
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
onOpenChange(false);
|
||||
},
|
||||
});
|
||||
|
||||
const onRestore = async () => {
|
||||
try {
|
||||
await restoreDocument({ id, teamId });
|
||||
} catch {
|
||||
toast({
|
||||
title: 'Something went wrong',
|
||||
description: 'This document could not be restored at this time. Please try again.',
|
||||
variant: 'destructive',
|
||||
duration: 7500,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AlertDialog open={open} onOpenChange={(value) => !isLoading && onOpenChange(value)}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
You are about to restore the document <strong>"{documentTitle}"</strong>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
loading={isLoading}
|
||||
onClick={onRestore}
|
||||
disabled={!canManageDocument}
|
||||
>
|
||||
Restore
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
}
|
||||
@@ -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) => {
|
||||
try {
|
||||
await updateTemplateSettings({
|
||||
@@ -211,6 +228,12 @@ export const EditTemplateForm = ({
|
||||
fields: data.fields,
|
||||
});
|
||||
|
||||
await updateTypedSignature({
|
||||
templateId: template.id,
|
||||
teamId: team?.id,
|
||||
typedSignatureEnabled: data.typedSignatureEnabled,
|
||||
});
|
||||
|
||||
// Clear all field data from localStorage
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
@@ -225,14 +248,13 @@ export const EditTemplateForm = ({
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
// Router refresh is here to clear the router cache for when navigating to /documents.
|
||||
router.refresh();
|
||||
|
||||
router.push(templateRootPath);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while adding signers.`),
|
||||
description: _(msg`An error occurred while adding fields.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@@ -301,6 +323,7 @@ export const EditTemplateForm = ({
|
||||
fields={fields}
|
||||
onSubmit={onAddFieldsFormSubmit}
|
||||
teamId={team?.id}
|
||||
typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled}
|
||||
/>
|
||||
</Stepper>
|
||||
</DocumentFlowFormContainer>
|
||||
|
||||
@@ -63,7 +63,10 @@ export const TemplateEditPageView = async ({ params, team }: TemplateEditPageVie
|
||||
<Trans>Template</Trans>
|
||||
</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}
|
||||
</h1>
|
||||
|
||||
|
||||
@@ -73,7 +73,6 @@ export const TemplatePageView = async ({ params, team }: TemplatePageViewProps)
|
||||
|
||||
const mockedDocumentMeta = templateMeta
|
||||
? {
|
||||
typedSignatureEnabled: false,
|
||||
...templateMeta,
|
||||
signingOrder: templateMeta.signingOrder || DocumentSigningOrder.SEQUENTIAL,
|
||||
documentId: 0,
|
||||
@@ -89,7 +88,10 @@ export const TemplatePageView = async ({ params, team }: TemplatePageViewProps)
|
||||
|
||||
<div className="flex flex-row justify-between truncate">
|
||||
<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}
|
||||
</h1>
|
||||
|
||||
@@ -155,7 +157,7 @@ export const TemplatePageView = async ({ params, team }: TemplatePageViewProps)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-muted-foreground mt-2 px-4 text-sm ">
|
||||
<p className="text-muted-foreground mt-2 px-4 text-sm">
|
||||
<Trans>Manage and view template</Trans>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -209,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)`,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={`${signature.Signature?.signatureImageAsBase64}`}
|
||||
alt="Signature"
|
||||
className="max-h-12 max-w-full"
|
||||
/>
|
||||
{signature.Signature?.signatureImageAsBase64 && (
|
||||
<img
|
||||
src={`${signature.Signature?.signatureImageAsBase64}`}
|
||||
alt="Signature"
|
||||
className="max-h-12 max-w-full"
|
||||
/>
|
||||
)}
|
||||
|
||||
{signature.Signature?.typedSignature && (
|
||||
<p className="font-signature text-center text-sm">
|
||||
{signature.Signature?.typedSignature}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<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 { SigningProvider } from '~/app/(signing)/sign/[token]/provider';
|
||||
import { truncateTitle } from '~/helpers/truncate-title';
|
||||
|
||||
import { DirectTemplatePageView } from './direct-template';
|
||||
import { DirectTemplateAuthPageView } from './signing-auth-page';
|
||||
@@ -72,8 +71,11 @@ export default async function TemplatesDirectPage({ params }: TemplatesDirectPag
|
||||
user={user}
|
||||
>
|
||||
<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}>
|
||||
{truncateTitle(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}
|
||||
</h1>
|
||||
|
||||
<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(),
|
||||
recipientId: 1,
|
||||
fieldId: 1,
|
||||
signatureImageAsBase64: value.value,
|
||||
typedSignature: null,
|
||||
};
|
||||
signatureImageAsBase64: value.value.startsWith('data:') ? value.value : null,
|
||||
typedSignature: value.value.startsWith('data:') ? null : value.value,
|
||||
} satisfies Signature;
|
||||
}
|
||||
|
||||
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 { Badge } from '@documenso/ui/primitives/badge';
|
||||
|
||||
import { truncateTitle } from '~/helpers/truncate-title';
|
||||
|
||||
import { SigningAuthPageView } from '../signing-auth-page';
|
||||
import { ClaimAccount } from './claim-account';
|
||||
import { DocumentPreviewButton } from './document-preview-button';
|
||||
@@ -61,8 +59,6 @@ export default async function CompletedSigningPage({
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const truncatedTitle = truncateTitle(document.title);
|
||||
|
||||
const { documentData } = document;
|
||||
|
||||
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">
|
||||
{truncatedTitle}
|
||||
<span className="block max-w-[10rem] truncate font-medium hover:underline md:max-w-[20rem]">
|
||||
{document.title}
|
||||
</span>
|
||||
</Badge>
|
||||
|
||||
{/* Card with recipient */}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext, useState } from 'react';
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
|
||||
export type SigningContextValue = {
|
||||
fullName: string;
|
||||
@@ -44,6 +44,12 @@ export const SigningProvider = ({
|
||||
const [email, setEmail] = useState(initialEmail || '');
|
||||
const [signature, setSignature] = useState(initialSignature || null);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialSignature) {
|
||||
setSignature(initialSignature);
|
||||
}
|
||||
}, [initialSignature]);
|
||||
|
||||
return (
|
||||
<SigningContext.Provider
|
||||
value={{
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
|
||||
import { SigningDisclosure } from '~/components/general/signing-disclosure';
|
||||
import { truncateTitle } from '~/helpers/truncate-title';
|
||||
|
||||
export type SignDialogProps = {
|
||||
isSubmitting: boolean;
|
||||
@@ -36,7 +35,7 @@ export const SignDialog = ({
|
||||
disabled = false,
|
||||
}: SignDialogProps) => {
|
||||
const [showDialog, setShowDialog] = useState(false);
|
||||
const truncatedTitle = truncateTitle(documentTitle);
|
||||
|
||||
const isComplete = fields.every((field) => field.inserted);
|
||||
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
@@ -75,7 +74,13 @@ export const SignDialog = ({
|
||||
{role === RecipientRole.VIEWER && (
|
||||
<span>
|
||||
<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?
|
||||
</Trans>
|
||||
</span>
|
||||
@@ -83,7 +88,13 @@ export const SignDialog = ({
|
||||
{role === RecipientRole.SIGNER && (
|
||||
<span>
|
||||
<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?
|
||||
</Trans>
|
||||
</span>
|
||||
@@ -91,7 +102,13 @@ export const SignDialog = ({
|
||||
{role === RecipientRole.APPROVER && (
|
||||
<span>
|
||||
<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?
|
||||
</Trans>
|
||||
</span>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useMemo, useState, useTransition } from 'react';
|
||||
import { useLayoutEffect, useMemo, useRef, useState, useTransition } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
@@ -51,6 +51,10 @@ export const SignatureField = ({
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const signatureRef = useRef<HTMLParagraphElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [fontSize, setFontSize] = useState(2);
|
||||
|
||||
const { signature: providedSignature, setSignature: setProvidedSignature } =
|
||||
useRequiredSigningContext();
|
||||
|
||||
@@ -108,6 +112,7 @@ export const SignatureField = ({
|
||||
actionTarget: field.type,
|
||||
});
|
||||
};
|
||||
|
||||
const onSign = async (authOptions?: TRecipientActionAuth, signature?: string) => {
|
||||
try {
|
||||
const value = signature || providedSignature;
|
||||
@@ -117,11 +122,23 @@ export const SignatureField = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const isTypedSignature = !value.startsWith('data:image');
|
||||
|
||||
if (isTypedSignature && !typedSignatureEnabled) {
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`Typed signatures are not allowed. Please draw your signature.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const payload: TSignFieldWithTokenMutationSchema = {
|
||||
token: recipient.token,
|
||||
fieldId: field.id,
|
||||
value,
|
||||
isBase64: true,
|
||||
isBase64: !isTypedSignature,
|
||||
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 (
|
||||
<SigningFieldContainer
|
||||
field={field}
|
||||
@@ -205,10 +257,15 @@ export const SignatureField = ({
|
||||
)}
|
||||
|
||||
{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">
|
||||
{/* This optional chaining is intentional, we don't want to move the check into the condition above */}
|
||||
{signature?.typedSignature}
|
||||
</p>
|
||||
<div ref={containerRef} className="flex h-full w-full items-center justify-center p-2">
|
||||
<p
|
||||
ref={signatureRef}
|
||||
className="font-signature text-muted-foreground dark:text-background w-full overflow-hidden break-all text-center leading-tight duration-200"
|
||||
style={{ fontSize: `${fontSize}rem` }}
|
||||
>
|
||||
{signature?.typedSignature}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Dialog open={showSignatureModal} onOpenChange={setShowSignatureModal}>
|
||||
|
||||
@@ -55,7 +55,10 @@ export const SigningPageView = ({
|
||||
|
||||
return (
|
||||
<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}
|
||||
</h1>
|
||||
|
||||
|
||||
@@ -52,13 +52,7 @@ export default async function TeamsSettingsPage({ params }: TeamsSettingsPagePro
|
||||
|
||||
<AvatarImageForm className="mb-8" team={team} user={session.user} />
|
||||
|
||||
<UpdateTeamForm
|
||||
teamId={team.id}
|
||||
teamName={team.name}
|
||||
teamUrl={team.url}
|
||||
documentVisibility={team.teamGlobalSettings?.documentVisibility}
|
||||
includeSenderDetails={team.teamGlobalSettings?.includeSenderDetails}
|
||||
/>
|
||||
<UpdateTeamForm teamId={team.id} teamName={team.name} teamUrl={team.url} />
|
||||
|
||||
<section className="mt-6 space-y-6">
|
||||
{(team.teamEmail || team.emailVerification) && (
|
||||
|
||||
@@ -39,6 +39,8 @@ const ZTeamDocumentPreferencesFormSchema = z.object({
|
||||
documentVisibility: z.nativeEnum(DocumentVisibility),
|
||||
documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES),
|
||||
includeSenderDetails: z.boolean(),
|
||||
typedSignatureEnabled: z.boolean(),
|
||||
includeSigningCertificate: z.boolean(),
|
||||
});
|
||||
|
||||
type TTeamDocumentPreferencesFormSchema = z.infer<typeof ZTeamDocumentPreferencesFormSchema>;
|
||||
@@ -68,6 +70,8 @@ export const TeamDocumentPreferencesForm = ({
|
||||
? settings?.documentLanguage
|
||||
: 'en',
|
||||
includeSenderDetails: settings?.includeSenderDetails ?? false,
|
||||
typedSignatureEnabled: settings?.typedSignatureEnabled ?? true,
|
||||
includeSigningCertificate: settings?.includeSigningCertificate ?? true,
|
||||
},
|
||||
resolver: zodResolver(ZTeamDocumentPreferencesFormSchema),
|
||||
});
|
||||
@@ -76,7 +80,13 @@ export const TeamDocumentPreferencesForm = ({
|
||||
|
||||
const onSubmit = async (data: TTeamDocumentPreferencesFormSchema) => {
|
||||
try {
|
||||
const { documentVisibility, documentLanguage, includeSenderDetails } = data;
|
||||
const {
|
||||
documentVisibility,
|
||||
documentLanguage,
|
||||
includeSenderDetails,
|
||||
includeSigningCertificate,
|
||||
typedSignatureEnabled,
|
||||
} = data;
|
||||
|
||||
await updateTeamDocumentPreferences({
|
||||
teamId: team.id,
|
||||
@@ -84,6 +94,8 @@ export const TeamDocumentPreferencesForm = ({
|
||||
documentVisibility,
|
||||
documentLanguage,
|
||||
includeSenderDetails,
|
||||
typedSignatureEnabled,
|
||||
includeSigningCertificate,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -105,7 +117,7 @@ export const TeamDocumentPreferencesForm = ({
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<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}
|
||||
>
|
||||
<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">
|
||||
<Button type="submit" loading={form.formState.isSubmitting}>
|
||||
<Trans>Save</Trans>
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZCssVarsSchema } from './css-vars';
|
||||
|
||||
export const ZBaseEmbedDataSchema = z.object({
|
||||
darkModeDisabled: z.boolean().optional().default(false),
|
||||
css: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((value) => value || undefined),
|
||||
cssVars: ZCssVarsSchema.optional().default({}),
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ export type EmbedDocumentCompletedPageProps = {
|
||||
};
|
||||
|
||||
export const EmbedDocumentCompleted = ({ name, signature }: EmbedDocumentCompletedPageProps) => {
|
||||
console.log({ signature });
|
||||
return (
|
||||
<div className="relative mx-auto flex min-h-[100dvh] max-w-screen-lg flex-col items-center justify-center p-6">
|
||||
<h3 className="text-foreground text-2xl font-semibold">
|
||||
|
||||
59
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';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
|
||||
@@ -14,7 +14,7 @@ import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-form
|
||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||
import { validateFieldsInserted } from '@documenso/lib/utils/fields';
|
||||
import type { DocumentMeta, Recipient, 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 { trpc } from '@documenso/trpc/react';
|
||||
import type {
|
||||
@@ -38,6 +38,7 @@ import { Logo } from '~/components/branding/logo';
|
||||
import { EmbedClientLoading } from '../../client-loading';
|
||||
import { EmbedDocumentCompleted } from '../../completed';
|
||||
import { EmbedDocumentFields } from '../../document-fields';
|
||||
import { injectCss } from '../../util';
|
||||
import { ZDirectTemplateEmbedDataSchema } from './schema';
|
||||
|
||||
export type EmbedDirectTemplateClientPageProps = {
|
||||
@@ -47,6 +48,8 @@ export type EmbedDirectTemplateClientPageProps = {
|
||||
recipient: Recipient;
|
||||
fields: Field[];
|
||||
metadata?: DocumentMeta | TemplateMeta | null;
|
||||
hidePoweredBy?: boolean;
|
||||
isPlatformOrEnterprise?: boolean;
|
||||
};
|
||||
|
||||
export const EmbedDirectTemplateClientPage = ({
|
||||
@@ -56,6 +59,8 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
recipient,
|
||||
fields,
|
||||
metadata,
|
||||
hidePoweredBy = false,
|
||||
isPlatformOrEnterprise = false,
|
||||
}: EmbedDirectTemplateClientPageProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
@@ -108,9 +113,9 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
created: new Date(),
|
||||
recipientId: 1,
|
||||
fieldId: 1,
|
||||
signatureImageAsBase64: payload.value,
|
||||
typedSignature: null,
|
||||
};
|
||||
signatureImageAsBase64: payload.value.startsWith('data:') ? payload.value : null,
|
||||
typedSignature: payload.value.startsWith('data:') ? null : payload.value,
|
||||
} satisfies Signature;
|
||||
}
|
||||
|
||||
if (field.type === FieldType.DATE) {
|
||||
@@ -249,7 +254,7 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
const hash = window.location.hash.slice(1);
|
||||
|
||||
try {
|
||||
@@ -264,6 +269,17 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
setFullName(data.name);
|
||||
setIsNameLocked(!!data.lockName);
|
||||
}
|
||||
|
||||
if (data.darkModeDisabled) {
|
||||
document.documentElement.classList.add('dark-mode-disabled');
|
||||
}
|
||||
|
||||
if (isPlatformOrEnterprise) {
|
||||
injectCss({
|
||||
css: data.css,
|
||||
cssVars: data.cssVars,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
@@ -296,8 +312,8 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
fieldId: 1,
|
||||
recipientId: 1,
|
||||
created: new Date(),
|
||||
typedSignature: null,
|
||||
signatureImageAsBase64: signature,
|
||||
signatureImageAsBase64: signature?.startsWith('data:') ? signature : null,
|
||||
typedSignature: signature?.startsWith('data:') ? null : signature,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -452,10 +468,12 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-primary text-primary-foreground fixed bottom-0 left-0 z-40 rounded-tr px-2 py-1 text-xs font-medium opacity-60 hover:opacity-100">
|
||||
<span>Powered by</span>
|
||||
<Logo className="ml-2 inline-block h-[14px]" />
|
||||
</div>
|
||||
{!hidePoweredBy && (
|
||||
<div className="bg-primary text-primary-foreground fixed bottom-0 left-0 z-40 rounded-tr px-2 py-1 text-xs font-medium opacity-60 hover:opacity-100">
|
||||
<span>Powered by</span>
|
||||
<Logo className="ml-2 inline-block h-[14px]" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,8 +2,11 @@ import { notFound } from 'next/navigation';
|
||||
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||
import { isDocumentPlatform } from '@documenso/ee/server-only/util/is-document-platform';
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
||||
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
|
||||
import { getTemplateByDirectLinkToken } from '@documenso/lib/server-only/template/get-template-by-direct-link-token';
|
||||
import { DocumentAccessAuth } from '@documenso/lib/types/document-auth';
|
||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||
@@ -51,6 +54,14 @@ export default async function EmbedDirectTemplatePage({ params }: EmbedDirectTem
|
||||
documentAuth: template.authOptions,
|
||||
});
|
||||
|
||||
const [isPlatformDocument, isEnterpriseDocument] = await Promise.all([
|
||||
isDocumentPlatform(template),
|
||||
isUserEnterprise({
|
||||
userId: template.userId,
|
||||
teamId: template.teamId ?? undefined,
|
||||
}),
|
||||
]);
|
||||
|
||||
const isAccessAuthValid = match(derivedRecipientAccessAuth)
|
||||
.with(DocumentAccessAuth.ACCOUNT, () => user !== null)
|
||||
.with(null, () => true)
|
||||
@@ -72,6 +83,12 @@ export default async function EmbedDirectTemplatePage({ params }: EmbedDirectTem
|
||||
|
||||
const fields = template.Field.filter((field) => field.recipientId === directTemplateRecipientId);
|
||||
|
||||
const team = template.teamId
|
||||
? await getTeamById({ teamId: template.teamId, userId: template.userId }).catch(() => null)
|
||||
: null;
|
||||
|
||||
const hidePoweredBy = team?.teamGlobalSettings?.brandingHidePoweredBy ?? false;
|
||||
|
||||
return (
|
||||
<SigningProvider email={user?.email} fullName={user?.name} signature={user?.signature}>
|
||||
<DocumentAuthProvider
|
||||
@@ -86,6 +103,8 @@ export default async function EmbedDirectTemplatePage({ params }: EmbedDirectTem
|
||||
recipient={recipient}
|
||||
fields={fields}
|
||||
metadata={template.templateMeta}
|
||||
hidePoweredBy={isPlatformDocument || isEnterpriseDocument || hidePoweredBy}
|
||||
isPlatformOrEnterprise={isPlatformDocument || isEnterpriseDocument}
|
||||
/>
|
||||
</DocumentAuthProvider>
|
||||
</SigningProvider>
|
||||
|
||||
@@ -58,6 +58,7 @@ export const EmbedDocumentFields = ({
|
||||
recipient={recipient}
|
||||
onSignField={onSignField}
|
||||
onUnsignField={onUnsignField}
|
||||
typedSignatureEnabled={metadata?.typedSignatureEnabled}
|
||||
/>
|
||||
))
|
||||
.with(FieldType.INITIALS, () => (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
@@ -28,6 +28,7 @@ import { Logo } from '~/components/branding/logo';
|
||||
import { EmbedClientLoading } from '../../client-loading';
|
||||
import { EmbedDocumentCompleted } from '../../completed';
|
||||
import { EmbedDocumentFields } from '../../document-fields';
|
||||
import { injectCss } from '../../util';
|
||||
import { ZSignDocumentEmbedDataSchema } from './schema';
|
||||
|
||||
export type EmbedSignDocumentClientPageProps = {
|
||||
@@ -38,6 +39,8 @@ export type EmbedSignDocumentClientPageProps = {
|
||||
fields: Field[];
|
||||
metadata?: DocumentMeta | TemplateMeta | null;
|
||||
isCompleted?: boolean;
|
||||
hidePoweredBy?: boolean;
|
||||
isPlatformOrEnterprise?: boolean;
|
||||
};
|
||||
|
||||
export const EmbedSignDocumentClientPage = ({
|
||||
@@ -48,6 +51,8 @@ export const EmbedSignDocumentClientPage = ({
|
||||
fields,
|
||||
metadata,
|
||||
isCompleted,
|
||||
hidePoweredBy = false,
|
||||
isPlatformOrEnterprise = false,
|
||||
}: EmbedSignDocumentClientPageProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
@@ -131,7 +136,7 @@ export const EmbedSignDocumentClientPage = ({
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
const hash = window.location.hash.slice(1);
|
||||
|
||||
try {
|
||||
@@ -144,6 +149,17 @@ export const EmbedSignDocumentClientPage = ({
|
||||
// Since a recipient can be provided a name we can lock it without requiring
|
||||
// a to be provided by the parent application, unlike direct templates.
|
||||
setIsNameLocked(!!data.lockName);
|
||||
|
||||
if (data.darkModeDisabled) {
|
||||
document.documentElement.classList.add('dark-mode-disabled');
|
||||
}
|
||||
|
||||
if (isPlatformOrEnterprise) {
|
||||
injectCss({
|
||||
css: data.css,
|
||||
cssVars: data.cssVars,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
@@ -176,8 +192,8 @@ export const EmbedSignDocumentClientPage = ({
|
||||
fieldId: 1,
|
||||
recipientId: 1,
|
||||
created: new Date(),
|
||||
typedSignature: null,
|
||||
signatureImageAsBase64: signature,
|
||||
signatureImageAsBase64: signature?.startsWith('data:') ? signature : null,
|
||||
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"
|
||||
data-expanded={isExpanded || undefined}
|
||||
>
|
||||
<div className="border-border bg-widget flex w-full flex-col rounded-xl border px-4 py-4 md:py-6">
|
||||
<div className="border-border bg-widget flex w-full flex-col rounded-xl border px-4 py-4 md:py-6">
|
||||
{/* Header */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between gap-x-2">
|
||||
@@ -325,10 +341,12 @@ export const EmbedSignDocumentClientPage = ({
|
||||
<EmbedDocumentFields recipient={recipient} fields={fields} metadata={metadata} />
|
||||
</div>
|
||||
|
||||
<div className="bg-primary text-primary-foreground fixed bottom-0 left-0 z-40 rounded-tr px-2 py-1 text-xs font-medium opacity-60 hover:opacity-100">
|
||||
<span>Powered by</span>
|
||||
<Logo className="ml-2 inline-block h-[14px]" />
|
||||
</div>
|
||||
{!hidePoweredBy && (
|
||||
<div className="bg-primary text-primary-foreground fixed bottom-0 left-0 z-40 rounded-tr px-2 py-1 text-xs font-medium opacity-60 hover:opacity-100">
|
||||
<span>Powered by</span>
|
||||
<Logo className="ml-2 inline-block h-[14px]" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,11 +2,14 @@ import { notFound } from 'next/navigation';
|
||||
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||
import { isDocumentPlatform } from '@documenso/ee/server-only/util/is-document-platform';
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
||||
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
||||
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
|
||||
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
|
||||
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
|
||||
import { DocumentAccessAuth } from '@documenso/lib/types/document-auth';
|
||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||
import { DocumentStatus } from '@documenso/prisma/client';
|
||||
@@ -56,6 +59,14 @@ export default async function EmbedSignDocumentPage({ params }: EmbedSignDocumen
|
||||
return <EmbedPaywall />;
|
||||
}
|
||||
|
||||
const [isPlatformDocument, isEnterpriseDocument] = await Promise.all([
|
||||
isDocumentPlatform(document),
|
||||
isUserEnterprise({
|
||||
userId: document.userId,
|
||||
teamId: document.teamId ?? undefined,
|
||||
}),
|
||||
]);
|
||||
|
||||
const { derivedRecipientAccessAuth } = extractDocumentAuthMethods({
|
||||
documentAuth: document.authOptions,
|
||||
});
|
||||
@@ -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 (
|
||||
<SigningProvider
|
||||
email={recipient.email}
|
||||
@@ -93,6 +110,8 @@ export default async function EmbedSignDocumentPage({ params }: EmbedSignDocumen
|
||||
fields={fields}
|
||||
metadata={document.documentMeta}
|
||||
isCompleted={document.status === DocumentStatus.COMPLETED}
|
||||
hidePoweredBy={isPlatformDocument || isEnterpriseDocument || hidePoweredBy}
|
||||
isPlatformOrEnterprise={isPlatformDocument || isEnterpriseDocument}
|
||||
/>
|
||||
</DocumentAuthProvider>
|
||||
</SigningProvider>
|
||||
|
||||
20
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 { useLingui } from '@lingui/react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { match } from 'ts-pattern';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { DocumentVisibility } from '@documenso/prisma/client';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { ZUpdateTeamMutationSchema } from '@documenso/trpc/server/team-router/schema';
|
||||
import {
|
||||
DocumentVisibilitySelect,
|
||||
DocumentVisibilityTooltip,
|
||||
} from '@documenso/ui/components/document/document-visibility-select';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Checkbox } from '@documenso/ui/primitives/checkbox';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -37,29 +29,17 @@ export type UpdateTeamDialogProps = {
|
||||
teamId: number;
|
||||
teamName: string;
|
||||
teamUrl: string;
|
||||
documentVisibility?: DocumentVisibility;
|
||||
includeSenderDetails?: boolean;
|
||||
};
|
||||
|
||||
const ZUpdateTeamFormSchema = ZUpdateTeamMutationSchema.shape.data.pick({
|
||||
name: true,
|
||||
url: true,
|
||||
documentVisibility: true,
|
||||
includeSenderDetails: true,
|
||||
});
|
||||
|
||||
type TUpdateTeamFormSchema = z.infer<typeof ZUpdateTeamFormSchema>;
|
||||
|
||||
export const UpdateTeamForm = ({
|
||||
teamId,
|
||||
teamName,
|
||||
teamUrl,
|
||||
documentVisibility,
|
||||
includeSenderDetails,
|
||||
}: UpdateTeamDialogProps) => {
|
||||
export const UpdateTeamForm = ({ teamId, teamName, teamUrl }: UpdateTeamDialogProps) => {
|
||||
const router = useRouter();
|
||||
const { data: session } = useSession();
|
||||
const email = session?.user?.email;
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -68,36 +48,17 @@ export const UpdateTeamForm = ({
|
||||
defaultValues: {
|
||||
name: teamName,
|
||||
url: teamUrl,
|
||||
documentVisibility,
|
||||
includeSenderDetails,
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: updateTeam } = trpc.team.updateTeam.useMutation();
|
||||
const includeSenderDetailsCheck = form.watch('includeSenderDetails');
|
||||
|
||||
const mapVisibilityToRole = (visibility: DocumentVisibility): DocumentVisibility =>
|
||||
match(visibility)
|
||||
.with(DocumentVisibility.ADMIN, () => DocumentVisibility.ADMIN)
|
||||
.with(DocumentVisibility.MANAGER_AND_ABOVE, () => DocumentVisibility.MANAGER_AND_ABOVE)
|
||||
.otherwise(() => DocumentVisibility.EVERYONE);
|
||||
|
||||
const currentVisibilityRole = mapVisibilityToRole(
|
||||
documentVisibility ?? DocumentVisibility.EVERYONE,
|
||||
);
|
||||
const onFormSubmit = async ({
|
||||
name,
|
||||
url,
|
||||
documentVisibility,
|
||||
includeSenderDetails,
|
||||
}: TUpdateTeamFormSchema) => {
|
||||
const onFormSubmit = async ({ name, url }: TUpdateTeamFormSchema) => {
|
||||
try {
|
||||
await updateTeam({
|
||||
data: {
|
||||
name,
|
||||
url,
|
||||
documentVisibility,
|
||||
includeSenderDetails,
|
||||
},
|
||||
teamId,
|
||||
});
|
||||
@@ -111,8 +72,6 @@ export const UpdateTeamForm = ({
|
||||
form.reset({
|
||||
name,
|
||||
url,
|
||||
documentVisibility,
|
||||
includeSenderDetails,
|
||||
});
|
||||
|
||||
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">
|
||||
<AnimatePresence>
|
||||
{form.formState.isDirty && (
|
||||
|
||||
@@ -167,6 +167,7 @@ export const DocumentHistorySheet = ({
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_CREATED },
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED },
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED },
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RESTORED },
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED },
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED },
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED },
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { HTMLAttributes } from 'react';
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { CheckCircle2, Clock, File } from 'lucide-react';
|
||||
import { CheckCircle2, Clock, File, TrashIcon } from 'lucide-react';
|
||||
import type { LucideIcon } from 'lucide-react/dist/lucide-react';
|
||||
|
||||
import type { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||
@@ -47,6 +47,12 @@ export const FRIENDLY_STATUS_MAP: Record<ExtendedDocumentStatus, FriendlyStatus>
|
||||
labelExtended: msg`Document All`,
|
||||
color: 'text-muted-foreground',
|
||||
},
|
||||
BIN: {
|
||||
label: msg`Bin`,
|
||||
labelExtended: msg`Document Bin`,
|
||||
icon: TrashIcon,
|
||||
color: 'text-red-500 dark:text-red-200',
|
||||
},
|
||||
};
|
||||
|
||||
export type DocumentStatusProps = HTMLAttributes<HTMLSpanElement> & {
|
||||
|
||||
@@ -138,6 +138,7 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
|
||||
containerClassName={cn('rounded-lg border bg-background')}
|
||||
defaultValue={user.signature ?? undefined}
|
||||
onChange={(v) => onChange(v ?? '')}
|
||||
allowTypedSignature={true}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
|
||||
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,
|
||||
"version": "1.8.1-rc.0",
|
||||
"version": "1.8.1-rc.1",
|
||||
"scripts": {
|
||||
"build": "turbo run build",
|
||||
"build:web": "turbo run build --filter=@documenso/web",
|
||||
@@ -52,7 +52,7 @@
|
||||
"husky": "^9.0.11",
|
||||
"lint-staged": "^15.2.2",
|
||||
"playwright": "1.43.0",
|
||||
"prettier": "^2.5.1",
|
||||
"prettier": "^3.3.3",
|
||||
"rimraf": "^5.0.1",
|
||||
"turbo": "^1.9.3"
|
||||
},
|
||||
|
||||
@@ -302,6 +302,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
||||
redirectUrl: body.meta.redirectUrl,
|
||||
signingOrder: body.meta.signingOrder,
|
||||
language: body.meta.language,
|
||||
typedSignatureEnabled: body.meta.typedSignatureEnabled,
|
||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import { z } from 'zod';
|
||||
|
||||
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||
import '@documenso/lib/constants/time-zones';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
import { ZUrlSchema } from '@documenso/lib/schemas/common';
|
||||
import {
|
||||
@@ -14,6 +13,7 @@ import {
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import {
|
||||
DocumentDataType,
|
||||
DocumentDistributionMethod,
|
||||
DocumentSigningOrder,
|
||||
FieldType,
|
||||
ReadStatus,
|
||||
@@ -132,6 +132,7 @@ export const ZCreateDocumentMutationSchema = z.object({
|
||||
redirectUrl: z.string(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
|
||||
typedSignatureEnabled: z.boolean().optional().default(true),
|
||||
})
|
||||
.partial(),
|
||||
authOptions: z
|
||||
@@ -226,14 +227,14 @@ export type TCreateDocumentFromTemplateMutationResponseSchema = z.infer<
|
||||
|
||||
export const ZGenerateDocumentFromTemplateMutationSchema = z.object({
|
||||
title: z.string().optional(),
|
||||
externalId: z.string().nullish(),
|
||||
externalId: z.string().optional(),
|
||||
recipients: z
|
||||
.array(
|
||||
z.object({
|
||||
id: z.number(),
|
||||
email: z.string().email(),
|
||||
name: z.string().optional(),
|
||||
email: z.string().email().min(1),
|
||||
signingOrder: z.number().nullish(),
|
||||
signingOrder: z.number().optional(),
|
||||
}),
|
||||
)
|
||||
.refine(
|
||||
@@ -252,8 +253,10 @@ export const ZGenerateDocumentFromTemplateMutationSchema = z.object({
|
||||
timezone: z.string(),
|
||||
dateFormat: z.string(),
|
||||
redirectUrl: ZUrlSchema,
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder),
|
||||
language: z.enum(SUPPORTED_LANGUAGE_CODES),
|
||||
distributionMethod: z.nativeEnum(DocumentDistributionMethod),
|
||||
typedSignatureEnabled: z.boolean(),
|
||||
})
|
||||
.partial()
|
||||
.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,
|
||||
email: team.owner.email,
|
||||
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('button', { name: 'Update team' }).click();
|
||||
await page.getByRole('button', { name: 'Save' }).first().click();
|
||||
|
||||
const toast = page.locator('li[role="status"][data-state="open"]').first();
|
||||
await expect(toast).toBeVisible();
|
||||
await expect(toast.getByText('Success', { exact: true })).toBeVisible();
|
||||
await expect(
|
||||
toast.getByText('Your team has been successfully updated.', { exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(toast.getByText('Document preferences updated', { exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
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,
|
||||
email: team.owner.email,
|
||||
password: 'password',
|
||||
redirectPath: `/t/${team.url}/settings`,
|
||||
redirectPath: `/t/${team.url}/settings/preferences`,
|
||||
});
|
||||
|
||||
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 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();
|
||||
await expect(toast).toBeVisible();
|
||||
await expect(toast.getByText('Success', { exact: true })).toBeVisible();
|
||||
await expect(
|
||||
toast.getByText('Your team has been successfully updated.', { exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(toast.getByText('Document preferences updated', { exact: true })).toBeVisible();
|
||||
|
||||
await expect(checkbox).toBeChecked();
|
||||
});
|
||||
|
||||
@@ -7,15 +7,17 @@
|
||||
"scripts": {
|
||||
"test:dev": "NODE_OPTIONS=--experimental-require-module playwright test",
|
||||
"test-ui:dev": "NODE_OPTIONS=--experimental-require-module playwright test --ui",
|
||||
"test:e2e": "NODE_OPTIONS=--experimental-require-module start-server-and-test \"npm run start -w @documenso/web\" http://localhost:3000 \"playwright test\""
|
||||
"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": [],
|
||||
"author": "",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.18.1",
|
||||
"@types/node": "^20.8.2",
|
||||
"@documenso/lib": "*",
|
||||
"@documenso/prisma": "*",
|
||||
"@documenso/web": "*"
|
||||
"@documenso/web": "*",
|
||||
"pdf-lib": "^1.17.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"start-server-and-test": "^2.0.1"
|
||||
|
||||
@@ -9,6 +9,7 @@ export const getDocumentRelatedPrices = async () => {
|
||||
return await getPricesByPlan([
|
||||
STRIPE_PLAN_TYPE.REGULAR,
|
||||
STRIPE_PLAN_TYPE.COMMUNITY,
|
||||
STRIPE_PLAN_TYPE.PLATFORM,
|
||||
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([
|
||||
STRIPE_PLAN_TYPE.REGULAR,
|
||||
STRIPE_PLAN_TYPE.COMMUNITY,
|
||||
STRIPE_PLAN_TYPE.PLATFORM,
|
||||
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.
|
||||
*/
|
||||
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',
|
||||
TEAM = 'team',
|
||||
COMMUNITY = 'community',
|
||||
PLATFORM = 'platform',
|
||||
ENTERPRISE = 'enterprise',
|
||||
}
|
||||
|
||||
@@ -17,12 +17,14 @@ const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
|
||||
documentVisibility: z.nativeEnum(DocumentVisibility),
|
||||
documentLanguage: z.string(),
|
||||
includeSenderDetails: z.boolean(),
|
||||
includeSigningCertificate: z.boolean(),
|
||||
brandingEnabled: z.boolean(),
|
||||
brandingLogo: z.string(),
|
||||
brandingUrl: z.string(),
|
||||
brandingCompanyDetails: z.string(),
|
||||
brandingHidePoweredBy: z.boolean(),
|
||||
teamId: z.number(),
|
||||
typedSignatureEnabled: z.boolean(),
|
||||
})
|
||||
.nullish(),
|
||||
}),
|
||||
|
||||
@@ -57,7 +57,17 @@ export const SEAL_DOCUMENT_JOB_DEFINITION = {
|
||||
},
|
||||
},
|
||||
include: {
|
||||
documentMeta: 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 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 pdfDoc = await PDFDocument.load(pdfData);
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
"pg": "^8.11.3",
|
||||
"playwright": "1.43.0",
|
||||
"react": "^18",
|
||||
"remeda": "^2.12.1",
|
||||
"remeda": "^2.17.3",
|
||||
"sharp": "0.32.6",
|
||||
"stripe": "^12.7.0",
|
||||
"ts-pattern": "^5.0.5",
|
||||
|
||||
@@ -13,6 +13,7 @@ export const getDocumentStats = async () => {
|
||||
[ExtendedDocumentStatus.DRAFT]: 0,
|
||||
[ExtendedDocumentStatus.PENDING]: 0,
|
||||
[ExtendedDocumentStatus.COMPLETED]: 0,
|
||||
[ExtendedDocumentStatus.BIN]: 0,
|
||||
[ExtendedDocumentStatus.ALL]: 0,
|
||||
};
|
||||
|
||||
|
||||
@@ -112,6 +112,7 @@ export const createDocument = async ({
|
||||
documentMeta: {
|
||||
create: {
|
||||
language: team?.teamGlobalSettings?.documentLanguage,
|
||||
typedSignatureEnabled: team?.teamGlobalSettings?.typedSignatureEnabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -158,6 +158,16 @@ const handleDocumentOwnerDelete = async ({
|
||||
}),
|
||||
});
|
||||
|
||||
// Soft delete for document recipients since the owner is deleting it
|
||||
await tx.recipient.updateMany({
|
||||
where: {
|
||||
documentId: document.id,
|
||||
},
|
||||
data: {
|
||||
documentDeletedAt: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
|
||||
return await tx.document.update({
|
||||
where: {
|
||||
id: document.id,
|
||||
|
||||
@@ -64,6 +64,7 @@ export const findDocumentAuditLogs = async ({
|
||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED,
|
||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_CREATED,
|
||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED,
|
||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RESTORED,
|
||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED,
|
||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED,
|
||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED,
|
||||
|
||||
@@ -2,15 +2,8 @@ import { DateTime } from 'luxon';
|
||||
import { P, match } from 'ts-pattern';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { RecipientRole, SigningStatus, TeamMemberRole } from '@documenso/prisma/client';
|
||||
import type {
|
||||
Document,
|
||||
DocumentSource,
|
||||
Prisma,
|
||||
Team,
|
||||
TeamEmail,
|
||||
User,
|
||||
} from '@documenso/prisma/client';
|
||||
import type { Document, DocumentSource, Team, TeamEmail, User } from '@documenso/prisma/client';
|
||||
import { Prisma, RecipientRole, SigningStatus, TeamMemberRole } from '@documenso/prisma/client';
|
||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||
|
||||
import { DocumentVisibility } from '../../types/document-visibility';
|
||||
@@ -88,14 +81,12 @@ export const findDocuments = async ({
|
||||
const teamMemberRole = team?.members[0].role ?? null;
|
||||
|
||||
const termFilters = match(term)
|
||||
.with(P.string.minLength(1), () => {
|
||||
return {
|
||||
title: {
|
||||
contains: term,
|
||||
mode: 'insensitive',
|
||||
},
|
||||
} as const;
|
||||
})
|
||||
.with(P.string.minLength(1), () => ({
|
||||
title: {
|
||||
contains: term,
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
},
|
||||
}))
|
||||
.otherwise(() => undefined);
|
||||
|
||||
const searchFilter: Prisma.DocumentWhereInput = {
|
||||
@@ -141,6 +132,8 @@ export const findDocuments = async ({
|
||||
|
||||
let filters: Prisma.DocumentWhereInput | null = findDocumentsFilter(status, user);
|
||||
|
||||
console.log('find documets team', team);
|
||||
|
||||
if (team) {
|
||||
filters = findTeamDocumentsFilter(status, team, visibilityFilters);
|
||||
}
|
||||
@@ -293,19 +286,21 @@ export const findDocuments = async ({
|
||||
} satisfies FindResultSet<typeof data>;
|
||||
};
|
||||
|
||||
const findDocumentsFilter = (status: ExtendedDocumentStatus, user: User) => {
|
||||
export const findDocumentsFilter = (status: ExtendedDocumentStatus, user: User) => {
|
||||
return match<ExtendedDocumentStatus, Prisma.DocumentWhereInput>(status)
|
||||
.with(ExtendedDocumentStatus.ALL, () => ({
|
||||
OR: [
|
||||
{
|
||||
userId: user.id,
|
||||
teamId: null,
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
status: ExtendedDocumentStatus.COMPLETED,
|
||||
Recipient: {
|
||||
some: {
|
||||
email: user.email,
|
||||
documentDeletedAt: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -314,6 +309,7 @@ const findDocumentsFilter = (status: ExtendedDocumentStatus, user: User) => {
|
||||
Recipient: {
|
||||
some: {
|
||||
email: user.email,
|
||||
documentDeletedAt: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -330,6 +326,7 @@ const findDocumentsFilter = (status: ExtendedDocumentStatus, user: User) => {
|
||||
role: {
|
||||
not: RecipientRole.CC,
|
||||
},
|
||||
documentDeletedAt: null,
|
||||
},
|
||||
},
|
||||
}))
|
||||
@@ -344,6 +341,7 @@ const findDocumentsFilter = (status: ExtendedDocumentStatus, user: User) => {
|
||||
userId: user.id,
|
||||
teamId: null,
|
||||
status: ExtendedDocumentStatus.PENDING,
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
status: ExtendedDocumentStatus.PENDING,
|
||||
@@ -354,6 +352,7 @@ const findDocumentsFilter = (status: ExtendedDocumentStatus, user: User) => {
|
||||
role: {
|
||||
not: RecipientRole.CC,
|
||||
},
|
||||
documentDeletedAt: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -365,12 +364,49 @@ const findDocumentsFilter = (status: ExtendedDocumentStatus, user: User) => {
|
||||
userId: user.id,
|
||||
teamId: null,
|
||||
status: ExtendedDocumentStatus.COMPLETED,
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
status: ExtendedDocumentStatus.COMPLETED,
|
||||
Recipient: {
|
||||
some: {
|
||||
email: user.email,
|
||||
documentDeletedAt: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}))
|
||||
.with(ExtendedDocumentStatus.BIN, () => ({
|
||||
OR: [
|
||||
{
|
||||
userId: user.id,
|
||||
teamId: null,
|
||||
deletedAt: {
|
||||
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
|
||||
},
|
||||
},
|
||||
{
|
||||
status: ExtendedDocumentStatus.PENDING,
|
||||
Recipient: {
|
||||
some: {
|
||||
email: user.email,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
documentDeletedAt: {
|
||||
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
status: ExtendedDocumentStatus.COMPLETED,
|
||||
Recipient: {
|
||||
some: {
|
||||
email: user.email,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
documentDeletedAt: {
|
||||
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -408,7 +444,7 @@ const findDocumentsFilter = (status: ExtendedDocumentStatus, user: User) => {
|
||||
* @param team The team to find the documents for.
|
||||
* @returns A filter which can be applied to the Prisma Document schema.
|
||||
*/
|
||||
const findTeamDocumentsFilter = (
|
||||
export const findTeamDocumentsFilter = (
|
||||
status: ExtendedDocumentStatus,
|
||||
team: Team & { teamEmail: TeamEmail | null },
|
||||
visibilityFilters: Prisma.DocumentWhereInput[],
|
||||
@@ -418,17 +454,16 @@ const findTeamDocumentsFilter = (
|
||||
return match<ExtendedDocumentStatus, Prisma.DocumentWhereInput | null>(status)
|
||||
.with(ExtendedDocumentStatus.ALL, () => {
|
||||
const filter: Prisma.DocumentWhereInput = {
|
||||
// Filter to display all documents that belong to the team.
|
||||
OR: [
|
||||
{
|
||||
teamId: team.id,
|
||||
deletedAt: null,
|
||||
OR: visibilityFilters,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (teamEmail && filter.OR) {
|
||||
// Filter to display all documents received by the team email that are not draft.
|
||||
filter.OR.push({
|
||||
status: {
|
||||
not: ExtendedDocumentStatus.DRAFT,
|
||||
@@ -438,14 +473,15 @@ const findTeamDocumentsFilter = (
|
||||
email: teamEmail,
|
||||
},
|
||||
},
|
||||
deletedAt: null,
|
||||
OR: visibilityFilters,
|
||||
});
|
||||
|
||||
// Filter to display all documents that have been sent by the team email.
|
||||
filter.OR.push({
|
||||
User: {
|
||||
email: teamEmail,
|
||||
},
|
||||
deletedAt: null,
|
||||
OR: visibilityFilters,
|
||||
});
|
||||
}
|
||||
@@ -453,7 +489,6 @@ const findTeamDocumentsFilter = (
|
||||
return filter;
|
||||
})
|
||||
.with(ExtendedDocumentStatus.INBOX, () => {
|
||||
// Return a filter that will return nothing.
|
||||
if (!teamEmail) {
|
||||
return null;
|
||||
}
|
||||
@@ -471,6 +506,7 @@ const findTeamDocumentsFilter = (
|
||||
},
|
||||
},
|
||||
},
|
||||
deletedAt: null,
|
||||
OR: visibilityFilters,
|
||||
};
|
||||
})
|
||||
@@ -480,6 +516,7 @@ const findTeamDocumentsFilter = (
|
||||
{
|
||||
teamId: team.id,
|
||||
status: ExtendedDocumentStatus.DRAFT,
|
||||
deletedAt: null,
|
||||
OR: visibilityFilters,
|
||||
},
|
||||
],
|
||||
@@ -491,6 +528,7 @@ const findTeamDocumentsFilter = (
|
||||
User: {
|
||||
email: teamEmail,
|
||||
},
|
||||
deletedAt: null,
|
||||
OR: visibilityFilters,
|
||||
});
|
||||
}
|
||||
@@ -503,6 +541,7 @@ const findTeamDocumentsFilter = (
|
||||
{
|
||||
teamId: team.id,
|
||||
status: ExtendedDocumentStatus.PENDING,
|
||||
deletedAt: null,
|
||||
OR: visibilityFilters,
|
||||
},
|
||||
],
|
||||
@@ -531,6 +570,7 @@ const findTeamDocumentsFilter = (
|
||||
OR: visibilityFilters,
|
||||
},
|
||||
],
|
||||
deletedAt: null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -539,6 +579,7 @@ const findTeamDocumentsFilter = (
|
||||
.with(ExtendedDocumentStatus.COMPLETED, () => {
|
||||
const filter: Prisma.DocumentWhereInput = {
|
||||
status: ExtendedDocumentStatus.COMPLETED,
|
||||
deletedAt: null,
|
||||
OR: [
|
||||
{
|
||||
teamId: team.id,
|
||||
@@ -568,5 +609,42 @@ const findTeamDocumentsFilter = (
|
||||
|
||||
return filter;
|
||||
})
|
||||
.with(ExtendedDocumentStatus.BIN, () => {
|
||||
const filters: Prisma.DocumentWhereInput[] = [
|
||||
{
|
||||
teamId: team.id,
|
||||
deletedAt: {
|
||||
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (teamEmail) {
|
||||
filters.push(
|
||||
{
|
||||
User: {
|
||||
email: teamEmail,
|
||||
},
|
||||
deletedAt: {
|
||||
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
|
||||
},
|
||||
},
|
||||
{
|
||||
Recipient: {
|
||||
some: {
|
||||
email: teamEmail,
|
||||
documentDeletedAt: {
|
||||
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
OR: filters,
|
||||
};
|
||||
})
|
||||
.exhaustive();
|
||||
};
|
||||
|
||||
118
packages/lib/server-only/document/get-stats-new.tsx
Normal file
118
packages/lib/server-only/document/get-stats-new.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import {
|
||||
type PeriodSelectorValue,
|
||||
findDocumentsFilter,
|
||||
findTeamDocumentsFilter,
|
||||
} from '@documenso/lib/server-only/document/find-documents';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Prisma, Team, TeamEmail, User } from '@documenso/prisma/client';
|
||||
import { DocumentVisibility, TeamMemberRole } from '@documenso/prisma/client';
|
||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||
|
||||
export type GetStatsInput = {
|
||||
user: User;
|
||||
team?: Team & { teamEmail: TeamEmail | null } & { currentTeamMember?: { role: TeamMemberRole } };
|
||||
period?: PeriodSelectorValue;
|
||||
search?: string;
|
||||
};
|
||||
|
||||
export const getStats = async ({ user, period, search, ...options }: GetStatsInput) => {
|
||||
let createdAt: Prisma.DocumentWhereInput['createdAt'];
|
||||
|
||||
if (period) {
|
||||
const daysAgo = parseInt(period.replace(/d$/, ''), 10);
|
||||
|
||||
const startOfPeriod = DateTime.now().minus({ days: daysAgo }).startOf('day');
|
||||
|
||||
createdAt = {
|
||||
gte: startOfPeriod.toJSDate(),
|
||||
};
|
||||
}
|
||||
|
||||
const stats: Record<ExtendedDocumentStatus, number> = {
|
||||
[ExtendedDocumentStatus.DRAFT]: 0,
|
||||
[ExtendedDocumentStatus.PENDING]: 0,
|
||||
[ExtendedDocumentStatus.COMPLETED]: 0,
|
||||
[ExtendedDocumentStatus.INBOX]: 0,
|
||||
[ExtendedDocumentStatus.ALL]: 0,
|
||||
[ExtendedDocumentStatus.BIN]: 0,
|
||||
};
|
||||
|
||||
const searchFilter: Prisma.DocumentWhereInput = search
|
||||
? {
|
||||
OR: [
|
||||
{ title: { contains: search, mode: 'insensitive' } },
|
||||
{ Recipient: { some: { name: { contains: search, mode: 'insensitive' } } } },
|
||||
{ Recipient: { some: { email: { contains: search, mode: 'insensitive' } } } },
|
||||
],
|
||||
}
|
||||
: {};
|
||||
|
||||
const visibilityFilters = [
|
||||
match(options.team?.currentTeamMember?.role)
|
||||
.with(TeamMemberRole.ADMIN, () => ({
|
||||
visibility: {
|
||||
in: [
|
||||
DocumentVisibility.EVERYONE,
|
||||
DocumentVisibility.MANAGER_AND_ABOVE,
|
||||
DocumentVisibility.ADMIN,
|
||||
],
|
||||
},
|
||||
}))
|
||||
.with(TeamMemberRole.MANAGER, () => ({
|
||||
visibility: {
|
||||
in: [DocumentVisibility.EVERYONE, DocumentVisibility.MANAGER_AND_ABOVE],
|
||||
},
|
||||
}))
|
||||
.otherwise(() => ({ visibility: DocumentVisibility.EVERYONE })),
|
||||
];
|
||||
|
||||
const statusCounts = await Promise.all(
|
||||
Object.values(ExtendedDocumentStatus).map(async (status) => {
|
||||
if (status === ExtendedDocumentStatus.ALL) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filter = options.team
|
||||
? findTeamDocumentsFilter(status, options.team, visibilityFilters)
|
||||
: findDocumentsFilter(status, user);
|
||||
|
||||
if (filter === null) {
|
||||
return { status, count: 0 };
|
||||
}
|
||||
|
||||
const whereClause = {
|
||||
...filter,
|
||||
...(createdAt && { createdAt }),
|
||||
...searchFilter,
|
||||
};
|
||||
|
||||
const count = await prisma.document.count({
|
||||
where: whereClause,
|
||||
});
|
||||
|
||||
return { status, count };
|
||||
}),
|
||||
);
|
||||
|
||||
statusCounts.forEach((result) => {
|
||||
if (result) {
|
||||
stats[result.status] = result.count;
|
||||
if (
|
||||
result.status !== ExtendedDocumentStatus.BIN &&
|
||||
[
|
||||
ExtendedDocumentStatus.DRAFT,
|
||||
ExtendedDocumentStatus.PENDING,
|
||||
ExtendedDocumentStatus.COMPLETED,
|
||||
ExtendedDocumentStatus.INBOX,
|
||||
].includes(result.status)
|
||||
) {
|
||||
stats[ExtendedDocumentStatus.ALL] += result.count;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return stats;
|
||||
};
|
||||
@@ -1,12 +1,16 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import type { PeriodSelectorValue } from '@documenso/lib/server-only/document/find-documents';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { TeamMemberRole } from '@documenso/prisma/client';
|
||||
import type { Prisma, User } from '@documenso/prisma/client';
|
||||
import { SigningStatus } from '@documenso/prisma/client';
|
||||
import { DocumentVisibility } from '@documenso/prisma/client';
|
||||
import {
|
||||
DocumentVisibility,
|
||||
RecipientRole,
|
||||
SigningStatus,
|
||||
TeamMemberRole,
|
||||
} from '@documenso/prisma/client';
|
||||
import { isExtendedDocumentStatus } from '@documenso/prisma/guards/is-extended-document-status';
|
||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||
|
||||
@@ -30,7 +34,7 @@ export const getStats = async ({ user, period, search, ...options }: GetStatsInp
|
||||
};
|
||||
}
|
||||
|
||||
const [ownerCounts, notSignedCounts, hasSignedCounts] = await (options.team
|
||||
const [ownerCounts, notSignedCounts, hasSignedCounts, deletedCounts] = await (options.team
|
||||
? getTeamCounts({
|
||||
...options.team,
|
||||
createdAt,
|
||||
@@ -46,6 +50,7 @@ export const getStats = async ({ user, period, search, ...options }: GetStatsInp
|
||||
[ExtendedDocumentStatus.COMPLETED]: 0,
|
||||
[ExtendedDocumentStatus.INBOX]: 0,
|
||||
[ExtendedDocumentStatus.ALL]: 0,
|
||||
[ExtendedDocumentStatus.BIN]: 0,
|
||||
};
|
||||
|
||||
ownerCounts.forEach((stat) => {
|
||||
@@ -66,6 +71,10 @@ export const getStats = async ({ user, period, search, ...options }: GetStatsInp
|
||||
}
|
||||
});
|
||||
|
||||
deletedCounts.forEach((stat) => {
|
||||
stats[ExtendedDocumentStatus.BIN] += stat._count._all;
|
||||
});
|
||||
|
||||
Object.keys(stats).forEach((key) => {
|
||||
if (key !== ExtendedDocumentStatus.ALL && isExtendedDocumentStatus(key)) {
|
||||
stats[ExtendedDocumentStatus.ALL] += stats[key];
|
||||
@@ -98,25 +107,45 @@ const getCounts = async ({ user, createdAt, search }: GetCountsOption) => {
|
||||
_all: true,
|
||||
},
|
||||
where: {
|
||||
userId: user.id,
|
||||
OR: [
|
||||
{
|
||||
userId: user.id,
|
||||
teamId: null,
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
status: {
|
||||
not: ExtendedDocumentStatus.DRAFT,
|
||||
},
|
||||
Recipient: {
|
||||
some: {
|
||||
email: user.email,
|
||||
documentDeletedAt: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
createdAt,
|
||||
teamId: null,
|
||||
deletedAt: null,
|
||||
AND: [searchFilter],
|
||||
},
|
||||
}),
|
||||
// Not signed counts.
|
||||
// Not signed counts (Inbox).
|
||||
prisma.document.groupBy({
|
||||
by: ['status'],
|
||||
_count: {
|
||||
_all: true,
|
||||
},
|
||||
where: {
|
||||
status: ExtendedDocumentStatus.PENDING,
|
||||
status: {
|
||||
not: ExtendedDocumentStatus.DRAFT,
|
||||
},
|
||||
Recipient: {
|
||||
some: {
|
||||
email: user.email,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
role: {
|
||||
not: RecipientRole.CC,
|
||||
},
|
||||
documentDeletedAt: null,
|
||||
},
|
||||
},
|
||||
@@ -131,30 +160,81 @@ const getCounts = async ({ user, createdAt, search }: GetCountsOption) => {
|
||||
_all: true,
|
||||
},
|
||||
where: {
|
||||
createdAt,
|
||||
User: {
|
||||
email: {
|
||||
not: user.email,
|
||||
},
|
||||
},
|
||||
OR: [
|
||||
{
|
||||
userId: user.id,
|
||||
teamId: null,
|
||||
status: ExtendedDocumentStatus.PENDING,
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
status: ExtendedDocumentStatus.PENDING,
|
||||
Recipient: {
|
||||
some: {
|
||||
email: user.email,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
role: {
|
||||
not: RecipientRole.CC,
|
||||
},
|
||||
documentDeletedAt: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
userId: user.id,
|
||||
teamId: null,
|
||||
status: ExtendedDocumentStatus.COMPLETED,
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
status: ExtendedDocumentStatus.COMPLETED,
|
||||
Recipient: {
|
||||
some: {
|
||||
email: user.email,
|
||||
documentDeletedAt: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
createdAt,
|
||||
AND: [searchFilter],
|
||||
},
|
||||
}),
|
||||
// Deleted counts.
|
||||
prisma.document.groupBy({
|
||||
by: ['status'],
|
||||
_count: {
|
||||
_all: true,
|
||||
},
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
userId: user.id,
|
||||
teamId: null,
|
||||
deletedAt: {
|
||||
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
|
||||
},
|
||||
},
|
||||
{
|
||||
status: ExtendedDocumentStatus.PENDING,
|
||||
Recipient: {
|
||||
some: {
|
||||
email: user.email,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
documentDeletedAt: {
|
||||
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
status: ExtendedDocumentStatus.COMPLETED,
|
||||
Recipient: {
|
||||
some: {
|
||||
email: user.email,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
documentDeletedAt: null,
|
||||
documentDeletedAt: {
|
||||
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -177,9 +257,7 @@ type GetTeamCountsOption = {
|
||||
};
|
||||
|
||||
const getTeamCounts = async (options: GetTeamCountsOption) => {
|
||||
const { createdAt, teamId, teamEmail } = options;
|
||||
|
||||
const senderIds = options.senderIds ?? [];
|
||||
const { createdAt, teamId, teamEmail, senderIds = [], currentTeamMemberRole, search } = options;
|
||||
|
||||
const userIdWhereClause: Prisma.DocumentWhereInput['userId'] =
|
||||
senderIds.length > 0
|
||||
@@ -188,148 +266,226 @@ const getTeamCounts = async (options: GetTeamCountsOption) => {
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const searchFilter: Prisma.DocumentWhereInput = {
|
||||
OR: [
|
||||
{ title: { contains: options.search, mode: 'insensitive' } },
|
||||
{ Recipient: { some: { name: { contains: options.search, mode: 'insensitive' } } } },
|
||||
{ Recipient: { some: { email: { contains: options.search, mode: 'insensitive' } } } },
|
||||
],
|
||||
};
|
||||
|
||||
let ownerCountsWhereInput: Prisma.DocumentWhereInput = {
|
||||
userId: userIdWhereClause,
|
||||
createdAt,
|
||||
teamId,
|
||||
deletedAt: null,
|
||||
};
|
||||
|
||||
let notSignedCountsGroupByArgs = null;
|
||||
let hasSignedCountsGroupByArgs = null;
|
||||
|
||||
const visibilityFiltersWhereInput: Prisma.DocumentWhereInput = {
|
||||
AND: [
|
||||
{ deletedAt: null },
|
||||
{
|
||||
const searchFilter: Prisma.DocumentWhereInput = search
|
||||
? {
|
||||
OR: [
|
||||
match(options.currentTeamMemberRole)
|
||||
.with(TeamMemberRole.ADMIN, () => ({
|
||||
visibility: {
|
||||
in: [
|
||||
DocumentVisibility.EVERYONE,
|
||||
DocumentVisibility.MANAGER_AND_ABOVE,
|
||||
DocumentVisibility.ADMIN,
|
||||
],
|
||||
},
|
||||
}))
|
||||
.with(TeamMemberRole.MANAGER, () => ({
|
||||
visibility: {
|
||||
in: [DocumentVisibility.EVERYONE, DocumentVisibility.MANAGER_AND_ABOVE],
|
||||
},
|
||||
}))
|
||||
.otherwise(() => ({
|
||||
visibility: {
|
||||
equals: DocumentVisibility.EVERYONE,
|
||||
},
|
||||
})),
|
||||
{
|
||||
OR: [
|
||||
{ userId: options.userId },
|
||||
{ Recipient: { some: { email: options.currentUserEmail } } },
|
||||
],
|
||||
},
|
||||
{ title: { contains: search, mode: 'insensitive' } },
|
||||
{ Recipient: { some: { name: { contains: search, mode: 'insensitive' } } } },
|
||||
{ Recipient: { some: { email: { contains: search, mode: 'insensitive' } } } },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
: {};
|
||||
|
||||
ownerCountsWhereInput = {
|
||||
...ownerCountsWhereInput,
|
||||
...visibilityFiltersWhereInput,
|
||||
...searchFilter,
|
||||
};
|
||||
|
||||
if (teamEmail) {
|
||||
ownerCountsWhereInput = {
|
||||
userId: userIdWhereClause,
|
||||
createdAt,
|
||||
OR: [
|
||||
{
|
||||
teamId,
|
||||
const visibilityFilters = [
|
||||
match(currentTeamMemberRole)
|
||||
.with(TeamMemberRole.ADMIN, () => ({
|
||||
visibility: {
|
||||
in: [
|
||||
DocumentVisibility.EVERYONE,
|
||||
DocumentVisibility.MANAGER_AND_ABOVE,
|
||||
DocumentVisibility.ADMIN,
|
||||
],
|
||||
},
|
||||
{
|
||||
User: {
|
||||
email: teamEmail,
|
||||
},
|
||||
}))
|
||||
.with(TeamMemberRole.MANAGER, () => ({
|
||||
visibility: {
|
||||
in: [DocumentVisibility.EVERYONE, DocumentVisibility.MANAGER_AND_ABOVE],
|
||||
},
|
||||
],
|
||||
deletedAt: null,
|
||||
};
|
||||
|
||||
notSignedCountsGroupByArgs = {
|
||||
by: ['status'],
|
||||
_count: {
|
||||
_all: true,
|
||||
},
|
||||
where: {
|
||||
userId: userIdWhereClause,
|
||||
createdAt,
|
||||
status: ExtendedDocumentStatus.PENDING,
|
||||
Recipient: {
|
||||
some: {
|
||||
email: teamEmail,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
documentDeletedAt: null,
|
||||
},
|
||||
},
|
||||
deletedAt: null,
|
||||
},
|
||||
} satisfies Prisma.DocumentGroupByArgs;
|
||||
|
||||
hasSignedCountsGroupByArgs = {
|
||||
by: ['status'],
|
||||
_count: {
|
||||
_all: true,
|
||||
},
|
||||
where: {
|
||||
userId: userIdWhereClause,
|
||||
createdAt,
|
||||
OR: [
|
||||
{
|
||||
status: ExtendedDocumentStatus.PENDING,
|
||||
Recipient: {
|
||||
some: {
|
||||
email: teamEmail,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
documentDeletedAt: null,
|
||||
},
|
||||
},
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
status: ExtendedDocumentStatus.COMPLETED,
|
||||
Recipient: {
|
||||
some: {
|
||||
email: teamEmail,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
documentDeletedAt: null,
|
||||
},
|
||||
},
|
||||
deletedAt: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
} satisfies Prisma.DocumentGroupByArgs;
|
||||
}
|
||||
}))
|
||||
.otherwise(() => ({ visibility: DocumentVisibility.EVERYONE })),
|
||||
];
|
||||
|
||||
return Promise.all([
|
||||
// Owner counts (ALL)
|
||||
prisma.document.groupBy({
|
||||
by: ['status'],
|
||||
_count: {
|
||||
_all: true,
|
||||
_count: { _all: true },
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
teamId,
|
||||
deletedAt: null,
|
||||
OR: visibilityFilters,
|
||||
},
|
||||
...(teamEmail
|
||||
? [
|
||||
{
|
||||
status: {
|
||||
not: ExtendedDocumentStatus.DRAFT,
|
||||
},
|
||||
Recipient: {
|
||||
some: {
|
||||
email: teamEmail,
|
||||
documentDeletedAt: null,
|
||||
},
|
||||
},
|
||||
deletedAt: null,
|
||||
OR: visibilityFilters,
|
||||
},
|
||||
{
|
||||
User: {
|
||||
email: teamEmail,
|
||||
},
|
||||
deletedAt: null,
|
||||
OR: visibilityFilters,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
userId: userIdWhereClause,
|
||||
createdAt,
|
||||
...searchFilter,
|
||||
},
|
||||
}),
|
||||
|
||||
// Not signed counts (INBOX)
|
||||
prisma.document.groupBy({
|
||||
by: ['status'],
|
||||
_count: { _all: true },
|
||||
where: teamEmail
|
||||
? {
|
||||
userId: userIdWhereClause,
|
||||
createdAt,
|
||||
status: {
|
||||
not: ExtendedDocumentStatus.DRAFT,
|
||||
},
|
||||
Recipient: {
|
||||
some: {
|
||||
email: teamEmail,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
role: {
|
||||
not: RecipientRole.CC,
|
||||
},
|
||||
},
|
||||
},
|
||||
deletedAt: null,
|
||||
OR: visibilityFilters,
|
||||
...searchFilter,
|
||||
}
|
||||
: {
|
||||
userId: userIdWhereClause,
|
||||
createdAt,
|
||||
AND: [
|
||||
{
|
||||
OR: [{ id: -1 }], // Empty set if no team email
|
||||
},
|
||||
searchFilter,
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
// Has signed counts (PENDING + COMPLETED)
|
||||
prisma.document.groupBy({
|
||||
by: ['status'],
|
||||
_count: { _all: true },
|
||||
where: {
|
||||
userId: userIdWhereClause,
|
||||
createdAt,
|
||||
OR: [
|
||||
{
|
||||
teamId,
|
||||
status: ExtendedDocumentStatus.PENDING,
|
||||
deletedAt: null,
|
||||
OR: visibilityFilters,
|
||||
},
|
||||
{
|
||||
teamId,
|
||||
status: ExtendedDocumentStatus.COMPLETED,
|
||||
deletedAt: null,
|
||||
OR: visibilityFilters,
|
||||
},
|
||||
...(teamEmail
|
||||
? [
|
||||
{
|
||||
status: ExtendedDocumentStatus.PENDING,
|
||||
OR: [
|
||||
{
|
||||
Recipient: {
|
||||
some: {
|
||||
email: teamEmail,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
role: {
|
||||
not: RecipientRole.CC,
|
||||
},
|
||||
documentDeletedAt: null,
|
||||
},
|
||||
},
|
||||
OR: visibilityFilters,
|
||||
},
|
||||
{
|
||||
User: {
|
||||
email: teamEmail,
|
||||
},
|
||||
OR: visibilityFilters,
|
||||
},
|
||||
],
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
status: ExtendedDocumentStatus.COMPLETED,
|
||||
OR: [
|
||||
{
|
||||
Recipient: {
|
||||
some: {
|
||||
email: teamEmail,
|
||||
documentDeletedAt: null,
|
||||
},
|
||||
},
|
||||
OR: visibilityFilters,
|
||||
},
|
||||
{
|
||||
User: {
|
||||
email: teamEmail,
|
||||
},
|
||||
OR: visibilityFilters,
|
||||
},
|
||||
],
|
||||
deletedAt: null,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
...searchFilter,
|
||||
},
|
||||
}),
|
||||
|
||||
// Deleted counts (BIN)
|
||||
prisma.document.groupBy({
|
||||
by: ['status'],
|
||||
_count: { _all: true },
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
teamId,
|
||||
deletedAt: {
|
||||
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
|
||||
},
|
||||
},
|
||||
...(teamEmail
|
||||
? [
|
||||
{
|
||||
User: {
|
||||
email: teamEmail,
|
||||
},
|
||||
deletedAt: {
|
||||
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
|
||||
},
|
||||
},
|
||||
{
|
||||
Recipient: {
|
||||
some: {
|
||||
email: teamEmail,
|
||||
documentDeletedAt: {
|
||||
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
...searchFilter,
|
||||
},
|
||||
where: ownerCountsWhereInput,
|
||||
}),
|
||||
notSignedCountsGroupByArgs ? prisma.document.groupBy(notSignedCountsGroupByArgs) : [],
|
||||
hasSignedCountsGroupByArgs ? prisma.document.groupBy(hasSignedCountsGroupByArgs) : [],
|
||||
]);
|
||||
};
|
||||
|
||||
149
packages/lib/server-only/document/restore-document.ts
Normal file
149
packages/lib/server-only/document/restore-document.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
'use server';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Document, DocumentMeta, Recipient, User } from '@documenso/prisma/client';
|
||||
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||
|
||||
export type RestoreDocumentOptions = {
|
||||
id: number;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
requestMetadata?: RequestMetadata;
|
||||
};
|
||||
|
||||
export const restoreDocument = async ({
|
||||
id,
|
||||
userId,
|
||||
teamId,
|
||||
requestMetadata,
|
||||
}: RestoreDocumentOptions) => {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
const document = await prisma.document.findUnique({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
include: {
|
||||
Recipient: true,
|
||||
documentMeta: true,
|
||||
team: {
|
||||
select: {
|
||||
members: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!document || (teamId !== undefined && teamId !== document.teamId)) {
|
||||
throw new Error('Document not found');
|
||||
}
|
||||
|
||||
const isUserOwner = document.userId === userId;
|
||||
const isUserTeamMember = document.team?.members.some((member) => member.userId === userId);
|
||||
const userRecipient = document.Recipient.find((recipient) => recipient.email === user.email);
|
||||
|
||||
if (!isUserOwner && !isUserTeamMember && !userRecipient) {
|
||||
throw new Error('Not allowed');
|
||||
}
|
||||
|
||||
// Handle restoring the actual document if user has permission.
|
||||
if (isUserOwner || isUserTeamMember) {
|
||||
await handleDocumentOwnerRestore({
|
||||
document,
|
||||
user,
|
||||
requestMetadata,
|
||||
});
|
||||
}
|
||||
|
||||
// Continue to show the document to the user if they are a recipient.
|
||||
if (userRecipient?.documentDeletedAt !== null) {
|
||||
await prisma.recipient
|
||||
.update({
|
||||
where: {
|
||||
id: userRecipient?.id,
|
||||
},
|
||||
data: {
|
||||
documentDeletedAt: null,
|
||||
},
|
||||
})
|
||||
.catch(() => {
|
||||
// Do nothing.
|
||||
});
|
||||
}
|
||||
|
||||
// Return partial document for API v1 response.
|
||||
return {
|
||||
id: document.id,
|
||||
userId: document.userId,
|
||||
teamId: document.teamId,
|
||||
title: document.title,
|
||||
status: document.status,
|
||||
documentDataId: document.documentDataId,
|
||||
createdAt: document.createdAt,
|
||||
updatedAt: document.updatedAt,
|
||||
completedAt: document.completedAt,
|
||||
};
|
||||
};
|
||||
|
||||
type HandleDocumentOwnerRestoreOptions = {
|
||||
document: Document & {
|
||||
Recipient: Recipient[];
|
||||
documentMeta: DocumentMeta | null;
|
||||
};
|
||||
user: User;
|
||||
requestMetadata?: RequestMetadata;
|
||||
};
|
||||
|
||||
const handleDocumentOwnerRestore = async ({
|
||||
document,
|
||||
user,
|
||||
requestMetadata,
|
||||
}: HandleDocumentOwnerRestoreOptions) => {
|
||||
if (!document.deletedAt) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Restore soft-deleted documents.
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
await tx.documentAuditLog.create({
|
||||
data: createDocumentAuditLogData({
|
||||
documentId: document.id,
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RESTORED,
|
||||
user,
|
||||
requestMetadata,
|
||||
data: {
|
||||
type: 'RESTORE',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await tx.recipient.updateMany({
|
||||
where: {
|
||||
documentId: document.id,
|
||||
},
|
||||
data: {
|
||||
documentDeletedAt: null,
|
||||
},
|
||||
});
|
||||
|
||||
return await tx.document.update({
|
||||
where: {
|
||||
id: document.id,
|
||||
},
|
||||
data: {
|
||||
deletedAt: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -10,7 +10,6 @@ import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/
|
||||
import { WebhookTriggerEvents } from '@documenso/prisma/client';
|
||||
import { signPdf } from '@documenso/signing';
|
||||
|
||||
import { ZSupportedLanguageCodeSchema } from '../../constants/i18n';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { getFile } from '../../universal/upload/get-file';
|
||||
import { putPdfFile } from '../../universal/upload/put-file';
|
||||
@@ -48,6 +47,15 @@ export const sealDocument = async ({
|
||||
documentData: true,
|
||||
documentMeta: 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
|
||||
const pdfData = await getFile(documentData);
|
||||
|
||||
const documentLanguage = ZSupportedLanguageCodeSchema.parse(document.documentMeta?.language);
|
||||
|
||||
const certificate = await getCertificatePdf({ documentId, language: documentLanguage })
|
||||
.then(async (doc) => PDFDocument.load(doc))
|
||||
.catch(() => null);
|
||||
const certificateData =
|
||||
(document.team?.teamGlobalSettings?.includeSigningCertificate ?? true)
|
||||
? await getCertificatePdf({
|
||||
documentId,
|
||||
language: document.documentMeta?.language,
|
||||
}).catch(() => null)
|
||||
: null;
|
||||
|
||||
const doc = await PDFDocument.load(pdfData);
|
||||
|
||||
@@ -105,7 +115,9 @@ export const sealDocument = async ({
|
||||
flattenForm(doc);
|
||||
flattenAnnotations(doc);
|
||||
|
||||
if (certificate) {
|
||||
if (certificateData) {
|
||||
const certificate = await PDFDocument.load(certificateData);
|
||||
|
||||
const certificatePages = await doc.copyPages(certificate, certificate.getPageIndices());
|
||||
|
||||
certificatePages.forEach((page) => {
|
||||
|
||||
@@ -5,7 +5,11 @@ import { getToken } from 'next-auth/jwt';
|
||||
import { LOCAL_FEATURE_FLAGS } from '@documenso/lib/constants/feature-flags';
|
||||
import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-post-hog-server-client';
|
||||
|
||||
import { NEXT_PUBLIC_MARKETING_URL, NEXT_PUBLIC_WEBAPP_URL, NEXT_PRIVATE_INTERNAL_WEBAPP_URL } from '../../constants/app';
|
||||
import {
|
||||
NEXT_PRIVATE_INTERNAL_WEBAPP_URL,
|
||||
NEXT_PUBLIC_MARKETING_URL,
|
||||
NEXT_PUBLIC_WEBAPP_URL,
|
||||
} from '../../constants/app';
|
||||
import { extractDistinctUserId, mapJwtToFlagProperties } from './get';
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,11 @@ import { getToken } from 'next-auth/jwt';
|
||||
import { LOCAL_FEATURE_FLAGS, extractPostHogConfig } from '@documenso/lib/constants/feature-flags';
|
||||
import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-post-hog-server-client';
|
||||
|
||||
import { NEXT_PUBLIC_MARKETING_URL, NEXT_PUBLIC_WEBAPP_URL, NEXT_PRIVATE_INTERNAL_WEBAPP_URL } from '../../constants/app';
|
||||
import {
|
||||
NEXT_PRIVATE_INTERNAL_WEBAPP_URL,
|
||||
NEXT_PUBLIC_MARKETING_URL,
|
||||
NEXT_PUBLIC_WEBAPP_URL,
|
||||
} from '../../constants/app';
|
||||
|
||||
/**
|
||||
* Evaluate a single feature flag based on the current user if possible.
|
||||
@@ -67,7 +71,7 @@ export default async function handleFeatureFlagGet(req: Request) {
|
||||
if (origin.startsWith(NEXT_PUBLIC_MARKETING_URL() ?? 'http://localhost:3001')) {
|
||||
res.headers.set('Access-Control-Allow-Origin', origin);
|
||||
}
|
||||
|
||||
|
||||
if (origin.startsWith(NEXT_PRIVATE_INTERNAL_WEBAPP_URL ?? 'http://localhost:3000')) {
|
||||
res.headers.set('Access-Control-Allow-Origin', origin);
|
||||
}
|
||||
|
||||
@@ -177,6 +177,10 @@ export const signFieldWithToken = async ({
|
||||
throw new Error('Signature field must have a signature');
|
||||
}
|
||||
|
||||
if (isSignatureField && !documentMeta?.typedSignatureEnabled && typedSignature) {
|
||||
throw new Error('Typed signatures are not allowed. Please draw your signature');
|
||||
}
|
||||
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
const updatedField = await tx.field.update({
|
||||
where: {
|
||||
|
||||
@@ -2,12 +2,13 @@ import { DateTime } from 'luxon';
|
||||
import type { Browser } from 'playwright';
|
||||
|
||||
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';
|
||||
|
||||
export type GetCertificatePdfOptions = {
|
||||
documentId: number;
|
||||
language?: SupportedLanguageCodes;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
language?: SupportedLanguageCodes | (string & {});
|
||||
};
|
||||
|
||||
export const getCertificatePdf = async ({ documentId, language }: GetCertificatePdfOptions) => {
|
||||
@@ -38,15 +39,15 @@ export const getCertificatePdf = async ({ documentId, language }: GetCertificate
|
||||
|
||||
const page = await browserContext.newPage();
|
||||
|
||||
if (language) {
|
||||
await page.context().addCookies([
|
||||
{
|
||||
name: 'language',
|
||||
value: language,
|
||||
url: NEXT_PUBLIC_WEBAPP_URL(),
|
||||
},
|
||||
]);
|
||||
}
|
||||
const lang = isValidLanguageCode(language) ? language : 'en';
|
||||
|
||||
await page.context().addCookies([
|
||||
{
|
||||
name: 'language',
|
||||
value: lang,
|
||||
url: NEXT_PUBLIC_WEBAPP_URL(),
|
||||
},
|
||||
]);
|
||||
|
||||
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encryptedId}`, {
|
||||
waitUntil: 'networkidle',
|
||||
|
||||
@@ -82,7 +82,10 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
||||
const fieldX = pageWidth * (Number(field.positionX) / 100);
|
||||
const fieldY = pageHeight * (Number(field.positionY) / 100);
|
||||
|
||||
const font = await pdf.embedFont(isSignatureField ? fontCaveat : fontNoto);
|
||||
const font = await pdf.embedFont(
|
||||
isSignatureField ? fontCaveat : fontNoto,
|
||||
isSignatureField ? { features: { calt: false } } : undefined,
|
||||
);
|
||||
|
||||
if (field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE) {
|
||||
await pdf.embedFont(fontCaveat);
|
||||
@@ -92,45 +95,89 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
||||
.with(
|
||||
{
|
||||
type: P.union(FieldType.SIGNATURE, FieldType.FREE_SIGNATURE),
|
||||
Signature: { signatureImageAsBase64: P.string },
|
||||
},
|
||||
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 imageHeight = image.height;
|
||||
let imageWidth = image.width;
|
||||
let imageHeight = image.height;
|
||||
|
||||
const scalingFactor = Math.min(fieldWidth / imageWidth, fieldHeight / imageHeight, 1);
|
||||
const scalingFactor = Math.min(fieldWidth / imageWidth, fieldHeight / imageHeight, 1);
|
||||
|
||||
imageWidth = imageWidth * scalingFactor;
|
||||
imageHeight = imageHeight * scalingFactor;
|
||||
imageWidth = imageWidth * scalingFactor;
|
||||
imageHeight = imageHeight * scalingFactor;
|
||||
|
||||
let imageX = fieldX + (fieldWidth - imageWidth) / 2;
|
||||
let imageY = fieldY + (fieldHeight - imageHeight) / 2;
|
||||
let imageX = fieldX + (fieldWidth - imageWidth) / 2;
|
||||
let imageY = fieldY + (fieldHeight - imageHeight) / 2;
|
||||
|
||||
// Invert the Y axis since PDFs use a bottom-left coordinate system
|
||||
imageY = pageHeight - imageY - imageHeight;
|
||||
// Invert the Y axis since PDFs use a bottom-left coordinate system
|
||||
imageY = pageHeight - imageY - imageHeight;
|
||||
|
||||
if (pageRotationInDegrees !== 0) {
|
||||
const adjustedPosition = adjustPositionForRotation(
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
imageX,
|
||||
imageY,
|
||||
pageRotationInDegrees,
|
||||
);
|
||||
if (pageRotationInDegrees !== 0) {
|
||||
const adjustedPosition = adjustPositionForRotation(
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
imageX,
|
||||
imageY,
|
||||
pageRotationInDegrees,
|
||||
);
|
||||
|
||||
imageX = adjustedPosition.xPos;
|
||||
imageY = adjustedPosition.yPos;
|
||||
imageX = adjustedPosition.xPos;
|
||||
imageY = adjustedPosition.yPos;
|
||||
}
|
||||
|
||||
page.drawImage(image, {
|
||||
x: imageX,
|
||||
y: imageY,
|
||||
width: imageWidth,
|
||||
height: imageHeight,
|
||||
rotate: degrees(pageRotationInDegrees),
|
||||
});
|
||||
} else {
|
||||
const signatureText = field.Signature?.typedSignature ?? '';
|
||||
|
||||
const longestLineInTextForWidth = signatureText
|
||||
.split('\n')
|
||||
.sort((a, b) => b.length - a.length)[0];
|
||||
|
||||
let fontSize = maxFontSize;
|
||||
let textWidth = font.widthOfTextAtSize(longestLineInTextForWidth, fontSize);
|
||||
let textHeight = font.heightAtSize(fontSize);
|
||||
|
||||
const scalingFactor = Math.min(fieldWidth / textWidth, fieldHeight / textHeight, 1);
|
||||
fontSize = Math.max(Math.min(fontSize * scalingFactor, maxFontSize), minFontSize);
|
||||
|
||||
textWidth = font.widthOfTextAtSize(longestLineInTextForWidth, fontSize);
|
||||
textHeight = font.heightAtSize(fontSize);
|
||||
|
||||
let textX = fieldX + (fieldWidth - textWidth) / 2;
|
||||
let textY = fieldY + (fieldHeight - textHeight) / 2;
|
||||
|
||||
// Invert the Y axis since PDFs use a bottom-left coordinate system
|
||||
textY = pageHeight - textY - textHeight;
|
||||
|
||||
if (pageRotationInDegrees !== 0) {
|
||||
const adjustedPosition = adjustPositionForRotation(
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
textX,
|
||||
textY,
|
||||
pageRotationInDegrees,
|
||||
);
|
||||
|
||||
textX = adjustedPosition.xPos;
|
||||
textY = adjustedPosition.yPos;
|
||||
}
|
||||
|
||||
page.drawText(signatureText, {
|
||||
x: textX,
|
||||
y: textY,
|
||||
size: fontSize,
|
||||
font,
|
||||
rotate: degrees(pageRotationInDegrees),
|
||||
});
|
||||
}
|
||||
|
||||
page.drawImage(image, {
|
||||
x: imageX,
|
||||
y: imageY,
|
||||
width: imageWidth,
|
||||
height: imageHeight,
|
||||
rotate: degrees(pageRotationInDegrees),
|
||||
});
|
||||
},
|
||||
)
|
||||
.with({ type: FieldType.CHECKBOX }, (field) => {
|
||||
|
||||
@@ -12,6 +12,8 @@ export type UpdateTeamDocumentSettingsOptions = {
|
||||
documentVisibility: DocumentVisibility;
|
||||
documentLanguage: SupportedLanguageCodes;
|
||||
includeSenderDetails: boolean;
|
||||
typedSignatureEnabled: boolean;
|
||||
includeSigningCertificate: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -20,7 +22,13 @@ export const updateTeamDocumentSettings = async ({
|
||||
teamId,
|
||||
settings,
|
||||
}: UpdateTeamDocumentSettingsOptions) => {
|
||||
const { documentVisibility, documentLanguage, includeSenderDetails } = settings;
|
||||
const {
|
||||
documentVisibility,
|
||||
documentLanguage,
|
||||
includeSenderDetails,
|
||||
includeSigningCertificate,
|
||||
typedSignatureEnabled,
|
||||
} = settings;
|
||||
|
||||
const member = await prisma.teamMember.findFirst({
|
||||
where: {
|
||||
@@ -42,11 +50,15 @@ export const updateTeamDocumentSettings = async ({
|
||||
documentVisibility,
|
||||
documentLanguage,
|
||||
includeSenderDetails,
|
||||
typedSignatureEnabled,
|
||||
includeSigningCertificate,
|
||||
},
|
||||
update: {
|
||||
documentVisibility,
|
||||
documentLanguage,
|
||||
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 { prisma } from '@documenso/prisma';
|
||||
import { Prisma } from '@documenso/prisma/client';
|
||||
import type { DocumentVisibility } from '@documenso/prisma/client';
|
||||
|
||||
export type UpdateTeamOptions = {
|
||||
userId: number;
|
||||
@@ -12,8 +11,6 @@ export type UpdateTeamOptions = {
|
||||
data: {
|
||||
name?: string;
|
||||
url?: string;
|
||||
documentVisibility?: DocumentVisibility;
|
||||
includeSenderDetails?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -45,18 +42,6 @@ export const updateTeam = async ({ userId, teamId, data }: UpdateTeamOptions) =>
|
||||
data: {
|
||||
url: data.url,
|
||||
name: data.name,
|
||||
teamGlobalSettings: {
|
||||
upsert: {
|
||||
create: {
|
||||
documentVisibility: data.documentVisibility,
|
||||
includeSenderDetails: data.includeSenderDetails,
|
||||
},
|
||||
update: {
|
||||
documentVisibility: data.documentVisibility,
|
||||
includeSenderDetails: data.includeSenderDetails,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ export type CreateDocumentFromTemplateOptions = {
|
||||
signingOrder?: DocumentSigningOrder;
|
||||
language?: SupportedLanguageCodes;
|
||||
distributionMethod?: DocumentDistributionMethod;
|
||||
typedSignatureEnabled?: boolean;
|
||||
};
|
||||
requestMetadata?: RequestMetadata;
|
||||
};
|
||||
@@ -146,7 +147,7 @@ export const createDocumentFromTemplate = async ({
|
||||
return {
|
||||
templateRecipientId: templateRecipient.id,
|
||||
fields: templateRecipient.Field,
|
||||
name: foundRecipient ? foundRecipient.name ?? '' : templateRecipient.name,
|
||||
name: foundRecipient ? (foundRecipient.name ?? '') : templateRecipient.name,
|
||||
email: foundRecipient ? foundRecipient.email : templateRecipient.email,
|
||||
role: templateRecipient.role,
|
||||
signingOrder: foundRecipient?.signingOrder ?? templateRecipient.signingOrder,
|
||||
@@ -196,6 +197,8 @@ export const createDocumentFromTemplate = async ({
|
||||
override?.language ||
|
||||
template.templateMeta?.language ||
|
||||
template.team?.teamGlobalSettings?.documentLanguage,
|
||||
typedSignatureEnabled:
|
||||
override?.typedSignatureEnabled ?? template.templateMeta?.typedSignatureEnabled,
|
||||
},
|
||||
},
|
||||
Recipient: {
|
||||
|
||||
@@ -135,11 +135,11 @@ msgstr "{prefix} hat das Dokument erstellt"
|
||||
msgid "{prefix} deleted the document"
|
||||
msgstr "{prefix} hat das Dokument gelöscht"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:335
|
||||
#: packages/lib/utils/document-audit-logs.ts:339
|
||||
msgid "{prefix} moved the document to team"
|
||||
msgstr "{prefix} hat das Dokument ins Team verschoben"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:319
|
||||
#: packages/lib/utils/document-audit-logs.ts:323
|
||||
msgid "{prefix} opened the document"
|
||||
msgstr "{prefix} hat das Dokument geöffnet"
|
||||
|
||||
@@ -151,23 +151,27 @@ msgstr "{prefix} hat ein Feld entfernt"
|
||||
msgid "{prefix} removed a recipient"
|
||||
msgstr "{prefix} hat einen Empfänger entfernt"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:365
|
||||
#: packages/lib/utils/document-audit-logs.ts:369
|
||||
msgid "{prefix} resent an email to {0}"
|
||||
msgstr "{prefix} hat eine E-Mail an {0} erneut gesendet"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:366
|
||||
#: packages/lib/utils/document-audit-logs.ts:295
|
||||
msgid "{prefix} restored the document"
|
||||
msgstr ""
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:370
|
||||
msgid "{prefix} sent an email to {0}"
|
||||
msgstr "{prefix} hat eine E-Mail an {0} gesendet"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:331
|
||||
#: packages/lib/utils/document-audit-logs.ts:335
|
||||
msgid "{prefix} sent the document"
|
||||
msgstr "{prefix} hat das Dokument gesendet"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:295
|
||||
#: packages/lib/utils/document-audit-logs.ts:299
|
||||
msgid "{prefix} signed a field"
|
||||
msgstr "{prefix} hat ein Feld unterschrieben"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:299
|
||||
#: packages/lib/utils/document-audit-logs.ts:303
|
||||
msgid "{prefix} unsigned a field"
|
||||
msgstr "{prefix} hat ein Feld ungültig gemacht"
|
||||
|
||||
@@ -179,27 +183,27 @@ msgstr "{prefix} hat ein Feld aktualisiert"
|
||||
msgid "{prefix} updated a recipient"
|
||||
msgstr "{prefix} hat einen Empfänger aktualisiert"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:315
|
||||
#: packages/lib/utils/document-audit-logs.ts:319
|
||||
msgid "{prefix} updated the document"
|
||||
msgstr "{prefix} hat das Dokument aktualisiert"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:307
|
||||
#: packages/lib/utils/document-audit-logs.ts:311
|
||||
msgid "{prefix} updated the document access auth requirements"
|
||||
msgstr "{prefix} hat die Anforderungen an die Dokumentenzugriffsautorisierung aktualisiert"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:327
|
||||
#: packages/lib/utils/document-audit-logs.ts:331
|
||||
msgid "{prefix} updated the document external ID"
|
||||
msgstr "{prefix} hat die externe ID des Dokuments aktualisiert"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:311
|
||||
#: packages/lib/utils/document-audit-logs.ts:315
|
||||
msgid "{prefix} updated the document signing auth requirements"
|
||||
msgstr "{prefix} hat die Authentifizierungsanforderungen für die Dokumentenunterzeichnung aktualisiert"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:323
|
||||
#: packages/lib/utils/document-audit-logs.ts:327
|
||||
msgid "{prefix} updated the document title"
|
||||
msgstr "{prefix} hat den Titel des Dokuments aktualisiert"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:303
|
||||
#: packages/lib/utils/document-audit-logs.ts:307
|
||||
msgid "{prefix} updated the document visibility"
|
||||
msgstr "{prefix} hat die Sichtbarkeit des Dokuments aktualisiert"
|
||||
|
||||
@@ -227,27 +231,27 @@ msgstr "{teamName} hat Sie eingeladen, {action} {documentName}"
|
||||
msgid "{teamName} ownership transfer request"
|
||||
msgstr "Anfrage zur Übertragung des Eigentums von {teamName}"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:343
|
||||
#: packages/lib/utils/document-audit-logs.ts:347
|
||||
msgid "{userName} approved the document"
|
||||
msgstr "{userName} hat das Dokument genehmigt"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:344
|
||||
#: packages/lib/utils/document-audit-logs.ts:348
|
||||
msgid "{userName} CC'd the document"
|
||||
msgstr "{userName} hat das Dokument in CC gesetzt"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:345
|
||||
#: packages/lib/utils/document-audit-logs.ts:349
|
||||
msgid "{userName} completed their task"
|
||||
msgstr "{userName} hat ihre Aufgabe abgeschlossen"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:355
|
||||
#: packages/lib/utils/document-audit-logs.ts:359
|
||||
msgid "{userName} rejected the document"
|
||||
msgstr "{userName} hat das Dokument abgelehnt"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:341
|
||||
#: packages/lib/utils/document-audit-logs.ts:345
|
||||
msgid "{userName} signed the document"
|
||||
msgstr "{userName} hat das Dokument unterschrieben"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:342
|
||||
#: packages/lib/utils/document-audit-logs.ts:346
|
||||
msgid "{userName} viewed the document"
|
||||
msgstr "{userName} hat das Dokument angesehen"
|
||||
|
||||
@@ -444,7 +448,7 @@ msgid "Advanced Options"
|
||||
msgstr "Erweiterte Optionen"
|
||||
|
||||
#: 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"
|
||||
msgstr "Erweiterte Einstellungen"
|
||||
|
||||
@@ -500,11 +504,11 @@ msgstr "Genehmigung"
|
||||
msgid "Before you get started, please confirm your email address by clicking the button below:"
|
||||
msgstr "Bitte bestätige vor dem Start deine E-Mail-Adresse, indem du auf den Button unten klickst:"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:377
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:383
|
||||
msgid "Black"
|
||||
msgstr "Schwarz"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:391
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:397
|
||||
msgid "Blue"
|
||||
msgstr "Blau"
|
||||
|
||||
@@ -562,7 +566,7 @@ msgstr "Checkbox-Werte"
|
||||
msgid "Clear filters"
|
||||
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"
|
||||
msgstr "Unterschrift löschen"
|
||||
|
||||
@@ -590,7 +594,7 @@ msgid "Configure Direct Recipient"
|
||||
msgstr "Direkten Empfänger konfigurieren"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:577
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:410
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:415
|
||||
msgid "Configure the {0} field"
|
||||
msgstr "Konfigurieren Sie das Feld {0}"
|
||||
|
||||
@@ -653,7 +657,7 @@ msgstr "Benutzerdefinierter Text"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:934
|
||||
#: 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"
|
||||
msgstr "Datum"
|
||||
|
||||
@@ -688,17 +692,17 @@ msgstr "Dokument \"{0}\" - Ablehnung Bestätigt"
|
||||
msgid "Document access"
|
||||
msgstr "Dokumentenzugriff"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:306
|
||||
#: packages/lib/utils/document-audit-logs.ts:310
|
||||
msgid "Document access auth updated"
|
||||
msgstr "Die Authentifizierung für den Dokumentenzugriff wurde aktualisiert"
|
||||
|
||||
#: packages/lib/server-only/document/delete-document.ts:246
|
||||
#: packages/lib/server-only/document/delete-document.ts:256
|
||||
#: packages/lib/server-only/document/super-delete-document.ts:98
|
||||
msgid "Document Cancelled"
|
||||
msgstr "Dokument storniert"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:369
|
||||
#: packages/lib/utils/document-audit-logs.ts:370
|
||||
#: packages/lib/utils/document-audit-logs.ts:373
|
||||
#: packages/lib/utils/document-audit-logs.ts:374
|
||||
msgid "Document completed"
|
||||
msgstr "Dokument abgeschlossen"
|
||||
|
||||
@@ -736,15 +740,15 @@ msgstr "Dokument gelöscht!"
|
||||
msgid "Document Distribution Method"
|
||||
msgstr "Verteilungsmethode für Dokumente"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:326
|
||||
#: packages/lib/utils/document-audit-logs.ts:330
|
||||
msgid "Document external ID updated"
|
||||
msgstr "Externe ID des Dokuments aktualisiert"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:334
|
||||
#: packages/lib/utils/document-audit-logs.ts:338
|
||||
msgid "Document moved to team"
|
||||
msgstr "Dokument ins Team verschoben"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:318
|
||||
#: packages/lib/utils/document-audit-logs.ts:322
|
||||
msgid "Document opened"
|
||||
msgstr "Dokument geöffnet"
|
||||
|
||||
@@ -759,23 +763,27 @@ msgstr "Dokument Abgelehnt"
|
||||
#~ msgid "Document Rejection Confirmed"
|
||||
#~ msgstr "Document Rejection Confirmed"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:330
|
||||
#: packages/lib/utils/document-audit-logs.ts:294
|
||||
msgid "Document restored"
|
||||
msgstr ""
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:334
|
||||
msgid "Document sent"
|
||||
msgstr "Dokument gesendet"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:310
|
||||
#: packages/lib/utils/document-audit-logs.ts:314
|
||||
msgid "Document signing auth updated"
|
||||
msgstr "Dokument unterzeichnen Authentifizierung aktualisiert"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:322
|
||||
#: packages/lib/utils/document-audit-logs.ts:326
|
||||
msgid "Document title updated"
|
||||
msgstr "Dokumenttitel aktualisiert"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:314
|
||||
#: packages/lib/utils/document-audit-logs.ts:318
|
||||
msgid "Document updated"
|
||||
msgstr "Dokument aktualisiert"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:302
|
||||
#: packages/lib/utils/document-audit-logs.ts:306
|
||||
msgid "Document visibility updated"
|
||||
msgstr "Sichtbarkeit des Dokuments aktualisiert"
|
||||
|
||||
@@ -793,7 +801,7 @@ msgid "Drag & drop your PDF here."
|
||||
msgstr "Ziehen Sie Ihr PDF hierher."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:1065
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:827
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:860
|
||||
msgid "Dropdown"
|
||||
msgstr "Dropdown"
|
||||
|
||||
@@ -807,7 +815,7 @@ msgstr "Dropdown-Optionen"
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:512
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:519
|
||||
#: 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:478
|
||||
msgid "Email"
|
||||
@@ -821,11 +829,11 @@ msgstr "E-Mail ist erforderlich"
|
||||
msgid "Email Options"
|
||||
msgstr "E-Mail-Optionen"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:363
|
||||
#: packages/lib/utils/document-audit-logs.ts:367
|
||||
msgid "Email resent"
|
||||
msgstr "E-Mail erneut gesendet"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:363
|
||||
#: packages/lib/utils/document-audit-logs.ts:367
|
||||
msgid "Email sent"
|
||||
msgstr "E-Mail gesendet"
|
||||
|
||||
@@ -843,6 +851,7 @@ msgid "Enable signing order"
|
||||
msgstr "Aktiviere die Signaturreihenfolge"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:802
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:597
|
||||
msgid "Enable Typed Signatures"
|
||||
msgstr "Aktivieren Sie getippte Unterschriften"
|
||||
|
||||
@@ -889,11 +898,11 @@ msgstr "Feldbeschriftung"
|
||||
msgid "Field placeholder"
|
||||
msgstr "Feldplatzhalter"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:294
|
||||
#: packages/lib/utils/document-audit-logs.ts:298
|
||||
msgid "Field signed"
|
||||
msgstr "Feld unterschrieben"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:298
|
||||
#: packages/lib/utils/document-audit-logs.ts:302
|
||||
msgid "Field unsigned"
|
||||
msgstr "Feld nicht unterschrieben"
|
||||
|
||||
@@ -930,7 +939,7 @@ msgstr "Globale Empfängerauthentifizierung"
|
||||
msgid "Go Back"
|
||||
msgstr "Zurück"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:398
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:404
|
||||
msgid "Green"
|
||||
msgstr "Grün"
|
||||
|
||||
@@ -1025,7 +1034,7 @@ msgstr "Min"
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:550
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:556
|
||||
#: 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:512
|
||||
msgid "Name"
|
||||
@@ -1044,7 +1053,7 @@ msgid "Needs to view"
|
||||
msgstr "Muss sehen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:693
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:511
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:516
|
||||
msgid "No recipient matching this description was found."
|
||||
msgstr "Kein passender Empfänger mit dieser Beschreibung gefunden."
|
||||
|
||||
@@ -1053,7 +1062,7 @@ msgid "No recipients"
|
||||
msgstr "Keine Empfänger"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:708
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:526
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:531
|
||||
msgid "No recipients with this role"
|
||||
msgstr "Keine Empfänger mit dieser Rolle"
|
||||
|
||||
@@ -1083,7 +1092,7 @@ msgstr "Keine"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:986
|
||||
#: 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"
|
||||
msgstr "Nummer"
|
||||
|
||||
@@ -1175,7 +1184,6 @@ msgid "Please try again or contact our support."
|
||||
msgstr "Bitte versuchen Sie es erneut oder kontaktieren Sie unseren Support."
|
||||
|
||||
#: packages/ui/primitives/document-flow/types.ts:57
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:775
|
||||
msgid "Radio"
|
||||
msgstr "Radio"
|
||||
|
||||
@@ -1199,8 +1207,8 @@ msgstr "Grund für die Ablehnung: {rejectionReason}"
|
||||
msgid "Receives copy"
|
||||
msgstr "Erhält Kopie"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:338
|
||||
#: packages/lib/utils/document-audit-logs.ts:353
|
||||
#: packages/lib/utils/document-audit-logs.ts:342
|
||||
#: packages/lib/utils/document-audit-logs.ts:357
|
||||
msgid "Recipient"
|
||||
msgstr "Empfänger"
|
||||
|
||||
@@ -1218,7 +1226,7 @@ msgstr "E-Mail des entfernten Empfängers"
|
||||
msgid "Recipient signing request email"
|
||||
msgstr "E-Mail zur Unterzeichnungsanfrage des Empfängers"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:384
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:390
|
||||
msgid "Red"
|
||||
msgstr "Rot"
|
||||
|
||||
@@ -1287,7 +1295,7 @@ msgstr "Zeilen pro Seite"
|
||||
msgid "Save"
|
||||
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"
|
||||
msgstr "Vorlage speichern"
|
||||
|
||||
@@ -1380,7 +1388,7 @@ msgstr "Anmelden"
|
||||
#: packages/ui/primitives/document-flow/add-signature.tsx:323
|
||||
#: packages/ui/primitives/document-flow/field-icon.tsx:52
|
||||
#: packages/ui/primitives/document-flow/types.ts:49
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:593
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:625
|
||||
msgid "Signature"
|
||||
msgstr "Unterschrift"
|
||||
|
||||
@@ -1465,7 +1473,7 @@ msgstr "Vorlagentitel"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:960
|
||||
#: 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"
|
||||
msgstr "Text"
|
||||
|
||||
@@ -1629,7 +1637,7 @@ msgid "Title"
|
||||
msgstr "Titel"
|
||||
|
||||
#: 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."
|
||||
msgstr "Um fortzufahren, legen Sie bitte mindestens einen Wert für das Feld {0} fest."
|
||||
|
||||
@@ -1814,4 +1822,3 @@ msgstr "Dein Passwort wurde aktualisiert."
|
||||
#: packages/email/templates/team-delete.tsx:32
|
||||
msgid "Your team has been deleted"
|
||||
msgstr "Dein Team wurde gelöscht"
|
||||
|
||||
|
||||
@@ -602,4 +602,3 @@ msgstr "Sie können Documenso kostenlos selbst hosten oder unsere sofort einsatz
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:272
|
||||
msgid "Your browser does not support the video tag."
|
||||
msgstr "Ihr Browser unterstützt das Video-Tag nicht."
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -130,11 +130,11 @@ msgstr "{prefix} created the document"
|
||||
msgid "{prefix} deleted the document"
|
||||
msgstr "{prefix} deleted the document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:335
|
||||
#: packages/lib/utils/document-audit-logs.ts:339
|
||||
msgid "{prefix} moved the document to team"
|
||||
msgstr "{prefix} moved the document to team"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:319
|
||||
#: packages/lib/utils/document-audit-logs.ts:323
|
||||
msgid "{prefix} opened the document"
|
||||
msgstr "{prefix} opened the document"
|
||||
|
||||
@@ -146,23 +146,27 @@ msgstr "{prefix} removed a field"
|
||||
msgid "{prefix} removed a recipient"
|
||||
msgstr "{prefix} removed a recipient"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:365
|
||||
#: packages/lib/utils/document-audit-logs.ts:369
|
||||
msgid "{prefix} resent an email to {0}"
|
||||
msgstr "{prefix} resent an email to {0}"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:366
|
||||
#: packages/lib/utils/document-audit-logs.ts:295
|
||||
msgid "{prefix} restored the document"
|
||||
msgstr "{prefix} restored the document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:370
|
||||
msgid "{prefix} sent an email to {0}"
|
||||
msgstr "{prefix} sent an email to {0}"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:331
|
||||
#: packages/lib/utils/document-audit-logs.ts:335
|
||||
msgid "{prefix} sent the document"
|
||||
msgstr "{prefix} sent the document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:295
|
||||
#: packages/lib/utils/document-audit-logs.ts:299
|
||||
msgid "{prefix} signed a field"
|
||||
msgstr "{prefix} signed a field"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:299
|
||||
#: packages/lib/utils/document-audit-logs.ts:303
|
||||
msgid "{prefix} unsigned a field"
|
||||
msgstr "{prefix} unsigned a field"
|
||||
|
||||
@@ -174,27 +178,27 @@ msgstr "{prefix} updated a field"
|
||||
msgid "{prefix} updated a recipient"
|
||||
msgstr "{prefix} updated a recipient"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:315
|
||||
#: packages/lib/utils/document-audit-logs.ts:319
|
||||
msgid "{prefix} updated the document"
|
||||
msgstr "{prefix} updated the document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:307
|
||||
#: packages/lib/utils/document-audit-logs.ts:311
|
||||
msgid "{prefix} updated the document access auth requirements"
|
||||
msgstr "{prefix} updated the document access auth requirements"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:327
|
||||
#: packages/lib/utils/document-audit-logs.ts:331
|
||||
msgid "{prefix} updated the document external ID"
|
||||
msgstr "{prefix} updated the document external ID"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:311
|
||||
#: packages/lib/utils/document-audit-logs.ts:315
|
||||
msgid "{prefix} updated the document signing auth requirements"
|
||||
msgstr "{prefix} updated the document signing auth requirements"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:323
|
||||
#: packages/lib/utils/document-audit-logs.ts:327
|
||||
msgid "{prefix} updated the document title"
|
||||
msgstr "{prefix} updated the document title"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:303
|
||||
#: packages/lib/utils/document-audit-logs.ts:307
|
||||
msgid "{prefix} updated the document visibility"
|
||||
msgstr "{prefix} updated the document visibility"
|
||||
|
||||
@@ -222,27 +226,27 @@ msgstr "{teamName} has invited you to {action} {documentName}"
|
||||
msgid "{teamName} ownership transfer request"
|
||||
msgstr "{teamName} ownership transfer request"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:343
|
||||
#: packages/lib/utils/document-audit-logs.ts:347
|
||||
msgid "{userName} approved the document"
|
||||
msgstr "{userName} approved the document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:344
|
||||
#: packages/lib/utils/document-audit-logs.ts:348
|
||||
msgid "{userName} CC'd the document"
|
||||
msgstr "{userName} CC'd the document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:345
|
||||
#: packages/lib/utils/document-audit-logs.ts:349
|
||||
msgid "{userName} completed their task"
|
||||
msgstr "{userName} completed their task"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:355
|
||||
#: packages/lib/utils/document-audit-logs.ts:359
|
||||
msgid "{userName} rejected the document"
|
||||
msgstr "{userName} rejected the document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:341
|
||||
#: packages/lib/utils/document-audit-logs.ts:345
|
||||
msgid "{userName} signed the document"
|
||||
msgstr "{userName} signed the document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:342
|
||||
#: packages/lib/utils/document-audit-logs.ts:346
|
||||
msgid "{userName} viewed the document"
|
||||
msgstr "{userName} viewed the document"
|
||||
|
||||
@@ -439,7 +443,7 @@ msgid "Advanced Options"
|
||||
msgstr "Advanced Options"
|
||||
|
||||
#: 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"
|
||||
msgstr "Advanced settings"
|
||||
|
||||
@@ -495,11 +499,11 @@ msgstr "Approving"
|
||||
msgid "Before you get started, please confirm your email address by clicking the button below:"
|
||||
msgstr "Before you get started, please confirm your email address by clicking the button below:"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:377
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:383
|
||||
msgid "Black"
|
||||
msgstr "Black"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:391
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:397
|
||||
msgid "Blue"
|
||||
msgstr "Blue"
|
||||
|
||||
@@ -557,7 +561,7 @@ msgstr "Checkbox values"
|
||||
msgid "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"
|
||||
msgstr "Clear Signature"
|
||||
|
||||
@@ -585,7 +589,7 @@ msgid "Configure Direct Recipient"
|
||||
msgstr "Configure Direct Recipient"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:577
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:410
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:415
|
||||
msgid "Configure the {0} field"
|
||||
msgstr "Configure the {0} field"
|
||||
|
||||
@@ -648,7 +652,7 @@ msgstr "Custom Text"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:934
|
||||
#: 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"
|
||||
msgstr "Date"
|
||||
|
||||
@@ -683,17 +687,17 @@ msgstr "Document \"{0}\" - Rejection Confirmed"
|
||||
msgid "Document access"
|
||||
msgstr "Document access"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:306
|
||||
#: packages/lib/utils/document-audit-logs.ts:310
|
||||
msgid "Document access auth updated"
|
||||
msgstr "Document access auth updated"
|
||||
|
||||
#: packages/lib/server-only/document/delete-document.ts:246
|
||||
#: packages/lib/server-only/document/delete-document.ts:256
|
||||
#: packages/lib/server-only/document/super-delete-document.ts:98
|
||||
msgid "Document Cancelled"
|
||||
msgstr "Document Cancelled"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:369
|
||||
#: packages/lib/utils/document-audit-logs.ts:370
|
||||
#: packages/lib/utils/document-audit-logs.ts:373
|
||||
#: packages/lib/utils/document-audit-logs.ts:374
|
||||
msgid "Document completed"
|
||||
msgstr "Document completed"
|
||||
|
||||
@@ -731,15 +735,15 @@ msgstr "Document Deleted!"
|
||||
msgid "Document Distribution Method"
|
||||
msgstr "Document Distribution Method"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:326
|
||||
#: packages/lib/utils/document-audit-logs.ts:330
|
||||
msgid "Document external ID updated"
|
||||
msgstr "Document external ID updated"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:334
|
||||
#: packages/lib/utils/document-audit-logs.ts:338
|
||||
msgid "Document moved to team"
|
||||
msgstr "Document moved to team"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:318
|
||||
#: packages/lib/utils/document-audit-logs.ts:322
|
||||
msgid "Document opened"
|
||||
msgstr "Document opened"
|
||||
|
||||
@@ -754,23 +758,27 @@ msgstr "Document Rejected"
|
||||
#~ msgid "Document Rejection Confirmed"
|
||||
#~ msgstr "Document Rejection Confirmed"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:330
|
||||
#: packages/lib/utils/document-audit-logs.ts:294
|
||||
msgid "Document restored"
|
||||
msgstr "Document restored"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:334
|
||||
msgid "Document sent"
|
||||
msgstr "Document sent"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:310
|
||||
#: packages/lib/utils/document-audit-logs.ts:314
|
||||
msgid "Document signing auth updated"
|
||||
msgstr "Document signing auth updated"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:322
|
||||
#: packages/lib/utils/document-audit-logs.ts:326
|
||||
msgid "Document title updated"
|
||||
msgstr "Document title updated"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:314
|
||||
#: packages/lib/utils/document-audit-logs.ts:318
|
||||
msgid "Document updated"
|
||||
msgstr "Document updated"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:302
|
||||
#: packages/lib/utils/document-audit-logs.ts:306
|
||||
msgid "Document visibility updated"
|
||||
msgstr "Document visibility updated"
|
||||
|
||||
@@ -788,7 +796,7 @@ msgid "Drag & drop your PDF here."
|
||||
msgstr "Drag & drop your PDF here."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:1065
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:827
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:860
|
||||
msgid "Dropdown"
|
||||
msgstr "Dropdown"
|
||||
|
||||
@@ -802,7 +810,7 @@ msgstr "Dropdown options"
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:512
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:519
|
||||
#: 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:478
|
||||
msgid "Email"
|
||||
@@ -816,11 +824,11 @@ msgstr "Email is required"
|
||||
msgid "Email Options"
|
||||
msgstr "Email Options"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:363
|
||||
#: packages/lib/utils/document-audit-logs.ts:367
|
||||
msgid "Email resent"
|
||||
msgstr "Email resent"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:363
|
||||
#: packages/lib/utils/document-audit-logs.ts:367
|
||||
msgid "Email sent"
|
||||
msgstr "Email sent"
|
||||
|
||||
@@ -838,6 +846,7 @@ msgid "Enable signing order"
|
||||
msgstr "Enable signing order"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:802
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:597
|
||||
msgid "Enable Typed Signatures"
|
||||
msgstr "Enable Typed Signatures"
|
||||
|
||||
@@ -884,11 +893,11 @@ msgstr "Field label"
|
||||
msgid "Field placeholder"
|
||||
msgstr "Field placeholder"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:294
|
||||
#: packages/lib/utils/document-audit-logs.ts:298
|
||||
msgid "Field signed"
|
||||
msgstr "Field signed"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:298
|
||||
#: packages/lib/utils/document-audit-logs.ts:302
|
||||
msgid "Field unsigned"
|
||||
msgstr "Field unsigned"
|
||||
|
||||
@@ -925,7 +934,7 @@ msgstr "Global recipient action authentication"
|
||||
msgid "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"
|
||||
msgstr "Green"
|
||||
|
||||
@@ -1020,7 +1029,7 @@ msgstr "Min"
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:550
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:556
|
||||
#: 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:512
|
||||
msgid "Name"
|
||||
@@ -1039,7 +1048,7 @@ msgid "Needs to view"
|
||||
msgstr "Needs to view"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:693
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:511
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:516
|
||||
msgid "No recipient matching this description was found."
|
||||
msgstr "No recipient matching this description was found."
|
||||
|
||||
@@ -1048,7 +1057,7 @@ msgid "No recipients"
|
||||
msgstr "No recipients"
|
||||
|
||||
#: 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"
|
||||
msgstr "No recipients with this role"
|
||||
|
||||
@@ -1078,7 +1087,7 @@ msgstr "None"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:986
|
||||
#: 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"
|
||||
msgstr "Number"
|
||||
|
||||
@@ -1170,7 +1179,6 @@ msgid "Please try again or contact our support."
|
||||
msgstr "Please try again or contact our support."
|
||||
|
||||
#: packages/ui/primitives/document-flow/types.ts:57
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:775
|
||||
msgid "Radio"
|
||||
msgstr "Radio"
|
||||
|
||||
@@ -1194,8 +1202,8 @@ msgstr "Reason for rejection: {rejectionReason}"
|
||||
msgid "Receives copy"
|
||||
msgstr "Receives copy"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:338
|
||||
#: packages/lib/utils/document-audit-logs.ts:353
|
||||
#: packages/lib/utils/document-audit-logs.ts:342
|
||||
#: packages/lib/utils/document-audit-logs.ts:357
|
||||
msgid "Recipient"
|
||||
msgstr "Recipient"
|
||||
|
||||
@@ -1213,7 +1221,7 @@ msgstr "Recipient removed email"
|
||||
msgid "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"
|
||||
msgstr "Red"
|
||||
|
||||
@@ -1282,7 +1290,7 @@ msgstr "Rows per page"
|
||||
msgid "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"
|
||||
msgstr "Save Template"
|
||||
|
||||
@@ -1375,7 +1383,7 @@ msgstr "Sign In"
|
||||
#: packages/ui/primitives/document-flow/add-signature.tsx:323
|
||||
#: packages/ui/primitives/document-flow/field-icon.tsx:52
|
||||
#: packages/ui/primitives/document-flow/types.ts:49
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:593
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:625
|
||||
msgid "Signature"
|
||||
msgstr "Signature"
|
||||
|
||||
@@ -1460,7 +1468,7 @@ msgstr "Template title"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:960
|
||||
#: 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"
|
||||
msgstr "Text"
|
||||
|
||||
@@ -1624,7 +1632,7 @@ msgid "Title"
|
||||
msgstr "Title"
|
||||
|
||||
#: 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."
|
||||
msgstr "To proceed further, please set at least one value for the {0} field."
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -135,11 +135,11 @@ msgstr "{prefix} creó el documento"
|
||||
msgid "{prefix} deleted the document"
|
||||
msgstr "{prefix} eliminó el documento"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:335
|
||||
#: packages/lib/utils/document-audit-logs.ts:339
|
||||
msgid "{prefix} moved the document to team"
|
||||
msgstr "{prefix} movió el documento al equipo"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:319
|
||||
#: packages/lib/utils/document-audit-logs.ts:323
|
||||
msgid "{prefix} opened the document"
|
||||
msgstr "{prefix} abrió el documento"
|
||||
|
||||
@@ -151,23 +151,27 @@ msgstr "{prefix} eliminó un campo"
|
||||
msgid "{prefix} removed a recipient"
|
||||
msgstr "{prefix} eliminó un destinatario"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:365
|
||||
#: packages/lib/utils/document-audit-logs.ts:369
|
||||
msgid "{prefix} resent an email to {0}"
|
||||
msgstr "{prefix} reenviaron un correo electrónico a {0}"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:366
|
||||
#: packages/lib/utils/document-audit-logs.ts:295
|
||||
msgid "{prefix} restored the document"
|
||||
msgstr ""
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:370
|
||||
msgid "{prefix} sent an email to {0}"
|
||||
msgstr "{prefix} envió un correo electrónico a {0}"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:331
|
||||
#: packages/lib/utils/document-audit-logs.ts:335
|
||||
msgid "{prefix} sent the document"
|
||||
msgstr "{prefix} envió el documento"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:295
|
||||
#: packages/lib/utils/document-audit-logs.ts:299
|
||||
msgid "{prefix} signed a field"
|
||||
msgstr "{prefix} firmó un campo"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:299
|
||||
#: packages/lib/utils/document-audit-logs.ts:303
|
||||
msgid "{prefix} unsigned a field"
|
||||
msgstr "{prefix} no firmó un campo"
|
||||
|
||||
@@ -179,27 +183,27 @@ msgstr "{prefix} actualizó un campo"
|
||||
msgid "{prefix} updated a recipient"
|
||||
msgstr "{prefix} actualizó un destinatario"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:315
|
||||
#: packages/lib/utils/document-audit-logs.ts:319
|
||||
msgid "{prefix} updated the document"
|
||||
msgstr "{prefix} actualizó el documento"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:307
|
||||
#: packages/lib/utils/document-audit-logs.ts:311
|
||||
msgid "{prefix} updated the document access auth requirements"
|
||||
msgstr "{prefix} actualizó los requisitos de autorización de acceso al documento"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:327
|
||||
#: packages/lib/utils/document-audit-logs.ts:331
|
||||
msgid "{prefix} updated the document external ID"
|
||||
msgstr "{prefix} actualizó el ID externo del documento"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:311
|
||||
#: packages/lib/utils/document-audit-logs.ts:315
|
||||
msgid "{prefix} updated the document signing auth requirements"
|
||||
msgstr "{prefix} actualizó los requisitos de autenticación para la firma del documento"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:323
|
||||
#: packages/lib/utils/document-audit-logs.ts:327
|
||||
msgid "{prefix} updated the document title"
|
||||
msgstr "{prefix} actualizó el título del documento"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:303
|
||||
#: packages/lib/utils/document-audit-logs.ts:307
|
||||
msgid "{prefix} updated the document visibility"
|
||||
msgstr "{prefix} actualizó la visibilidad del documento"
|
||||
|
||||
@@ -227,27 +231,27 @@ msgstr "{teamName} te ha invitado a {action} {documentName}"
|
||||
msgid "{teamName} ownership transfer request"
|
||||
msgstr "solicitud de transferencia de propiedad de {teamName}"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:343
|
||||
#: packages/lib/utils/document-audit-logs.ts:347
|
||||
msgid "{userName} approved the document"
|
||||
msgstr "{userName} aprobó el documento"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:344
|
||||
#: packages/lib/utils/document-audit-logs.ts:348
|
||||
msgid "{userName} CC'd the document"
|
||||
msgstr "{userName} envió una copia del documento"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:345
|
||||
#: packages/lib/utils/document-audit-logs.ts:349
|
||||
msgid "{userName} completed their task"
|
||||
msgstr "{userName} completó su tarea"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:355
|
||||
#: packages/lib/utils/document-audit-logs.ts:359
|
||||
msgid "{userName} rejected the document"
|
||||
msgstr "{userName} rechazó el documento"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:341
|
||||
#: packages/lib/utils/document-audit-logs.ts:345
|
||||
msgid "{userName} signed the document"
|
||||
msgstr "{userName} firmó el documento"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:342
|
||||
#: packages/lib/utils/document-audit-logs.ts:346
|
||||
msgid "{userName} viewed the document"
|
||||
msgstr "{userName} vio el documento"
|
||||
|
||||
@@ -444,7 +448,7 @@ msgid "Advanced Options"
|
||||
msgstr "Opciones avanzadas"
|
||||
|
||||
#: 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"
|
||||
msgstr "Configuraciones avanzadas"
|
||||
|
||||
@@ -500,11 +504,11 @@ msgstr "Aprobando"
|
||||
msgid "Before you get started, please confirm your email address by clicking the button below:"
|
||||
msgstr "Antes de comenzar, por favor confirma tu dirección de correo electrónico haciendo clic en el botón de abajo:"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:377
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:383
|
||||
msgid "Black"
|
||||
msgstr "Negro"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:391
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:397
|
||||
msgid "Blue"
|
||||
msgstr "Azul"
|
||||
|
||||
@@ -562,7 +566,7 @@ msgstr "Valores de Checkbox"
|
||||
msgid "Clear filters"
|
||||
msgstr "Limpiar filtros"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:411
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:417
|
||||
msgid "Clear Signature"
|
||||
msgstr "Limpiar firma"
|
||||
|
||||
@@ -590,7 +594,7 @@ msgid "Configure Direct Recipient"
|
||||
msgstr "Configurar destinatario directo"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:577
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:410
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:415
|
||||
msgid "Configure the {0} field"
|
||||
msgstr "Configurar el campo {0}"
|
||||
|
||||
@@ -653,7 +657,7 @@ msgstr "Texto personalizado"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:934
|
||||
#: 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"
|
||||
msgstr "Fecha"
|
||||
|
||||
@@ -688,17 +692,17 @@ msgstr "Documento \"{0}\" - Rechazo confirmado"
|
||||
msgid "Document access"
|
||||
msgstr "Acceso al documento"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:306
|
||||
#: packages/lib/utils/document-audit-logs.ts:310
|
||||
msgid "Document access auth updated"
|
||||
msgstr "Se actualizó la autenticación de acceso al documento"
|
||||
|
||||
#: packages/lib/server-only/document/delete-document.ts:246
|
||||
#: packages/lib/server-only/document/delete-document.ts:256
|
||||
#: packages/lib/server-only/document/super-delete-document.ts:98
|
||||
msgid "Document Cancelled"
|
||||
msgstr "Documento cancelado"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:369
|
||||
#: packages/lib/utils/document-audit-logs.ts:370
|
||||
#: packages/lib/utils/document-audit-logs.ts:373
|
||||
#: packages/lib/utils/document-audit-logs.ts:374
|
||||
msgid "Document completed"
|
||||
msgstr "Documento completado"
|
||||
|
||||
@@ -736,15 +740,15 @@ msgstr "¡Documento eliminado!"
|
||||
msgid "Document Distribution Method"
|
||||
msgstr "Método de distribución de documentos"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:326
|
||||
#: packages/lib/utils/document-audit-logs.ts:330
|
||||
msgid "Document external ID updated"
|
||||
msgstr "ID externo del documento actualizado"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:334
|
||||
#: packages/lib/utils/document-audit-logs.ts:338
|
||||
msgid "Document moved to team"
|
||||
msgstr "Documento movido al equipo"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:318
|
||||
#: packages/lib/utils/document-audit-logs.ts:322
|
||||
msgid "Document opened"
|
||||
msgstr "Documento abierto"
|
||||
|
||||
@@ -759,23 +763,27 @@ msgstr "Documento Rechazado"
|
||||
#~ msgid "Document Rejection Confirmed"
|
||||
#~ msgstr "Document Rejection Confirmed"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:330
|
||||
#: packages/lib/utils/document-audit-logs.ts:294
|
||||
msgid "Document restored"
|
||||
msgstr ""
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:334
|
||||
msgid "Document sent"
|
||||
msgstr "Documento enviado"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:310
|
||||
#: packages/lib/utils/document-audit-logs.ts:314
|
||||
msgid "Document signing auth updated"
|
||||
msgstr "Se actualizó la autenticación de firma del documento"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:322
|
||||
#: packages/lib/utils/document-audit-logs.ts:326
|
||||
msgid "Document title updated"
|
||||
msgstr "Título del documento actualizado"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:314
|
||||
#: packages/lib/utils/document-audit-logs.ts:318
|
||||
msgid "Document updated"
|
||||
msgstr "Documento actualizado"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:302
|
||||
#: packages/lib/utils/document-audit-logs.ts:306
|
||||
msgid "Document visibility updated"
|
||||
msgstr "Visibilidad del documento actualizada"
|
||||
|
||||
@@ -793,7 +801,7 @@ msgid "Drag & drop your PDF here."
|
||||
msgstr "Arrastre y suelte su PDF aquí."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:1065
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:827
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:860
|
||||
msgid "Dropdown"
|
||||
msgstr "Menú desplegable"
|
||||
|
||||
@@ -807,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:519
|
||||
#: 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:478
|
||||
msgid "Email"
|
||||
@@ -821,11 +829,11 @@ msgstr "Se requiere email"
|
||||
msgid "Email Options"
|
||||
msgstr "Opciones de correo electrónico"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:363
|
||||
#: packages/lib/utils/document-audit-logs.ts:367
|
||||
msgid "Email resent"
|
||||
msgstr "Correo electrónico reeenviado"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:363
|
||||
#: packages/lib/utils/document-audit-logs.ts:367
|
||||
msgid "Email sent"
|
||||
msgstr "Correo electrónico enviado"
|
||||
|
||||
@@ -843,6 +851,7 @@ msgid "Enable signing order"
|
||||
msgstr "Habilitar orden de firma"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:802
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:597
|
||||
msgid "Enable Typed Signatures"
|
||||
msgstr "Habilitar firmas escritas"
|
||||
|
||||
@@ -889,11 +898,11 @@ msgstr "Etiqueta de campo"
|
||||
msgid "Field placeholder"
|
||||
msgstr "Marcador de posición de campo"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:294
|
||||
#: packages/lib/utils/document-audit-logs.ts:298
|
||||
msgid "Field signed"
|
||||
msgstr "Campo firmado"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:298
|
||||
#: packages/lib/utils/document-audit-logs.ts:302
|
||||
msgid "Field unsigned"
|
||||
msgstr "Campo no firmado"
|
||||
|
||||
@@ -930,7 +939,7 @@ msgstr "Autenticación de acción de destinatario global"
|
||||
msgid "Go Back"
|
||||
msgstr "Regresar"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:398
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:404
|
||||
msgid "Green"
|
||||
msgstr "Verde"
|
||||
|
||||
@@ -1025,7 +1034,7 @@ msgstr "Mín"
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:550
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:556
|
||||
#: 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:512
|
||||
msgid "Name"
|
||||
@@ -1044,7 +1053,7 @@ msgid "Needs to view"
|
||||
msgstr "Necesita ver"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:693
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:511
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:516
|
||||
msgid "No recipient matching this description was found."
|
||||
msgstr "No se encontró ningún destinatario que coincidiera con esta descripción."
|
||||
|
||||
@@ -1053,7 +1062,7 @@ msgid "No recipients"
|
||||
msgstr "Sin destinatarios"
|
||||
|
||||
#: 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"
|
||||
msgstr "No hay destinatarios con este rol"
|
||||
|
||||
@@ -1083,7 +1092,7 @@ msgstr "Ninguno"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:986
|
||||
#: 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"
|
||||
msgstr "Número"
|
||||
|
||||
@@ -1175,7 +1184,6 @@ msgid "Please try again or contact our support."
|
||||
msgstr "Por favor, inténtalo de nuevo o contacta a nuestro soporte."
|
||||
|
||||
#: packages/ui/primitives/document-flow/types.ts:57
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:775
|
||||
msgid "Radio"
|
||||
msgstr "Radio"
|
||||
|
||||
@@ -1199,8 +1207,8 @@ msgstr "Razón del rechazo: {rejectionReason}"
|
||||
msgid "Receives copy"
|
||||
msgstr "Recibe copia"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:338
|
||||
#: packages/lib/utils/document-audit-logs.ts:353
|
||||
#: packages/lib/utils/document-audit-logs.ts:342
|
||||
#: packages/lib/utils/document-audit-logs.ts:357
|
||||
msgid "Recipient"
|
||||
msgstr "Destinatario"
|
||||
|
||||
@@ -1218,7 +1226,7 @@ msgstr "Correo electrónico de destinatario eliminado"
|
||||
msgid "Recipient signing request email"
|
||||
msgstr "Correo electrónico de solicitud de firma de destinatario"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:384
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:390
|
||||
msgid "Red"
|
||||
msgstr "Rojo"
|
||||
|
||||
@@ -1287,7 +1295,7 @@ msgstr "Filas por página"
|
||||
msgid "Save"
|
||||
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"
|
||||
msgstr "Guardar plantilla"
|
||||
|
||||
@@ -1380,7 +1388,7 @@ msgstr "Iniciar sesión"
|
||||
#: packages/ui/primitives/document-flow/add-signature.tsx:323
|
||||
#: packages/ui/primitives/document-flow/field-icon.tsx:52
|
||||
#: packages/ui/primitives/document-flow/types.ts:49
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:593
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:625
|
||||
msgid "Signature"
|
||||
msgstr "Firma"
|
||||
|
||||
@@ -1465,7 +1473,7 @@ msgstr "Título de plantilla"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:960
|
||||
#: 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"
|
||||
msgstr "Texto"
|
||||
|
||||
@@ -1629,7 +1637,7 @@ msgid "Title"
|
||||
msgstr "Título"
|
||||
|
||||
#: 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."
|
||||
msgstr "Para continuar, por favor establezca al menos un valor para el campo {0}."
|
||||
|
||||
@@ -1814,4 +1822,3 @@ msgstr "Tu contraseña ha sido actualizada."
|
||||
#: packages/email/templates/team-delete.tsx:32
|
||||
msgid "Your team has been deleted"
|
||||
msgstr "Tu equipo ha sido eliminado"
|
||||
|
||||
|
||||
@@ -602,4 +602,3 @@ msgstr "Puedes autoalojar Documenso de forma gratuita o usar nuestra versión al
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:272
|
||||
msgid "Your browser does not support the video tag."
|
||||
msgstr "Tu navegador no soporta la etiqueta de video."
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -135,11 +135,11 @@ msgstr "{prefix} a créé le document"
|
||||
msgid "{prefix} deleted the document"
|
||||
msgstr "{prefix} a supprimé le document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:335
|
||||
#: packages/lib/utils/document-audit-logs.ts:339
|
||||
msgid "{prefix} moved the document to team"
|
||||
msgstr "{prefix} a déplacé le document vers l'équipe"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:319
|
||||
#: packages/lib/utils/document-audit-logs.ts:323
|
||||
msgid "{prefix} opened the document"
|
||||
msgstr "{prefix} a ouvert le document"
|
||||
|
||||
@@ -151,23 +151,27 @@ msgstr "{prefix} a supprimé un champ"
|
||||
msgid "{prefix} removed a recipient"
|
||||
msgstr "{prefix} a supprimé un destinataire"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:365
|
||||
#: packages/lib/utils/document-audit-logs.ts:369
|
||||
msgid "{prefix} resent an email to {0}"
|
||||
msgstr "{prefix} a renvoyé un e-mail à {0}"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:366
|
||||
#: packages/lib/utils/document-audit-logs.ts:295
|
||||
msgid "{prefix} restored the document"
|
||||
msgstr ""
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:370
|
||||
msgid "{prefix} sent an email to {0}"
|
||||
msgstr "{prefix} a envoyé un email à {0}"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:331
|
||||
#: packages/lib/utils/document-audit-logs.ts:335
|
||||
msgid "{prefix} sent the document"
|
||||
msgstr "{prefix} a envoyé le document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:295
|
||||
#: packages/lib/utils/document-audit-logs.ts:299
|
||||
msgid "{prefix} signed a field"
|
||||
msgstr "{prefix} a signé un champ"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:299
|
||||
#: packages/lib/utils/document-audit-logs.ts:303
|
||||
msgid "{prefix} unsigned a field"
|
||||
msgstr "{prefix} n'a pas signé un champ"
|
||||
|
||||
@@ -179,27 +183,27 @@ msgstr "{prefix} a mis à jour un champ"
|
||||
msgid "{prefix} updated a recipient"
|
||||
msgstr "{prefix} a mis à jour un destinataire"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:315
|
||||
#: packages/lib/utils/document-audit-logs.ts:319
|
||||
msgid "{prefix} updated the document"
|
||||
msgstr "{prefix} a mis à jour le document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:307
|
||||
#: packages/lib/utils/document-audit-logs.ts:311
|
||||
msgid "{prefix} updated the document access auth requirements"
|
||||
msgstr "{prefix} a mis à jour les exigences d'authentification d'accès au document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:327
|
||||
#: packages/lib/utils/document-audit-logs.ts:331
|
||||
msgid "{prefix} updated the document external ID"
|
||||
msgstr "{prefix} a mis à jour l'ID externe du document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:311
|
||||
#: packages/lib/utils/document-audit-logs.ts:315
|
||||
msgid "{prefix} updated the document signing auth requirements"
|
||||
msgstr "{prefix} a mis à jour les exigences d'authentification pour la signature du document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:323
|
||||
#: packages/lib/utils/document-audit-logs.ts:327
|
||||
msgid "{prefix} updated the document title"
|
||||
msgstr "{prefix} a mis à jour le titre du document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:303
|
||||
#: packages/lib/utils/document-audit-logs.ts:307
|
||||
msgid "{prefix} updated the document visibility"
|
||||
msgstr "{prefix} a mis à jour la visibilité du document"
|
||||
|
||||
@@ -227,27 +231,27 @@ msgstr "{teamName} vous a invité à {action} {documentName}"
|
||||
msgid "{teamName} ownership transfer request"
|
||||
msgstr "Demande de transfert de propriété de {teamName}"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:343
|
||||
#: packages/lib/utils/document-audit-logs.ts:347
|
||||
msgid "{userName} approved the document"
|
||||
msgstr "{userName} a approuvé le document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:344
|
||||
#: packages/lib/utils/document-audit-logs.ts:348
|
||||
msgid "{userName} CC'd the document"
|
||||
msgstr "{userName} a mis en copie le document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:345
|
||||
#: packages/lib/utils/document-audit-logs.ts:349
|
||||
msgid "{userName} completed their task"
|
||||
msgstr "{userName} a complété sa tâche"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:355
|
||||
#: packages/lib/utils/document-audit-logs.ts:359
|
||||
msgid "{userName} rejected the document"
|
||||
msgstr "{userName} a rejeté le document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:341
|
||||
#: packages/lib/utils/document-audit-logs.ts:345
|
||||
msgid "{userName} signed the document"
|
||||
msgstr "{userName} a signé le document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:342
|
||||
#: packages/lib/utils/document-audit-logs.ts:346
|
||||
msgid "{userName} viewed the document"
|
||||
msgstr "{userName} a consulté le document"
|
||||
|
||||
@@ -444,7 +448,7 @@ msgid "Advanced Options"
|
||||
msgstr "Options avancées"
|
||||
|
||||
#: 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"
|
||||
msgstr "Paramètres avancés"
|
||||
|
||||
@@ -500,11 +504,11 @@ msgstr "En attente d'approbation"
|
||||
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 :"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:377
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:383
|
||||
msgid "Black"
|
||||
msgstr "Noir"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:391
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:397
|
||||
msgid "Blue"
|
||||
msgstr "Bleu"
|
||||
|
||||
@@ -562,7 +566,7 @@ msgstr "Valeurs de case à cocher"
|
||||
msgid "Clear filters"
|
||||
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"
|
||||
msgstr "Effacer la signature"
|
||||
|
||||
@@ -590,7 +594,7 @@ msgid "Configure Direct Recipient"
|
||||
msgstr "Configurer le destinataire direct"
|
||||
|
||||
#: 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"
|
||||
msgstr "Configurer le champ {0}"
|
||||
|
||||
@@ -653,7 +657,7 @@ msgstr "Texte personnalisé"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:934
|
||||
#: 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"
|
||||
msgstr "Date"
|
||||
|
||||
@@ -688,17 +692,17 @@ msgstr "Document \"{0}\" - Rejet Confirmé"
|
||||
msgid "Document access"
|
||||
msgstr "Accès au document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:306
|
||||
#: packages/lib/utils/document-audit-logs.ts:310
|
||||
msgid "Document access auth updated"
|
||||
msgstr "L'authentification d'accès au document a été mise à jour"
|
||||
|
||||
#: packages/lib/server-only/document/delete-document.ts:246
|
||||
#: packages/lib/server-only/document/delete-document.ts:256
|
||||
#: packages/lib/server-only/document/super-delete-document.ts:98
|
||||
msgid "Document Cancelled"
|
||||
msgstr "Document Annulé"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:369
|
||||
#: packages/lib/utils/document-audit-logs.ts:370
|
||||
#: packages/lib/utils/document-audit-logs.ts:373
|
||||
#: packages/lib/utils/document-audit-logs.ts:374
|
||||
msgid "Document completed"
|
||||
msgstr "Document terminé"
|
||||
|
||||
@@ -736,15 +740,15 @@ msgstr "Document Supprimé !"
|
||||
msgid "Document Distribution Method"
|
||||
msgstr "Méthode de distribution du document"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:326
|
||||
#: packages/lib/utils/document-audit-logs.ts:330
|
||||
msgid "Document external ID updated"
|
||||
msgstr "ID externe du document mis à jour"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:334
|
||||
#: packages/lib/utils/document-audit-logs.ts:338
|
||||
msgid "Document moved to team"
|
||||
msgstr "Document déplacé vers l'équipe"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:318
|
||||
#: packages/lib/utils/document-audit-logs.ts:322
|
||||
msgid "Document opened"
|
||||
msgstr "Document ouvert"
|
||||
|
||||
@@ -759,23 +763,27 @@ msgstr "Document Rejeté"
|
||||
#~ msgid "Document Rejection Confirmed"
|
||||
#~ msgstr "Document Rejection Confirmed"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:330
|
||||
#: packages/lib/utils/document-audit-logs.ts:294
|
||||
msgid "Document restored"
|
||||
msgstr ""
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:334
|
||||
msgid "Document sent"
|
||||
msgstr "Document envoyé"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:310
|
||||
#: packages/lib/utils/document-audit-logs.ts:314
|
||||
msgid "Document signing auth updated"
|
||||
msgstr "Authentification de signature de document mise à jour"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:322
|
||||
#: packages/lib/utils/document-audit-logs.ts:326
|
||||
msgid "Document title updated"
|
||||
msgstr "Titre du document mis à jour"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:314
|
||||
#: packages/lib/utils/document-audit-logs.ts:318
|
||||
msgid "Document updated"
|
||||
msgstr "Document mis à jour"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:302
|
||||
#: packages/lib/utils/document-audit-logs.ts:306
|
||||
msgid "Document visibility updated"
|
||||
msgstr "Visibilité du document mise à jour"
|
||||
|
||||
@@ -793,7 +801,7 @@ msgid "Drag & drop your PDF here."
|
||||
msgstr "Faites glisser et déposez votre PDF ici."
|
||||
|
||||
#: 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"
|
||||
msgstr "Liste déroulante"
|
||||
|
||||
@@ -807,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:519
|
||||
#: 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:478
|
||||
msgid "Email"
|
||||
@@ -821,11 +829,11 @@ msgstr "L'email est requis"
|
||||
msgid "Email Options"
|
||||
msgstr "Options d'email"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:363
|
||||
#: packages/lib/utils/document-audit-logs.ts:367
|
||||
msgid "Email resent"
|
||||
msgstr "Email renvoyé"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:363
|
||||
#: packages/lib/utils/document-audit-logs.ts:367
|
||||
msgid "Email sent"
|
||||
msgstr "Email envoyé"
|
||||
|
||||
@@ -843,6 +851,7 @@ msgid "Enable signing order"
|
||||
msgstr "Activer l'ordre de signature"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:802
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:597
|
||||
msgid "Enable Typed Signatures"
|
||||
msgstr "Activer les signatures tapées"
|
||||
|
||||
@@ -889,11 +898,11 @@ msgstr "Étiquette du champ"
|
||||
msgid "Field placeholder"
|
||||
msgstr "Espace réservé du champ"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:294
|
||||
#: packages/lib/utils/document-audit-logs.ts:298
|
||||
msgid "Field signed"
|
||||
msgstr "Champ signé"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:298
|
||||
#: packages/lib/utils/document-audit-logs.ts:302
|
||||
msgid "Field unsigned"
|
||||
msgstr "Champ non signé"
|
||||
|
||||
@@ -930,7 +939,7 @@ msgstr "Authentification d'action de destinataire globale"
|
||||
msgid "Go Back"
|
||||
msgstr "Retourner"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:398
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:404
|
||||
msgid "Green"
|
||||
msgstr "Vert"
|
||||
|
||||
@@ -1025,7 +1034,7 @@ msgstr "Min"
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:550
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:556
|
||||
#: 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:512
|
||||
msgid "Name"
|
||||
@@ -1044,7 +1053,7 @@ msgid "Needs to view"
|
||||
msgstr "Nécessite une visualisation"
|
||||
|
||||
#: 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."
|
||||
msgstr "Aucun destinataire correspondant à cette description n'a été trouvé."
|
||||
|
||||
@@ -1053,7 +1062,7 @@ msgid "No recipients"
|
||||
msgstr "Aucun destinataire"
|
||||
|
||||
#: 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"
|
||||
msgstr "Aucun destinataire avec ce rôle"
|
||||
|
||||
@@ -1083,7 +1092,7 @@ msgstr "Aucun"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:986
|
||||
#: 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"
|
||||
msgstr "Numéro"
|
||||
|
||||
@@ -1175,7 +1184,6 @@ msgid "Please try again or contact our support."
|
||||
msgstr "Veuillez réessayer ou contacter notre support."
|
||||
|
||||
#: packages/ui/primitives/document-flow/types.ts:57
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:775
|
||||
msgid "Radio"
|
||||
msgstr "Radio"
|
||||
|
||||
@@ -1199,8 +1207,8 @@ msgstr "Raison du rejet : {rejectionReason}"
|
||||
msgid "Receives copy"
|
||||
msgstr "Recevoir une copie"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts:338
|
||||
#: packages/lib/utils/document-audit-logs.ts:353
|
||||
#: packages/lib/utils/document-audit-logs.ts:342
|
||||
#: packages/lib/utils/document-audit-logs.ts:357
|
||||
msgid "Recipient"
|
||||
msgstr "Destinataire"
|
||||
|
||||
@@ -1218,7 +1226,7 @@ msgstr "E-mail de destinataire supprimé"
|
||||
msgid "Recipient signing request email"
|
||||
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"
|
||||
msgstr "Rouge"
|
||||
|
||||
@@ -1287,7 +1295,7 @@ msgstr "Lignes par page"
|
||||
msgid "Save"
|
||||
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"
|
||||
msgstr "Sauvegarder le modèle"
|
||||
|
||||
@@ -1380,7 +1388,7 @@ msgstr "Se connecter"
|
||||
#: packages/ui/primitives/document-flow/add-signature.tsx:323
|
||||
#: packages/ui/primitives/document-flow/field-icon.tsx:52
|
||||
#: packages/ui/primitives/document-flow/types.ts:49
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:593
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:625
|
||||
msgid "Signature"
|
||||
msgstr "Signature"
|
||||
|
||||
@@ -1465,7 +1473,7 @@ msgstr "Titre du modèle"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:960
|
||||
#: 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"
|
||||
msgstr "Texte"
|
||||
|
||||
@@ -1629,7 +1637,7 @@ msgid "Title"
|
||||
msgstr "Titre"
|
||||
|
||||
#: 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."
|
||||
msgstr "Pour continuer, veuillez définir au moins une valeur pour le champ {0}."
|
||||
|
||||
@@ -1814,4 +1822,3 @@ msgstr "Votre mot de passe a été mis à jour."
|
||||
#: packages/email/templates/team-delete.tsx:32
|
||||
msgid "Your team has been deleted"
|
||||
msgstr "Votre équipe a été supprimée"
|
||||
|
||||
|
||||
@@ -602,4 +602,3 @@ msgstr "Vous pouvez auto-héberger Documenso gratuitement ou utiliser notre vers
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:272
|
||||
msgid "Your browser does not support the video tag."
|
||||
msgstr "Votre navigateur ne prend pas en charge la balise vidéo."
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,7 @@ export const ZDocumentAuditLogTypeSchema = z.enum([
|
||||
'DOCUMENT_COMPLETED', // When the document is sealed and fully completed.
|
||||
'DOCUMENT_CREATED', // When the document is created.
|
||||
'DOCUMENT_DELETED', // When the document is soft deleted.
|
||||
'DOCUMENT_RESTORED', // When the document is restored.
|
||||
'DOCUMENT_FIELD_INSERTED', // When a field is inserted (signed/approved/etc) by a recipient.
|
||||
'DOCUMENT_FIELD_UNINSERTED', // When a field is uninserted by a recipient.
|
||||
'DOCUMENT_VISIBILITY_UPDATED', // When the document visibility scope is updated
|
||||
@@ -225,6 +226,16 @@ export const ZDocumentAuditLogEventDocumentDeletedSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* Event: Document restored.
|
||||
*/
|
||||
export const ZDocumentAuditLogEventDocumentRestoredSchema = z.object({
|
||||
type: z.literal(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RESTORED),
|
||||
data: z.object({
|
||||
type: z.enum(['RESTORE']),
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* Event: Document field inserted.
|
||||
*/
|
||||
@@ -490,6 +501,7 @@ export const ZDocumentAuditLogSchema = ZDocumentAuditLogBaseSchema.and(
|
||||
ZDocumentAuditLogEventDocumentCreatedSchema,
|
||||
ZDocumentAuditLogEventDocumentDeletedSchema,
|
||||
ZDocumentAuditLogEventDocumentMovedToTeamSchema,
|
||||
ZDocumentAuditLogEventDocumentRestoredSchema,
|
||||
ZDocumentAuditLogEventDocumentFieldInsertedSchema,
|
||||
ZDocumentAuditLogEventDocumentFieldUninsertedSchema,
|
||||
ZDocumentAuditLogEventDocumentVisibilitySchema,
|
||||
|
||||
@@ -290,6 +290,10 @@ export const formatDocumentAuditLogAction = (
|
||||
anonymous: msg`Document deleted`,
|
||||
identified: msg`${prefix} deleted the document`,
|
||||
}))
|
||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RESTORED }, () => ({
|
||||
anonymous: msg`Document restored`,
|
||||
identified: msg`${prefix} restored the document`,
|
||||
}))
|
||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_INSERTED }, () => ({
|
||||
anonymous: msg`Field signed`,
|
||||
identified: msg`${prefix} signed a field`,
|
||||
|
||||
@@ -24,7 +24,8 @@ module.exports = {
|
||||
|
||||
plugins: [
|
||||
'@trivago/prettier-plugin-sort-imports',
|
||||
'prettier-plugin-sql',
|
||||
// !: Disabled until Prettier 3.x is supported.
|
||||
// 'prettier-plugin-sql',
|
||||
'prettier-plugin-tailwindcss',
|
||||
],
|
||||
|
||||
|
||||
@@ -7,10 +7,9 @@
|
||||
"clean": "rimraf node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-sql": "^0.14.0",
|
||||
"prettier-plugin-tailwindcss": "^0.2.8"
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.9"
|
||||
},
|
||||
"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)
|
||||
redirectUrl String?
|
||||
signingOrder DocumentSigningOrder @default(PARALLEL)
|
||||
typedSignatureEnabled Boolean @default(false)
|
||||
typedSignatureEnabled Boolean @default(true)
|
||||
language String @default("en")
|
||||
distributionMethod DocumentDistributionMethod @default(EMAIL)
|
||||
emailSettings Json?
|
||||
@@ -511,10 +511,12 @@ enum TeamMemberInviteStatus {
|
||||
}
|
||||
|
||||
model TeamGlobalSettings {
|
||||
teamId Int @unique
|
||||
documentVisibility DocumentVisibility @default(EVERYONE)
|
||||
documentLanguage String @default("en")
|
||||
includeSenderDetails Boolean @default(true)
|
||||
teamId Int @unique
|
||||
documentVisibility DocumentVisibility @default(EVERYONE)
|
||||
documentLanguage String @default("en")
|
||||
includeSenderDetails Boolean @default(true)
|
||||
typedSignatureEnabled Boolean @default(true)
|
||||
includeSigningCertificate Boolean @default(true)
|
||||
|
||||
brandingEnabled Boolean @default(false)
|
||||
brandingLogo String @default("")
|
||||
@@ -628,19 +630,21 @@ enum TemplateType {
|
||||
}
|
||||
|
||||
model TemplateMeta {
|
||||
id String @id @default(cuid())
|
||||
subject String?
|
||||
message String?
|
||||
timezone String? @default("Etc/UTC") @db.Text
|
||||
password String?
|
||||
dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text
|
||||
signingOrder DocumentSigningOrder? @default(PARALLEL)
|
||||
templateId Int @unique
|
||||
template Template @relation(fields: [templateId], references: [id], onDelete: Cascade)
|
||||
redirectUrl String?
|
||||
language String @default("en")
|
||||
distributionMethod DocumentDistributionMethod @default(EMAIL)
|
||||
emailSettings Json?
|
||||
id String @id @default(cuid())
|
||||
subject String?
|
||||
message String?
|
||||
timezone String? @default("Etc/UTC") @db.Text
|
||||
password String?
|
||||
dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text
|
||||
signingOrder DocumentSigningOrder? @default(PARALLEL)
|
||||
typedSignatureEnabled Boolean @default(true)
|
||||
distributionMethod DocumentDistributionMethod @default(EMAIL)
|
||||
|
||||
templateId Int @unique
|
||||
template Template @relation(fields: [templateId], references: [id], onDelete: Cascade)
|
||||
redirectUrl String?
|
||||
language String @default("en")
|
||||
emailSettings Json?
|
||||
}
|
||||
|
||||
model Template {
|
||||
|
||||
@@ -4,6 +4,7 @@ export const ExtendedDocumentStatus = {
|
||||
...DocumentStatus,
|
||||
INBOX: 'INBOX',
|
||||
ALL: 'ALL',
|
||||
BIN: 'BIN',
|
||||
} as const;
|
||||
|
||||
export type ExtendedDocumentStatus =
|
||||
|
||||
@@ -3,7 +3,7 @@ const { fontFamily } = require('tailwindcss/defaultTheme');
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: ['class'],
|
||||
darkMode: ['variant', '&:is(.dark:not(.dark-mode-disabled) *)'],
|
||||
content: ['src/**/*.{ts,tsx}'],
|
||||
theme: {
|
||||
extend: {
|
||||
@@ -108,6 +108,9 @@ module.exports = {
|
||||
'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
||||
},
|
||||
borderRadius: {
|
||||
DEFAULT: 'calc(var(--radius) - 3px)',
|
||||
'2xl': 'calc(var(--radius) + 4px)',
|
||||
xl: 'calc(var(--radius) + 2px)',
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)',
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"postcss": "^8.4.32",
|
||||
"tailwindcss": "3.3.2",
|
||||
"tailwindcss": "3.4.15",
|
||||
"tailwindcss-animate": "^1.0.5"
|
||||
},
|
||||
"devDependencies": {},
|
||||
|
||||
@@ -17,6 +17,7 @@ import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document
|
||||
import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
|
||||
import { moveDocumentToTeam } from '@documenso/lib/server-only/document/move-document-to-team';
|
||||
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
|
||||
import { restoreDocument } from '@documenso/lib/server-only/document/restore-document';
|
||||
import { searchDocumentsWithKeyword } from '@documenso/lib/server-only/document/search-documents-with-keyword';
|
||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||
import { updateDocumentSettings } from '@documenso/lib/server-only/document/update-document-settings';
|
||||
@@ -38,6 +39,7 @@ import {
|
||||
ZGetDocumentWithDetailsByIdQuerySchema,
|
||||
ZMoveDocumentsToTeamSchema,
|
||||
ZResendDocumentMutationSchema,
|
||||
ZRestoreDocumentMutationSchema,
|
||||
ZSearchDocumentsMutationSchema,
|
||||
ZSendDocumentMutationSchema,
|
||||
ZSetPasswordForDocumentMutationSchema,
|
||||
@@ -223,6 +225,32 @@ export const documentRouter = router({
|
||||
}
|
||||
}),
|
||||
|
||||
restoreDocument: authenticatedProcedure
|
||||
.input(ZRestoreDocumentMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { id, teamId } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
const restoredDocument = await restoreDocument({
|
||||
id,
|
||||
userId,
|
||||
teamId,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
|
||||
return restoredDocument;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to restore this document. Please try again later.',
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
findDocumentAuditLogs: authenticatedProcedure
|
||||
.input(ZFindDocumentAuditLogsQuerySchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user