Merge branch 'main' into staging

This commit is contained in:
Timur Ercan
2024-11-07 16:10:10 +01:00
71 changed files with 3940 additions and 776 deletions

View File

@ -93,6 +93,8 @@ NEXT_PRIVATE_SMTP_UNSAFE_IGNORE_TLS=
NEXT_PRIVATE_SMTP_FROM_NAME="Documenso" NEXT_PRIVATE_SMTP_FROM_NAME="Documenso"
# REQUIRED: Defines the email address to use as the from address. # REQUIRED: Defines the email address to use as the from address.
NEXT_PRIVATE_SMTP_FROM_ADDRESS="noreply@documenso.com" NEXT_PRIVATE_SMTP_FROM_ADDRESS="noreply@documenso.com"
# OPTIONAL: Defines the service for nodemailer
NEXT_PRIVATE_SMTP_SERVICE=
# OPTIONAL: The API key to use for Resend.com # OPTIONAL: The API key to use for Resend.com
NEXT_PRIVATE_RESEND_API_KEY= NEXT_PRIVATE_RESEND_API_KEY=
# OPTIONAL: The API key to use for MailChannels. # OPTIONAL: The API key to use for MailChannels.

View File

@ -38,11 +38,17 @@ You will be prompted to enter some information, such as the certificate's Common
Combine the private key and the self-signed certificate to create a `.p12` certificate. Use the following command: Combine the private key and the self-signed certificate to create a `.p12` certificate. Use the following command:
```bash ```bash
openssl pkcs12 -export -out certificate.p12 -inkey private.key -in certificate.crt openssl pkcs12 -export -out certificate.p12 -inkey private.key -in certificate.crt -legacy
``` ```
<Callout type="warning"> <Callout type="warning">
If you get the error "Error: Failed to get private key bags", add the `-legacy` flag to the command `openssl pkcs12 -export -out certificate.p12 -inkey private.key -in certificate.crt -legacy`. When running the application in Docker, you may encounter permission issues when attempting to sign documents using your certificate (.p12) file. This happens because the application runs as a non-root user inside the container and needs read access to the certificate.
To resolve this, you'll need to update the certificate file permissions to allow the container user 1001, which runs NextJS, to read it:
```bash
sudo chown 1001 certificate.p12
```
</Callout> </Callout>
@ -54,8 +60,8 @@ Note that for local development, the password can be left empty.
### Add Certificate to the Project ### Add Certificate to the Project
Finally, add the certificate to the project. Place the `certificate.p12` file in the `/apps/web/resources` directory. If the directory doesn't exist, create it. Use the `NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH` environment variable to point at the certificate you created.
The final file path should be `/apps/web/resources/certificate.p12`. Details about environment variables associated with certificates can be found [here](/developers/self-hosting/signing-certificate#configure-documenso-to-use-the-certificate).
</Steps> </Steps>

View File

@ -133,7 +133,7 @@ volumes:
After updating the volume binding, save the `compose.yml` file and run the following command to start the containers: After updating the volume binding, save the `compose.yml` file and run the following command to start the containers:
```bash ```bash
docker-compose --env-file ./.env -d up docker-compose --env-file ./.env up -d
``` ```
The command will start the PostgreSQL database and the Documenso application containers. The command will start the PostgreSQL database and the Documenso application containers.

View File

@ -1,6 +1,6 @@
{ {
"name": "@documenso/marketing", "name": "@documenso/marketing",
"version": "1.7.2-rc.3", "version": "1.7.2-rc.4",
"private": true, "private": true,
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {

View File

@ -0,0 +1,23 @@
'use client';
import Image from 'next/image';
import type { DocumentTypes } from 'contentlayer/generated';
import type { MDXComponents } from 'mdx/types';
import { useMDXComponent } from 'next-contentlayer/hooks';
const mdxComponents: MDXComponents = {
MdxNextImage: (props: { width: number; height: number; alt?: string; src: string }) => (
<Image {...props} alt={props.alt ?? ''} />
),
};
export type ContentPageContentProps = {
document: DocumentTypes;
};
export const ContentPageContent = ({ document }: ContentPageContentProps) => {
const MDXContent = useMDXComponent(document.body.code);
return <MDXContent components={mdxComponents} />;
};

View File

@ -1,12 +1,11 @@
import Image from 'next/image';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import { allDocuments } from 'contentlayer/generated'; import { allDocuments } from 'contentlayer/generated';
import type { MDXComponents } from 'mdx/types';
import { useMDXComponent } from 'next-contentlayer/hooks';
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server'; import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
import { ContentPageContent } from './content';
export const dynamic = 'force-dynamic'; export const dynamic = 'force-dynamic';
export const generateMetadata = ({ params }: { params: { content: string } }) => { export const generateMetadata = ({ params }: { params: { content: string } }) => {
@ -19,12 +18,6 @@ export const generateMetadata = ({ params }: { params: { content: string } }) =>
return { title: document.title }; return { title: document.title };
}; };
const mdxComponents: MDXComponents = {
MdxNextImage: (props: { width: number; height: number; alt?: string; src: string }) => (
<Image {...props} alt={props.alt ?? ''} />
),
};
/** /**
* A generic catch all page for the root level that checks for content layer documents. * A generic catch all page for the root level that checks for content layer documents.
* *
@ -39,11 +32,9 @@ export default async function ContentPage({ params }: { params: { content: strin
notFound(); notFound();
} }
const MDXContent = useMDXComponent(post.body.code);
return ( return (
<article className="prose dark:prose-invert mx-auto"> <article className="prose dark:prose-invert mx-auto">
<MDXContent components={mdxComponents} /> <ContentPageContent document={post} />
</article> </article>
); );
} }

View File

@ -0,0 +1,23 @@
'use client';
import Image from 'next/image';
import type { BlogPost } from 'contentlayer/generated';
import type { MDXComponents } from 'mdx/types';
import { useMDXComponent } from 'next-contentlayer/hooks';
const mdxComponents: MDXComponents = {
MdxNextImage: (props: { width: number; height: number; alt?: string; src: string }) => (
<Image {...props} alt={props.alt ?? ''} />
),
};
export type BlogPostContentProps = {
post: BlogPost;
};
export const BlogPostContent = ({ post }: BlogPostContentProps) => {
const MdxContent = useMDXComponent(post.body.code);
return <MdxContent components={mdxComponents} />;
};

View File

@ -1,16 +1,15 @@
import Image from 'next/image';
import Link from 'next/link'; import Link from 'next/link';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import { allBlogPosts } from 'contentlayer/generated'; import { allBlogPosts } from 'contentlayer/generated';
import { ChevronLeft } from 'lucide-react'; import { ChevronLeft } from 'lucide-react';
import type { MDXComponents } from 'mdx/types';
import { useMDXComponent } from 'next-contentlayer/hooks';
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server'; import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
import { CallToAction } from '~/components/(marketing)/call-to-action'; import { CallToAction } from '~/components/(marketing)/call-to-action';
import { BlogPostContent } from './content';
export const dynamic = 'force-dynamic'; export const dynamic = 'force-dynamic';
export const generateMetadata = ({ params }: { params: { post: string } }) => { export const generateMetadata = ({ params }: { params: { post: string } }) => {
@ -42,12 +41,6 @@ export const generateMetadata = ({ params }: { params: { post: string } }) => {
}; };
}; };
const mdxComponents: MDXComponents = {
MdxNextImage: (props: { width: number; height: number; alt?: string; src: string }) => (
<Image {...props} alt={props.alt ?? ''} />
),
};
export default async function BlogPostPage({ params }: { params: { post: string } }) { export default async function BlogPostPage({ params }: { params: { post: string } }) {
await setupI18nSSR(); await setupI18nSSR();
@ -57,8 +50,6 @@ export default async function BlogPostPage({ params }: { params: { post: string
notFound(); notFound();
} }
const MDXContent = useMDXComponent(post.body.code);
return ( return (
<div> <div>
<article className="prose dark:prose-invert mx-auto py-8"> <article className="prose dark:prose-invert mx-auto py-8">
@ -87,7 +78,7 @@ export default async function BlogPostPage({ params }: { params: { post: string
</div> </div>
</div> </div>
<MDXContent components={mdxComponents} /> <BlogPostContent post={post} />
{post.tags.length > 0 && ( {post.tags.length > 0 && (
<ul className="not-prose flex list-none flex-row space-x-2 px-0"> <ul className="not-prose flex list-none flex-row space-x-2 px-0">

View File

@ -1,6 +1,7 @@
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { Trans, msg } from '@lingui/macro'; import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { z } from 'zod'; import { z } from 'zod';
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server'; import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
@ -130,9 +131,9 @@ const fetchEarlyAdopters = async () => {
}; };
export default async function OpenPage() { export default async function OpenPage() {
const { i18n } = await setupI18nSSR(); await setupI18nSSR();
const { _ } = i18n; const { _ } = useLingui();
const [ const [
{ forks_count: forksCount, stargazers_count: stargazersCount }, { forks_count: forksCount, stargazers_count: stargazersCount },

View File

@ -1,6 +1,6 @@
{ {
"name": "@documenso/web", "name": "@documenso/web",
"version": "1.7.2-rc.3", "version": "1.7.2-rc.4",
"private": true, "private": true,
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {

View File

@ -33,6 +33,8 @@ import {
} from '@documenso/ui/primitives/dropdown-menu'; } from '@documenso/ui/primitives/dropdown-menu';
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
import { DocumentRecipientLinkCopyDialog } from '~/components/document/document-recipient-link-copy-dialog';
import { ResendDocumentActionItem } from '../_action-items/resend-document'; import { ResendDocumentActionItem } from '../_action-items/resend-document';
import { DeleteDocumentDialog } from '../delete-document-dialog'; import { DeleteDocumentDialog } from '../delete-document-dialog';
import { DuplicateDocumentDialog } from '../duplicate-document-dialog'; import { DuplicateDocumentDialog } from '../duplicate-document-dialog';
@ -62,6 +64,7 @@ export const DocumentPageViewDropdown = ({ document, team }: DocumentPageViewDro
const isOwner = document.User.id === session.user.id; const isOwner = document.User.id === session.user.id;
const isDraft = document.status === DocumentStatus.DRAFT; const isDraft = document.status === DocumentStatus.DRAFT;
const isPending = document.status === DocumentStatus.PENDING;
const isDeleted = document.deletedAt !== null; const isDeleted = document.deletedAt !== null;
const isComplete = document.status === DocumentStatus.COMPLETED; const isComplete = document.status === DocumentStatus.COMPLETED;
const isCurrentTeamDocument = team && document.team?.url === team.url; const isCurrentTeamDocument = team && document.team?.url === team.url;
@ -145,6 +148,21 @@ export const DocumentPageViewDropdown = ({ document, team }: DocumentPageViewDro
<Trans>Share</Trans> <Trans>Share</Trans>
</DropdownMenuLabel> </DropdownMenuLabel>
{canManageDocument && (
<DocumentRecipientLinkCopyDialog
recipients={document.Recipient}
trigger={
<DropdownMenuItem
disabled={!isPending || isDeleted}
onSelect={(e) => e.preventDefault()}
>
<Copy className="mr-2 h-4 w-4" />
<Trans>Signing Links</Trans>
</DropdownMenuItem>
}
/>
)}
<ResendDocumentActionItem <ResendDocumentActionItem
document={document} document={document}
recipients={nonSignedRecipients} recipients={nonSignedRecipients}

View File

@ -143,17 +143,11 @@ export const DocumentPageViewRecentActivity = ({
))} ))}
</div> </div>
{/* Todo: Translations. */}
<p <p
className="text-muted-foreground dark:text-muted-foreground/70 flex-auto truncate py-0.5 text-xs leading-5" className="text-muted-foreground dark:text-muted-foreground/70 flex-auto truncate py-0.5 text-xs leading-5"
title={`${formatDocumentAuditLogAction(auditLog, userId).prefix} ${ title={formatDocumentAuditLogAction(_, auditLog, userId).description}
formatDocumentAuditLogAction(auditLog, userId).description
}`}
> >
<span className="text-foreground font-medium"> {formatDocumentAuditLogAction(_, auditLog, userId).description}
{formatDocumentAuditLogAction(auditLog, userId).prefix}
</span>{' '}
{formatDocumentAuditLogAction(auditLog, userId).description}
</p> </p>
<time className="text-muted-foreground dark:text-muted-foreground/70 flex-none py-0.5 text-xs leading-5"> <time className="text-muted-foreground dark:text-muted-foreground/70 flex-none py-0.5 text-xs leading-5">

View File

@ -1,3 +1,5 @@
'use client';
import Link from 'next/link'; import Link from 'next/link';
import { Trans, msg } from '@lingui/macro'; import { Trans, msg } from '@lingui/macro';
@ -6,11 +8,14 @@ import { CheckIcon, Clock, MailIcon, MailOpenIcon, PenIcon, PlusIcon } from 'luc
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles'; import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import { formatSigningLink } from '@documenso/lib/utils/recipients';
import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/client'; import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/client';
import type { Document, Recipient } from '@documenso/prisma/client'; import type { Document, Recipient } from '@documenso/prisma/client';
import { CopyTextButton } from '@documenso/ui/components/common/copy-text-button';
import { SignatureIcon } from '@documenso/ui/icons/signature'; import { SignatureIcon } from '@documenso/ui/icons/signature';
import { AvatarWithText } from '@documenso/ui/primitives/avatar'; import { AvatarWithText } from '@documenso/ui/primitives/avatar';
import { Badge } from '@documenso/ui/primitives/badge'; import { Badge } from '@documenso/ui/primitives/badge';
import { useToast } from '@documenso/ui/primitives/use-toast';
export type DocumentPageViewRecipientsProps = { export type DocumentPageViewRecipientsProps = {
document: Document & { document: Document & {
@ -24,6 +29,7 @@ export const DocumentPageViewRecipients = ({
documentRootPath, documentRootPath,
}: DocumentPageViewRecipientsProps) => { }: DocumentPageViewRecipientsProps) => {
const { _ } = useLingui(); const { _ } = useLingui();
const { toast } = useToast();
const recipients = document.Recipient; const recipients = document.Recipient;
@ -68,53 +74,69 @@ export const DocumentPageViewRecipients = ({
} }
/> />
{document.status !== DocumentStatus.DRAFT && <div className="flex flex-row items-center">
recipient.signingStatus === SigningStatus.SIGNED && ( {document.status !== DocumentStatus.DRAFT &&
<Badge variant="default"> recipient.signingStatus === SigningStatus.SIGNED && (
{match(recipient.role) <Badge variant="default">
.with(RecipientRole.APPROVER, () => ( {match(recipient.role)
<> .with(RecipientRole.APPROVER, () => (
<CheckIcon className="mr-1 h-3 w-3" />
<Trans>Approved</Trans>
</>
))
.with(RecipientRole.CC, () =>
document.status === DocumentStatus.COMPLETED ? (
<>
<MailIcon className="mr-1 h-3 w-3" />
<Trans>Sent</Trans>
</>
) : (
<> <>
<CheckIcon className="mr-1 h-3 w-3" /> <CheckIcon className="mr-1 h-3 w-3" />
<Trans>Ready</Trans> <Trans>Approved</Trans>
</> </>
), ))
) .with(RecipientRole.CC, () =>
document.status === DocumentStatus.COMPLETED ? (
<>
<MailIcon className="mr-1 h-3 w-3" />
<Trans>Sent</Trans>
</>
) : (
<>
<CheckIcon className="mr-1 h-3 w-3" />
<Trans>Ready</Trans>
</>
),
)
.with(RecipientRole.SIGNER, () => ( .with(RecipientRole.SIGNER, () => (
<> <>
<SignatureIcon className="mr-1 h-3 w-3" /> <SignatureIcon className="mr-1 h-3 w-3" />
<Trans>Signed</Trans> <Trans>Signed</Trans>
</> </>
)) ))
.with(RecipientRole.VIEWER, () => ( .with(RecipientRole.VIEWER, () => (
<> <>
<MailOpenIcon className="mr-1 h-3 w-3" /> <MailOpenIcon className="mr-1 h-3 w-3" />
<Trans>Viewed</Trans> <Trans>Viewed</Trans>
</> </>
)) ))
.exhaustive()} .exhaustive()}
</Badge> </Badge>
)} )}
{document.status !== DocumentStatus.DRAFT && {document.status !== DocumentStatus.DRAFT &&
recipient.signingStatus === SigningStatus.NOT_SIGNED && ( recipient.signingStatus === SigningStatus.NOT_SIGNED && (
<Badge variant="secondary"> <Badge variant="secondary">
<Clock className="mr-1 h-3 w-3" /> <Clock className="mr-1 h-3 w-3" />
<Trans>Pending</Trans> <Trans>Pending</Trans>
</Badge> </Badge>
)} )}
{document.status === DocumentStatus.PENDING &&
recipient.signingStatus === SigningStatus.NOT_SIGNED &&
recipient.role !== RecipientRole.CC && (
<CopyTextButton
value={formatSigningLink(recipient.token)}
onCopySuccess={() => {
toast({
title: _(msg`Copied to clipboard`),
description: _(msg`The signing link has been copied to your clipboard.`),
});
}}
/>
)}
</div>
</li> </li>
))} ))}
</ul> </ul>

View File

@ -26,6 +26,7 @@ import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
import { StackAvatarsWithTooltip } from '~/components/(dashboard)/avatar/stack-avatars-with-tooltip'; import { StackAvatarsWithTooltip } from '~/components/(dashboard)/avatar/stack-avatars-with-tooltip';
import { DocumentHistorySheet } from '~/components/document/document-history-sheet'; import { DocumentHistorySheet } from '~/components/document/document-history-sheet';
import { DocumentReadOnlyFields } from '~/components/document/document-read-only-fields'; import { DocumentReadOnlyFields } from '~/components/document/document-read-only-fields';
import { DocumentRecipientLinkCopyDialog } from '~/components/document/document-recipient-link-copy-dialog';
import { import {
DocumentStatus as DocumentStatusComponent, DocumentStatus as DocumentStatusComponent,
FRIENDLY_STATUS_MAP, FRIENDLY_STATUS_MAP,
@ -134,6 +135,10 @@ export const DocumentPageView = async ({ params, team }: DocumentPageViewProps)
return ( return (
<div className="mx-auto -mt-4 w-full max-w-screen-xl px-4 md:px-8"> <div className="mx-auto -mt-4 w-full max-w-screen-xl px-4 md:px-8">
{document.status === DocumentStatus.PENDING && (
<DocumentRecipientLinkCopyDialog recipients={recipients} />
)}
<Link href={documentRootPath} className="flex items-center text-[#7AC455] hover:opacity-80"> <Link href={documentRootPath} className="flex items-center text-[#7AC455] hover:opacity-80">
<ChevronLeft className="mr-2 inline-block h-5 w-5" /> <ChevronLeft className="mr-2 inline-block h-5 w-5" />
<Trans>Documents</Trans> <Trans>Documents</Trans>

View File

@ -58,10 +58,6 @@ export const DocumentLogsDataTable = ({ documentId }: DocumentLogsDataTableProps
}); });
}; };
const uppercaseFistLetter = (text: string) => {
return text.charAt(0).toUpperCase() + text.slice(1);
};
const results = data ?? { const results = data ?? {
data: [], data: [],
perPage: 10, perPage: 10,
@ -103,9 +99,7 @@ export const DocumentLogsDataTable = ({ documentId }: DocumentLogsDataTableProps
{ {
header: _(msg`Action`), header: _(msg`Action`),
accessorKey: 'type', accessorKey: 'type',
cell: ({ row }) => ( cell: ({ row }) => <span>{formatDocumentAuditLogAction(_, row.original).description}</span>,
<span>{uppercaseFistLetter(formatDocumentAuditLogAction(row.original).description)}</span>
),
}, },
{ {
header: 'IP Address', header: 'IP Address',

View File

@ -37,6 +37,8 @@ import {
} from '@documenso/ui/primitives/dropdown-menu'; } from '@documenso/ui/primitives/dropdown-menu';
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
import { DocumentRecipientLinkCopyDialog } from '~/components/document/document-recipient-link-copy-dialog';
import { ResendDocumentActionItem } from './_action-items/resend-document'; import { ResendDocumentActionItem } from './_action-items/resend-document';
import { DeleteDocumentDialog } from './delete-document-dialog'; import { DeleteDocumentDialog } from './delete-document-dialog';
import { DuplicateDocumentDialog } from './duplicate-document-dialog'; import { DuplicateDocumentDialog } from './duplicate-document-dialog';
@ -69,7 +71,7 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
const isOwner = row.User.id === session.user.id; const isOwner = row.User.id === session.user.id;
// const isRecipient = !!recipient; // const isRecipient = !!recipient;
const isDraft = row.status === DocumentStatus.DRAFT; const isDraft = row.status === DocumentStatus.DRAFT;
// const isPending = row.status === DocumentStatus.PENDING; const isPending = row.status === DocumentStatus.PENDING;
const isComplete = row.status === DocumentStatus.COMPLETED; const isComplete = row.status === DocumentStatus.COMPLETED;
// const isSigned = recipient?.signingStatus === SigningStatus.SIGNED; // const isSigned = recipient?.signingStatus === SigningStatus.SIGNED;
const isCurrentTeamDocument = team && row.team?.url === team.url; const isCurrentTeamDocument = team && row.team?.url === team.url;
@ -191,6 +193,20 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
<Trans>Share</Trans> <Trans>Share</Trans>
</DropdownMenuLabel> </DropdownMenuLabel>
{canManageDocument && (
<DocumentRecipientLinkCopyDialog
recipients={row.Recipient}
trigger={
<DropdownMenuItem disabled={!isPending} asChild onSelect={(e) => e.preventDefault()}>
<div>
<Copy className="mr-2 h-4 w-4" />
<Trans>Signing Links</Trans>
</div>
</DropdownMenuItem>
}
/>
)}
<ResendDocumentActionItem document={row} recipients={nonSignedRecipients} team={team} /> <ResendDocumentActionItem document={row} recipients={nonSignedRecipients} team={team} />
<DocumentShareButton <DocumentShareButton

View File

@ -44,11 +44,11 @@ export const BillingPlans = ({ prices }: BillingPlansProps) => {
const isMounted = useIsMounted(); const isMounted = useIsMounted();
const [interval, setInterval] = useState<Interval>('month'); const [interval, setInterval] = useState<Interval>('month');
const [isFetchingCheckoutSession, setIsFetchingCheckoutSession] = useState(false); const [checkoutSessionPriceId, setCheckoutSessionPriceId] = useState<string | null>(null);
const onSubscribeClick = async (priceId: string) => { const onSubscribeClick = async (priceId: string) => {
try { try {
setIsFetchingCheckoutSession(true); setCheckoutSessionPriceId(priceId);
const url = await createCheckout({ priceId }); const url = await createCheckout({ priceId });
@ -64,7 +64,7 @@ export const BillingPlans = ({ prices }: BillingPlansProps) => {
variant: 'destructive', variant: 'destructive',
}); });
} finally { } finally {
setIsFetchingCheckoutSession(false); setCheckoutSessionPriceId(null);
} }
}; };
@ -122,7 +122,8 @@ export const BillingPlans = ({ prices }: BillingPlansProps) => {
<Button <Button
className="mt-4" className="mt-4"
loading={isFetchingCheckoutSession} disabled={checkoutSessionPriceId !== null}
loading={checkoutSessionPriceId === price.id}
onClick={() => void onSubscribeClick(price.id)} onClick={() => void onSubscribeClick(price.id)}
> >
<Trans>Subscribe</Trans> <Trans>Subscribe</Trans>

View File

@ -0,0 +1,14 @@
import React from 'react';
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
import type { TemplateEditPageViewProps } from './template-edit-page-view';
import { TemplateEditPageView } from './template-edit-page-view';
type TemplateEditPageProps = Pick<TemplateEditPageViewProps, 'params'>;
export default async function TemplateEditPage({ params }: TemplateEditPageProps) {
await setupI18nSSR();
return <TemplateEditPageView params={params} />;
}

View File

@ -0,0 +1,96 @@
import React from 'react';
import Link from 'next/link';
import { redirect } from 'next/navigation';
import { Trans } from '@lingui/macro';
import { ChevronLeft } from 'lucide-react';
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getTemplateWithDetailsById } from '@documenso/lib/server-only/template/get-template-with-details-by-id';
import { formatTemplatesPath } from '@documenso/lib/utils/teams';
import type { Team } from '@documenso/prisma/client';
import { TemplateType } from '~/components/formatter/template-type';
import { TemplateDirectLinkBadge } from '../../template-direct-link-badge';
import { TemplateDirectLinkDialogWrapper } from '../template-direct-link-dialog-wrapper';
import { EditTemplateForm } from './edit-template';
export type TemplateEditPageViewProps = {
params: {
id: string;
};
team?: Team;
};
export const TemplateEditPageView = async ({ params, team }: TemplateEditPageViewProps) => {
const { id } = params;
const templateId = Number(id);
const templateRootPath = formatTemplatesPath(team?.url);
if (!templateId || Number.isNaN(templateId)) {
redirect(templateRootPath);
}
const { user } = await getRequiredServerComponentSession();
const template = await getTemplateWithDetailsById({
id: templateId,
userId: user.id,
}).catch(() => null);
if (!template || !template.templateDocumentData) {
redirect(templateRootPath);
}
const isTemplateEnterprise = await isUserEnterprise({
userId: user.id,
teamId: team?.id,
});
return (
<div className="mx-auto -mt-4 max-w-screen-xl px-4 md:px-8">
<div className="flex flex-col justify-between sm:flex-row">
<div>
<Link
href={`${templateRootPath}/${templateId}`}
className="flex items-center text-[#7AC455] hover:opacity-80"
>
<ChevronLeft className="mr-2 inline-block h-5 w-5" />
<Trans>Template</Trans>
</Link>
<h1 className="mt-4 truncate text-2xl font-semibold md:text-3xl" title={template.title}>
{template.title}
</h1>
<div className="mt-2.5 flex items-center">
<TemplateType inheritColor className="text-muted-foreground" type={template.type} />
{template.directLink?.token && (
<TemplateDirectLinkBadge
className="ml-4"
token={template.directLink.token}
enabled={template.directLink.enabled}
/>
)}
</div>
</div>
<div className="mt-2 sm:mt-0 sm:self-end">
<TemplateDirectLinkDialogWrapper template={template} />
</div>
</div>
<EditTemplateForm
className="mt-6"
initialTemplate={template}
templateRootPath={templateRootPath}
isEnterprise={isTemplateEnterprise}
/>
</div>
);
};

View File

@ -1,11 +1,12 @@
import React from 'react';
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server'; import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
import type { TemplatePageViewProps } from './template-page-view';
import { TemplatePageView } from './template-page-view'; import { TemplatePageView } from './template-page-view';
type TemplatePageProps = Pick<TemplatePageViewProps, 'params'>; export type TemplatePageProps = {
params: {
id: string;
};
};
export default async function TemplatePage({ params }: TemplatePageProps) { export default async function TemplatePage({ params }: TemplatePageProps) {
await setupI18nSSR(); await setupI18nSSR();

View File

@ -10,11 +10,13 @@ import { Button } from '@documenso/ui/primitives/button';
import { TemplateDirectLinkDialog } from '../template-direct-link-dialog'; import { TemplateDirectLinkDialog } from '../template-direct-link-dialog';
export type TemplatePageViewProps = { export type TemplateDirectLinkDialogWrapperProps = {
template: Template & { directLink?: TemplateDirectLink | null; Recipient: Recipient[] }; template: Template & { directLink?: TemplateDirectLink | null; Recipient: Recipient[] };
}; };
export const TemplateDirectLinkDialogWrapper = ({ template }: TemplatePageViewProps) => { export const TemplateDirectLinkDialogWrapper = ({
template,
}: TemplateDirectLinkDialogWrapperProps) => {
const [isTemplateDirectLinkOpen, setTemplateDirectLinkOpen] = useState(false); const [isTemplateDirectLinkOpen, setTemplateDirectLinkOpen] = useState(false);
return ( return (

View File

@ -0,0 +1,281 @@
'use client';
import { useMemo } from 'react';
import { useSearchParams } from 'next/navigation';
import type { MessageDescriptor } from '@lingui/core';
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { InfoIcon } from 'lucide-react';
import { DateTime } from 'luxon';
import { z } from 'zod';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params';
import type { Team } from '@documenso/prisma/client';
import { DocumentSource, DocumentStatus as DocumentStatusEnum } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react';
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
import { DataTable } from '@documenso/ui/primitives/data-table';
import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
import { SelectItem } from '@documenso/ui/primitives/select';
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
import { StackAvatarsWithTooltip } from '~/components/(dashboard)/avatar/stack-avatars-with-tooltip';
import { DocumentSearch } from '~/components/(dashboard)/document-search/document-search';
import { PeriodSelector } from '~/components/(dashboard)/period-selector/period-selector';
import { DocumentStatus } from '~/components/formatter/document-status';
import { SearchParamSelector } from '~/components/forms/search-param-selector';
import { DataTableActionButton } from '../../documents/data-table-action-button';
import { DataTableActionDropdown } from '../../documents/data-table-action-dropdown';
import { DataTableTitle } from '../../documents/data-table-title';
const DOCUMENT_SOURCE_LABELS: { [key in DocumentSource]: MessageDescriptor } = {
DOCUMENT: msg`Document`,
TEMPLATE: msg`Template`,
TEMPLATE_DIRECT_LINK: msg`Direct link`,
};
const ZTemplateSearchParamsSchema = ZBaseTableSearchParamsSchema.extend({
source: z
.nativeEnum(DocumentSource)
.optional()
.catch(() => undefined),
status: z
.nativeEnum(DocumentStatusEnum)
.optional()
.catch(() => undefined),
search: z.coerce
.string()
.optional()
.catch(() => undefined),
});
type TemplatePageViewDocumentsTableProps = {
templateId: number;
team?: Team;
};
export const TemplatePageViewDocumentsTable = ({
templateId,
team,
}: TemplatePageViewDocumentsTableProps) => {
const { _, i18n } = useLingui();
const searchParams = useSearchParams();
const updateSearchParams = useUpdateSearchParams();
const parsedSearchParams = ZTemplateSearchParamsSchema.parse(
Object.fromEntries(searchParams ?? []),
);
const { data, isLoading, isInitialLoading, isLoadingError } =
trpc.document.findDocuments.useQuery(
{
templateId,
teamId: team?.id,
page: parsedSearchParams.page,
perPage: parsedSearchParams.perPage,
search: parsedSearchParams.search,
source: parsedSearchParams.source,
status: parsedSearchParams.status,
},
{
keepPreviousData: true,
},
);
const onPaginationChange = (page: number, perPage: number) => {
updateSearchParams({
page,
perPage,
});
};
const results = data ?? {
data: [],
perPage: 10,
currentPage: 1,
totalPages: 1,
};
const columns = useMemo(() => {
return [
{
header: _(msg`Created`),
accessorKey: 'createdAt',
cell: ({ row }) =>
i18n.date(row.original.createdAt, { ...DateTime.DATETIME_SHORT, hourCycle: 'h12' }),
},
{
header: _(msg`Title`),
cell: ({ row }) => <DataTableTitle row={row.original} teamUrl={team?.url} />,
},
{
header: _(msg`Recipient`),
accessorKey: 'recipient',
cell: ({ row }) => (
<StackAvatarsWithTooltip
recipients={row.original.Recipient}
documentStatus={row.original.status}
/>
),
},
{
header: _(msg`Status`),
accessorKey: 'status',
cell: ({ row }) => <DocumentStatus status={row.getValue('status')} />,
size: 140,
},
{
header: () => (
<div className="flex flex-row items-center">
<Trans>Source</Trans>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="text-foreground max-w-md space-y-2 !p-0">
<ul className="text-muted-foreground space-y-0.5 divide-y [&>li]:p-4">
<li>
<h2 className="mb-2 flex flex-row items-center font-semibold">
<Trans>Template</Trans>
</h2>
<p>
<Trans>
This document was created by you or a team member using the template above.
</Trans>
</p>
</li>
<li>
<h2 className="mb-2 flex flex-row items-center font-semibold">
<Trans>Direct Link</Trans>
</h2>
<p>
<Trans>This document was created using a direct link.</Trans>
</p>
</li>
</ul>
</TooltipContent>
</Tooltip>
</div>
),
accessorKey: 'type',
cell: ({ row }) => (
<div className="flex flex-row items-center">
{_(DOCUMENT_SOURCE_LABELS[row.original.source])}
</div>
),
},
{
id: 'actions',
header: _(msg`Actions`),
cell: ({ row }) => (
<div className="flex items-center space-x-2">
<DataTableActionButton team={team} row={row.original} />
<DataTableActionDropdown team={team} row={row.original} />
</div>
),
},
] satisfies DataTableColumnDef<(typeof results)['data'][number]>[];
}, []);
return (
<div>
<div className="mb-4 flex flex-row space-x-4">
<DocumentSearch />
<SearchParamSelector
paramKey="status"
isValueValid={(value) =>
[...DocumentStatusEnum.COMPLETED].includes(value as unknown as string)
}
>
<SelectItem value="all">
<Trans>Any Status</Trans>
</SelectItem>
<SelectItem value={DocumentStatusEnum.COMPLETED}>
<Trans>Completed</Trans>
</SelectItem>
<SelectItem value={DocumentStatusEnum.PENDING}>
<Trans>Pending</Trans>
</SelectItem>
<SelectItem value={DocumentStatusEnum.DRAFT}>
<Trans>Draft</Trans>
</SelectItem>
</SearchParamSelector>
<SearchParamSelector
paramKey="source"
isValueValid={(value) =>
[...DocumentSource.TEMPLATE].includes(value as unknown as string)
}
>
<SelectItem value="all">
<Trans>Any Source</Trans>
</SelectItem>
<SelectItem value={DocumentSource.TEMPLATE}>
<Trans>Template</Trans>
</SelectItem>
<SelectItem value={DocumentSource.TEMPLATE_DIRECT_LINK}>
<Trans>Direct Link</Trans>
</SelectItem>
</SearchParamSelector>
<PeriodSelector />
</div>
<DataTable
columns={columns}
data={results.data}
perPage={results.perPage}
currentPage={results.currentPage}
totalPages={results.totalPages}
onPaginationChange={onPaginationChange}
error={{
enable: isLoadingError,
}}
skeleton={{
enable: isLoading && isInitialLoading,
rows: 3,
component: (
<>
<TableCell>
<Skeleton className="h-4 w-12 rounded-full" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-24 rounded-full" />
</TableCell>
<TableCell className="py-4 pr-4">
<Skeleton className="h-12 w-12 flex-shrink-0 rounded-full" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-12 rounded-full" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-12 rounded-full" />
</TableCell>
<TableCell>
<div className="flex flex-row justify-end space-x-2">
<Skeleton className="h-10 w-20 rounded" />
</div>
</TableCell>
</>
),
}}
>
{(table) => <DataTablePagination additionalInformation="VisibleCount" table={table} />}
</DataTable>
</div>
);
};

View File

@ -0,0 +1,66 @@
'use client';
import { useMemo } from 'react';
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { DateTime } from 'luxon';
import { useIsMounted } from '@documenso/lib/client-only/hooks/use-is-mounted';
import type { Template, User } from '@documenso/prisma/client';
export type TemplatePageViewInformationProps = {
userId: number;
template: Template & {
User: Pick<User, 'id' | 'name' | 'email'>;
};
};
export const TemplatePageViewInformation = ({
template,
userId,
}: TemplatePageViewInformationProps) => {
const isMounted = useIsMounted();
const { _, i18n } = useLingui();
const templateInformation = useMemo(() => {
return [
{
description: msg`Uploaded by`,
value: userId === template.userId ? _(msg`You`) : template.User.name ?? template.User.email,
},
{
description: msg`Created`,
value: i18n.date(template.createdAt, { dateStyle: 'medium' }),
},
{
description: msg`Last modified`,
value: DateTime.fromJSDate(template.updatedAt)
.setLocale(i18n.locales?.[0] || i18n.locale)
.toRelative(),
},
];
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isMounted, template, userId]);
return (
<section className="dark:bg-background text-foreground border-border bg-widget flex flex-col rounded-xl border">
<h1 className="px-4 py-3 font-medium">
<Trans>Information</Trans>
</h1>
<ul className="divide-y border-t">
{templateInformation.map((item, i) => (
<li
key={i}
className="flex items-center justify-between px-4 py-2.5 text-sm last:border-b"
>
<span className="text-muted-foreground">{_(item.description)}</span>
<span>{item.value}</span>
</li>
))}
</ul>
</section>
);
};

View File

@ -0,0 +1,163 @@
'use client';
import Link from 'next/link';
import { Trans } from '@lingui/macro';
import { Loader } from 'lucide-react';
import { DateTime } from 'luxon';
import { match } from 'ts-pattern';
import { DocumentSource } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
export type TemplatePageViewRecentActivityProps = {
templateId: number;
teamId?: number;
documentRootPath: string;
};
export const TemplatePageViewRecentActivity = ({
templateId,
teamId,
documentRootPath,
}: TemplatePageViewRecentActivityProps) => {
const { data, isLoading, isLoadingError, refetch } = trpc.document.findDocuments.useQuery({
templateId,
teamId,
orderBy: {
column: 'createdAt',
direction: 'asc',
},
perPage: 5,
});
const results = data ?? {
data: [],
perPage: 10,
currentPage: 1,
totalPages: 1,
};
return (
<section className="dark:bg-background border-border bg-widget flex flex-col rounded-xl border">
<div className="flex flex-row items-center justify-between border-b px-4 py-3">
<h1 className="text-foreground font-medium">
<Trans>Recent documents</Trans>
</h1>
{/* Can add dropdown menu here for additional options. */}
</div>
{isLoading && (
<div className="flex h-full items-center justify-center py-16">
<Loader className="text-muted-foreground h-6 w-6 animate-spin" />
</div>
)}
{isLoadingError && (
<div className="flex h-full flex-col items-center justify-center py-16">
<p className="text-foreground/80 text-sm">
<Trans>Unable to load documents</Trans>
</p>
<button
onClick={async () => refetch()}
className="text-foreground/70 hover:text-muted-foreground mt-2 text-sm"
>
<Trans>Click here to retry</Trans>
</button>
</div>
)}
{data && (
<>
<ul role="list" className="space-y-6 p-4">
{data.data.length > 0 && results.totalPages > 1 && (
<li className="relative flex gap-x-4">
<div className="absolute -bottom-6 left-0 top-0 flex w-6 justify-center">
<div className="bg-border w-px" />
</div>
<div className="bg-widget relative flex h-6 w-6 flex-none items-center justify-center">
<div className="bg-widget h-1.5 w-1.5 rounded-full ring-1 ring-gray-300 dark:ring-neutral-600" />
</div>
<button
onClick={() => {
window.scrollTo({
top: document.getElementById('documents')?.offsetTop,
behavior: 'smooth',
});
}}
className="text-foreground/70 hover:text-muted-foreground flex items-center text-xs"
>
<Trans>View more</Trans>
</button>
</li>
)}
{results.data.length === 0 && (
<div className="flex items-center justify-center py-4">
<p className="text-muted-foreground/70 text-sm">
<Trans>No recent documents</Trans>
</p>
</div>
)}
{results.data.map((document, documentIndex) => (
<li key={document.id} className="relative flex gap-x-4">
<div
className={cn(
documentIndex === results.data.length - 1 ? 'h-6' : '-bottom-6',
'absolute left-0 top-0 flex w-6 justify-center',
)}
>
<div className="bg-border w-px" />
</div>
<div className="bg-widget text-foreground/40 relative flex h-6 w-6 flex-none items-center justify-center">
<div className="bg-widget h-1.5 w-1.5 rounded-full ring-1 ring-gray-300 dark:ring-neutral-600" />
</div>
<Link
href={`${documentRootPath}/${document.id}`}
className="text-muted-foreground dark:text-muted-foreground/70 flex-auto truncate py-0.5 text-xs leading-5"
>
{match(document.source)
.with(DocumentSource.DOCUMENT, DocumentSource.TEMPLATE, () => (
<Trans>
Document created by <span className="font-bold">{document.User.name}</span>
</Trans>
))
.with(DocumentSource.TEMPLATE_DIRECT_LINK, () => (
<Trans>
Document created using a <span className="font-bold">direct link</span>
</Trans>
))
.exhaustive()}
</Link>
<time className="text-muted-foreground dark:text-muted-foreground/70 flex-none py-0.5 text-xs leading-5">
{DateTime.fromJSDate(document.createdAt).toRelative({ style: 'short' })}
</time>
</li>
))}
</ul>
<Button
className="mx-4 mb-4"
onClick={() => {
window.scrollTo({
top: document.getElementById('documents')?.offsetTop,
behavior: 'smooth',
});
}}
>
<Trans>View all related documents</Trans>
</Button>
</>
)}
</section>
);
};

View File

@ -0,0 +1,69 @@
import Link from 'next/link';
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { PenIcon, PlusIcon } from 'lucide-react';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import type { Recipient, Template } from '@documenso/prisma/client';
import { AvatarWithText } from '@documenso/ui/primitives/avatar';
export type TemplatePageViewRecipientsProps = {
template: Template & {
Recipient: Recipient[];
};
templateRootPath: string;
};
export const TemplatePageViewRecipients = ({
template,
templateRootPath,
}: TemplatePageViewRecipientsProps) => {
const { _ } = useLingui();
const recipients = template.Recipient;
return (
<section className="dark:bg-background border-border bg-widget flex flex-col rounded-xl border">
<div className="flex flex-row items-center justify-between px-4 py-3">
<h1 className="text-foreground font-medium">
<Trans>Recipients</Trans>
</h1>
<Link
href={`${templateRootPath}/${template.id}/edit?step=signers`}
title={_(msg`Modify recipients`)}
className="flex flex-row items-center justify-between"
>
{recipients.length === 0 ? (
<PlusIcon className="ml-2 h-4 w-4" />
) : (
<PenIcon className="ml-2 h-3 w-3" />
)}
</Link>
</div>
<ul className="text-muted-foreground divide-y border-t">
{recipients.length === 0 && (
<li className="flex flex-col items-center justify-center py-6 text-sm">
<Trans>No recipients</Trans>
</li>
)}
{recipients.map((recipient) => (
<li key={recipient.id} className="flex items-center justify-between px-4 py-2.5 text-sm">
<AvatarWithText
avatarFallback={recipient.email.slice(0, 1).toUpperCase()}
primaryText={<p className="text-muted-foreground text-sm">{recipient.email}</p>}
secondaryText={
<p className="text-muted-foreground/70 text-xs">
{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
</p>
}
/>
</li>
))}
</ul>
</section>
);
};

View File

@ -1,22 +1,28 @@
import React from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
import { Trans } from '@lingui/macro'; import { Trans } from '@lingui/macro';
import { ChevronLeft } from 'lucide-react'; import { ChevronLeft, LucideEdit } from 'lucide-react';
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session'; import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getTemplateWithDetailsById } from '@documenso/lib/server-only/template/get-template-with-details-by-id'; import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
import { formatTemplatesPath } from '@documenso/lib/utils/teams'; import { formatDocumentsPath, formatTemplatesPath } from '@documenso/lib/utils/teams';
import type { Team } from '@documenso/prisma/client'; import { DocumentSigningOrder, SigningStatus, type Team } from '@documenso/prisma/client';
import { Button } from '@documenso/ui/primitives/button';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
import { DocumentReadOnlyFields } from '~/components/document/document-read-only-fields';
import { TemplateType } from '~/components/formatter/template-type'; import { TemplateType } from '~/components/formatter/template-type';
import { DataTableActionDropdown } from '../data-table-action-dropdown';
import { TemplateDirectLinkBadge } from '../template-direct-link-badge'; import { TemplateDirectLinkBadge } from '../template-direct-link-badge';
import { EditTemplateForm } from './edit-template'; import { UseTemplateDialog } from '../use-template-dialog';
import { TemplateDirectLinkDialogWrapper } from './template-direct-link-dialog-wrapper'; import { TemplateDirectLinkDialogWrapper } from './template-direct-link-dialog-wrapper';
import { TemplatePageViewDocumentsTable } from './template-page-view-documents-table';
import { TemplatePageViewInformation } from './template-page-view-information';
import { TemplatePageViewRecentActivity } from './template-page-view-recent-activity';
import { TemplatePageViewRecipients } from './template-page-view-recipients';
export type TemplatePageViewProps = { export type TemplatePageViewProps = {
params: { params: {
@ -30,6 +36,7 @@ export const TemplatePageView = async ({ params, team }: TemplatePageViewProps)
const templateId = Number(id); const templateId = Number(id);
const templateRootPath = formatTemplatesPath(team?.url); const templateRootPath = formatTemplatesPath(team?.url);
const documentRootPath = formatDocumentsPath(team?.url);
if (!templateId || Number.isNaN(templateId)) { if (!templateId || Number.isNaN(templateId)) {
redirect(templateRootPath); redirect(templateRootPath);
@ -37,29 +44,51 @@ export const TemplatePageView = async ({ params, team }: TemplatePageViewProps)
const { user } = await getRequiredServerComponentSession(); const { user } = await getRequiredServerComponentSession();
const template = await getTemplateWithDetailsById({ const template = await getTemplateById({
id: templateId, id: templateId,
userId: user.id, userId: user.id,
teamId: team?.id,
}).catch(() => null); }).catch(() => null);
if (!template || !template.templateDocumentData) { if (!template || !template.templateDocumentData || (template?.teamId && !team?.url)) {
redirect(templateRootPath); redirect(templateRootPath);
} }
const isTemplateEnterprise = await isUserEnterprise({ const { templateDocumentData, Field, Recipient: recipients, templateMeta } = template;
userId: user.id,
teamId: team?.id, // Remap to fit the DocumentReadOnlyFields component.
const readOnlyFields = Field.map((field) => {
const recipient = recipients.find((recipient) => recipient.id === field.recipientId) || {
name: '',
email: '',
signingStatus: SigningStatus.NOT_SIGNED,
};
return {
...field,
Recipient: recipient,
Signature: null,
};
}); });
return ( const mockedDocumentMeta = templateMeta
<div className="mx-auto -mt-4 max-w-screen-xl px-4 md:px-8"> ? {
<div className="flex flex-col justify-between sm:flex-row"> typedSignatureEnabled: false,
<div> ...templateMeta,
<Link href="/templates" className="flex items-center text-[#7AC455] hover:opacity-80"> signingOrder: templateMeta.signingOrder || DocumentSigningOrder.SEQUENTIAL,
<ChevronLeft className="mr-2 inline-block h-5 w-5" /> documentId: 0,
<Trans>Templates</Trans> }
</Link> : undefined;
return (
<div className="mx-auto -mt-4 w-full max-w-screen-xl px-4 md:px-8">
<Link href={templateRootPath} className="flex items-center text-[#7AC455] hover:opacity-80">
<ChevronLeft className="mr-2 inline-block h-5 w-5" />
<Trans>Templates</Trans>
</Link>
<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 truncate text-2xl font-semibold md:text-3xl" title={template.title}>
{template.title} {template.title}
</h1> </h1>
@ -77,17 +106,97 @@ export const TemplatePageView = async ({ params, team }: TemplatePageViewProps)
</div> </div>
</div> </div>
<div className="mt-2 sm:mt-0 sm:self-end"> <div className="mt-2 flex flex-row space-x-4 sm:mt-0 sm:self-end">
<TemplateDirectLinkDialogWrapper template={template} /> <TemplateDirectLinkDialogWrapper template={template} />
<Button className="w-full" asChild>
<Link href={`${templateRootPath}/${template.id}/edit`}>
<LucideEdit className="mr-1.5 h-3.5 w-3.5" />
<Trans>Edit Template</Trans>
</Link>
</Button>
</div> </div>
</div> </div>
<EditTemplateForm <div className="mt-6 grid w-full grid-cols-12 gap-8">
className="mt-6" <Card
initialTemplate={template} className="relative col-span-12 rounded-xl before:rounded-xl lg:col-span-6 xl:col-span-7"
templateRootPath={templateRootPath} gradient
isEnterprise={isTemplateEnterprise} >
/> <CardContent className="p-2">
<LazyPDFViewer
document={template}
key={template.id}
documentData={templateDocumentData}
/>
</CardContent>
</Card>
<DocumentReadOnlyFields
fields={readOnlyFields}
showFieldStatus={false}
documentMeta={mockedDocumentMeta}
/>
<div className="col-span-12 lg:col-span-6 xl:col-span-5">
<div className="space-y-6">
<section className="border-border bg-widget flex flex-col rounded-xl border pb-4 pt-6">
<div className="flex flex-row items-center justify-between px-4">
<h3 className="text-foreground text-2xl font-semibold">
<Trans>Template</Trans>
</h3>
<div>
<DataTableActionDropdown
row={template}
teamId={team?.id}
templateRootPath={templateRootPath}
/>
</div>
</div>
<p className="text-muted-foreground mt-2 px-4 text-sm ">
<Trans>Manage and view template</Trans>
</p>
<div className="mt-4 border-t px-4 pt-4">
<UseTemplateDialog
templateId={template.id}
templateSigningOrder={template.templateMeta?.signingOrder}
recipients={template.Recipient}
documentRootPath={documentRootPath}
trigger={
<Button className="w-full">
<Trans>Use</Trans>
</Button>
}
/>
</div>
</section>
{/* Template information section. */}
<TemplatePageViewInformation template={template} userId={user.id} />
{/* Recipients section. */}
<TemplatePageViewRecipients template={template} templateRootPath={templateRootPath} />
{/* Recent activity section. */}
<TemplatePageViewRecentActivity
documentRootPath={documentRootPath}
templateId={template.id}
teamId={team?.id}
/>
</div>
</div>
</div>
<div className="mt-16" id="documents">
<h1 className="mb-4 text-2xl font-bold">
<Trans>Documents created from template</Trans>
</h1>
<TemplatePageViewDocumentsTable team={team} templateId={template.id} />
</div>
</div> </div>
); );
}; };

View File

@ -8,7 +8,7 @@ import { Trans } from '@lingui/macro';
import { Copy, Edit, MoreHorizontal, MoveRight, Share2Icon, Trash2 } from 'lucide-react'; import { Copy, Edit, MoreHorizontal, MoveRight, Share2Icon, Trash2 } from 'lucide-react';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import { type FindTemplateRow } from '@documenso/lib/server-only/template/find-templates'; import type { Recipient, Template, TemplateDirectLink } from '@documenso/prisma/client';
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@ -23,7 +23,10 @@ import { MoveTemplateDialog } from './move-template-dialog';
import { TemplateDirectLinkDialog } from './template-direct-link-dialog'; import { TemplateDirectLinkDialog } from './template-direct-link-dialog';
export type DataTableActionDropdownProps = { export type DataTableActionDropdownProps = {
row: FindTemplateRow; row: Template & {
directLink?: Pick<TemplateDirectLink, 'token' | 'enabled'> | null;
Recipient: Recipient[];
};
templateRootPath: string; templateRootPath: string;
teamId?: number; teamId?: number;
}; };
@ -57,7 +60,7 @@ export const DataTableActionDropdown = ({
<DropdownMenuLabel>Action</DropdownMenuLabel> <DropdownMenuLabel>Action</DropdownMenuLabel>
<DropdownMenuItem disabled={!isOwner && !isTeamTemplate} asChild> <DropdownMenuItem disabled={!isOwner && !isTeamTemplate} asChild>
<Link href={`${templateRootPath}/${row.id}`}> <Link href={`${templateRootPath}/${row.id}/edit`}>
<Edit className="mr-2 h-4 w-4" /> <Edit className="mr-2 h-4 w-4" />
<Trans>Edit</Trans> <Trans>Edit</Trans>
</Link> </Link>

View File

@ -124,7 +124,7 @@ export const TemplatesDataTable = ({
accessorKey: 'type', accessorKey: 'type',
cell: ({ row }) => ( cell: ({ row }) => (
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
<TemplateType type="PRIVATE" /> <TemplateType type={row.original.type} />
{row.original.directLink?.token && ( {row.original.directLink?.token && (
<TemplateDirectLinkBadge <TemplateDirectLinkBadge

View File

@ -73,7 +73,7 @@ export const NewTemplateDialog = ({ teamId, templateRootPath }: NewTemplateDialo
setShowNewTemplateDialog(false); setShowNewTemplateDialog(false);
router.push(`${templateRootPath}/${id}`); router.push(`${templateRootPath}/${id}/edit`);
} catch { } catch {
toast({ toast({
title: _(msg`Something went wrong`), title: _(msg`Something went wrong`),

View File

@ -1,3 +1,5 @@
'use client';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@ -92,6 +94,7 @@ export type UseTemplateDialogProps = {
templateSigningOrder?: DocumentSigningOrder | null; templateSigningOrder?: DocumentSigningOrder | null;
recipients: Recipient[]; recipients: Recipient[];
documentRootPath: string; documentRootPath: string;
trigger?: React.ReactNode;
}; };
export function UseTemplateDialog({ export function UseTemplateDialog({
@ -99,6 +102,7 @@ export function UseTemplateDialog({
documentRootPath, documentRootPath,
templateId, templateId,
templateSigningOrder, templateSigningOrder,
trigger,
}: UseTemplateDialogProps) { }: UseTemplateDialogProps) {
const router = useRouter(); const router = useRouter();
@ -186,10 +190,12 @@ export function UseTemplateDialog({
return ( return (
<Dialog open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}> <Dialog open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="outline" className="bg-background"> {trigger || (
<Plus className="-ml-1 mr-2 h-4 w-4" /> <Button variant="outline" className="bg-background">
<Trans>Use Template</Trans> <Plus className="-ml-1 mr-2 h-4 w-4" />
</Button> <Trans>Use Template</Trans>
</Button>
)}
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-lg"> <DialogContent className="sm:max-w-lg">
<DialogHeader> <DialogHeader>

View File

@ -1,5 +1,5 @@
'use client'; import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import type { DateTimeFormatOptions } from 'luxon'; import type { DateTimeFormatOptions } from 'luxon';
import { UAParser } from 'ua-parser-js'; import { UAParser } from 'ua-parser-js';
@ -25,7 +25,12 @@ const dateFormat: DateTimeFormatOptions = {
hourCycle: 'h12', hourCycle: 'h12',
}; };
/**
* DO NOT USE TRANS. YOU MUST USE _ FOR THIS FILE AND ALL CHILDREN COMPONENTS.
*/
export const AuditLogDataTable = ({ logs }: AuditLogDataTableProps) => { export const AuditLogDataTable = ({ logs }: AuditLogDataTableProps) => {
const { _ } = useLingui();
const parser = new UAParser(); const parser = new UAParser();
const uppercaseFistLetter = (text: string) => { const uppercaseFistLetter = (text: string) => {
@ -36,11 +41,11 @@ export const AuditLogDataTable = ({ logs }: AuditLogDataTableProps) => {
<Table overflowHidden> <Table overflowHidden>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead>Time</TableHead> <TableHead>{_(msg`Time`)}</TableHead>
<TableHead>User</TableHead> <TableHead>{_(msg`User`)}</TableHead>
<TableHead>Action</TableHead> <TableHead>{_(msg`Action`)}</TableHead>
<TableHead>IP Address</TableHead> <TableHead>{_(msg`IP Address`)}</TableHead>
<TableHead>Browser</TableHead> <TableHead>{_(msg`Browser`)}</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
@ -74,7 +79,7 @@ export const AuditLogDataTable = ({ logs }: AuditLogDataTableProps) => {
</TableCell> </TableCell>
<TableCell> <TableCell>
{uppercaseFistLetter(formatDocumentAuditLogAction(log).description)} {uppercaseFistLetter(formatDocumentAuditLogAction(_, log).description)}
</TableCell> </TableCell>
<TableCell>{log.ipAddress}</TableCell> <TableCell>{log.ipAddress}</TableCell>

View File

@ -2,13 +2,18 @@ import React from 'react';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { APP_I18N_OPTIONS } from '@documenso/lib/constants/i18n'; import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
import { RECIPIENT_ROLES_DESCRIPTION_ENG } from '@documenso/lib/constants/recipient-roles'; import { DOCUMENT_STATUS } from '@documenso/lib/constants/document';
import { APP_I18N_OPTIONS, ZSupportedLanguageCodeSchema } from '@documenso/lib/constants/i18n';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import { getEntireDocument } from '@documenso/lib/server-only/admin/get-entire-document'; import { getEntireDocument } from '@documenso/lib/server-only/admin/get-entire-document';
import { decryptSecondaryData } from '@documenso/lib/server-only/crypto/decrypt'; import { decryptSecondaryData } from '@documenso/lib/server-only/crypto/decrypt';
import { findDocumentAuditLogs } from '@documenso/lib/server-only/document/find-document-audit-logs'; import { findDocumentAuditLogs } from '@documenso/lib/server-only/document/find-document-audit-logs';
import { dynamicActivate } from '@documenso/lib/utils/i18n';
import { Card, CardContent } from '@documenso/ui/primitives/card'; import { Card, CardContent } from '@documenso/ui/primitives/card';
import { Logo } from '~/components/branding/logo'; import { Logo } from '~/components/branding/logo';
@ -21,7 +26,17 @@ type AuditLogProps = {
}; };
}; };
/**
* DO NOT USE TRANS. YOU MUST USE _ FOR THIS FILE AND ALL CHILDREN COMPONENTS.
*
* Cannot use dynamicActivate by itself to translate this specific page and all
* children components because `not-found.tsx` page runs and overrides the i18n.
*/
export default async function AuditLog({ searchParams }: AuditLogProps) { export default async function AuditLog({ searchParams }: AuditLogProps) {
const { i18n } = await setupI18nSSR();
const { _ } = useLingui();
const { d } = searchParams; const { d } = searchParams;
if (typeof d !== 'string' || !d) { if (typeof d !== 'string' || !d) {
@ -44,6 +59,10 @@ export default async function AuditLog({ searchParams }: AuditLogProps) {
return redirect('/'); return redirect('/');
} }
const documentLanguage = ZSupportedLanguageCodeSchema.parse(document.documentMeta?.language);
await dynamicActivate(i18n, documentLanguage);
const { data: auditLogs } = await findDocumentAuditLogs({ const { data: auditLogs } = await findDocumentAuditLogs({
documentId: documentId, documentId: documentId,
userId: document.userId, userId: document.userId,
@ -53,31 +72,35 @@ export default async function AuditLog({ searchParams }: AuditLogProps) {
return ( return (
<div className="print-provider pointer-events-none mx-auto max-w-screen-md"> <div className="print-provider pointer-events-none mx-auto max-w-screen-md">
<div className="flex items-center"> <div className="flex items-center">
<h1 className="my-8 text-2xl font-bold">Version History</h1> <h1 className="my-8 text-2xl font-bold">{_(msg`Version History`)}</h1>
</div> </div>
<Card> <Card>
<CardContent className="grid grid-cols-2 gap-4 p-6 text-sm print:text-xs"> <CardContent className="grid grid-cols-2 gap-4 p-6 text-sm print:text-xs">
<p> <p>
<span className="font-medium">Document ID</span> <span className="font-medium">{_(msg`Document ID`)}</span>
<span className="mt-1 block break-words">{document.id}</span> <span className="mt-1 block break-words">{document.id}</span>
</p> </p>
<p> <p>
<span className="font-medium">Enclosed Document</span> <span className="font-medium">{_(msg`Enclosed Document`)}</span>
<span className="mt-1 block break-words">{document.title}</span> <span className="mt-1 block break-words">{document.title}</span>
</p> </p>
<p> <p>
<span className="font-medium">Status</span> <span className="font-medium">{_(msg`Status`)}</span>
<span className="mt-1 block">{document.deletedAt ? 'DELETED' : document.status}</span> <span className="mt-1 block">
{_(
document.deletedAt ? msg`Deleted` : DOCUMENT_STATUS[document.status].description,
).toUpperCase()}
</span>
</p> </p>
<p> <p>
<span className="font-medium">Owner</span> <span className="font-medium">{_(msg`Owner`)}</span>
<span className="mt-1 block break-words"> <span className="mt-1 block break-words">
{document.User.name} ({document.User.email}) {document.User.name} ({document.User.email})
@ -85,7 +108,7 @@ export default async function AuditLog({ searchParams }: AuditLogProps) {
</p> </p>
<p> <p>
<span className="font-medium">Created At</span> <span className="font-medium">{_(msg`Created At`)}</span>
<span className="mt-1 block"> <span className="mt-1 block">
{DateTime.fromJSDate(document.createdAt) {DateTime.fromJSDate(document.createdAt)
@ -95,7 +118,7 @@ export default async function AuditLog({ searchParams }: AuditLogProps) {
</p> </p>
<p> <p>
<span className="font-medium">Last Updated</span> <span className="font-medium">{_(msg`Last Updated`)}</span>
<span className="mt-1 block"> <span className="mt-1 block">
{DateTime.fromJSDate(document.updatedAt) {DateTime.fromJSDate(document.updatedAt)
@ -105,7 +128,7 @@ export default async function AuditLog({ searchParams }: AuditLogProps) {
</p> </p>
<p> <p>
<span className="font-medium">Time Zone</span> <span className="font-medium">{_(msg`Time Zone`)}</span>
<span className="mt-1 block break-words"> <span className="mt-1 block break-words">
{document.documentMeta?.timezone ?? 'N/A'} {document.documentMeta?.timezone ?? 'N/A'}
@ -113,13 +136,13 @@ export default async function AuditLog({ searchParams }: AuditLogProps) {
</p> </p>
<div> <div>
<p className="font-medium">Recipients</p> <p className="font-medium">{_(msg`Recipients`)}</p>
<ul className="mt-1 list-inside list-disc"> <ul className="mt-1 list-inside list-disc">
{document.Recipient.map((recipient) => ( {document.Recipient.map((recipient) => (
<li key={recipient.id}> <li key={recipient.id}>
<span className="text-muted-foreground"> <span className="text-muted-foreground">
[{RECIPIENT_ROLES_DESCRIPTION_ENG[recipient.role].roleName}] [{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}]
</span>{' '} </span>{' '}
{recipient.name} ({recipient.email}) {recipient.name} ({recipient.email})
</li> </li>

View File

@ -2,20 +2,24 @@ import React from 'react';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import { UAParser } from 'ua-parser-js'; import { UAParser } from 'ua-parser-js';
import { APP_I18N_OPTIONS } from '@documenso/lib/constants/i18n'; import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
import { APP_I18N_OPTIONS, ZSupportedLanguageCodeSchema } from '@documenso/lib/constants/i18n';
import { import {
RECIPIENT_ROLES_DESCRIPTION_ENG, RECIPIENT_ROLES_DESCRIPTION,
RECIPIENT_ROLE_SIGNING_REASONS_ENG, RECIPIENT_ROLE_SIGNING_REASONS,
} from '@documenso/lib/constants/recipient-roles'; } from '@documenso/lib/constants/recipient-roles';
import { getEntireDocument } from '@documenso/lib/server-only/admin/get-entire-document'; import { getEntireDocument } from '@documenso/lib/server-only/admin/get-entire-document';
import { decryptSecondaryData } from '@documenso/lib/server-only/crypto/decrypt'; import { decryptSecondaryData } from '@documenso/lib/server-only/crypto/decrypt';
import { getDocumentCertificateAuditLogs } from '@documenso/lib/server-only/document/get-document-certificate-audit-logs'; import { getDocumentCertificateAuditLogs } from '@documenso/lib/server-only/document/get-document-certificate-audit-logs';
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs'; import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth'; import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
import { dynamicActivate } from '@documenso/lib/utils/i18n';
import { FieldType } from '@documenso/prisma/client'; import { FieldType } from '@documenso/prisma/client';
import { Card, CardContent } from '@documenso/ui/primitives/card'; import { Card, CardContent } from '@documenso/ui/primitives/card';
import { import {
@ -36,11 +40,21 @@ type SigningCertificateProps = {
}; };
const FRIENDLY_SIGNING_REASONS = { const FRIENDLY_SIGNING_REASONS = {
['__OWNER__']: `I am the owner of this document`, ['__OWNER__']: msg`I am the owner of this document`,
...RECIPIENT_ROLE_SIGNING_REASONS_ENG, ...RECIPIENT_ROLE_SIGNING_REASONS,
}; };
/**
* DO NOT USE TRANS. YOU MUST USE _ FOR THIS FILE AND ALL CHILDREN COMPONENTS.
*
* Cannot use dynamicActivate by itself to translate this specific page and all
* children components because `not-found.tsx` page runs and overrides the i18n.
*/
export default async function SigningCertificate({ searchParams }: SigningCertificateProps) { export default async function SigningCertificate({ searchParams }: SigningCertificateProps) {
const { i18n } = await setupI18nSSR();
const { _ } = useLingui();
const { d } = searchParams; const { d } = searchParams;
if (typeof d !== 'string' || !d) { if (typeof d !== 'string' || !d) {
@ -63,6 +77,10 @@ export default async function SigningCertificate({ searchParams }: SigningCertif
return redirect('/'); return redirect('/');
} }
const documentLanguage = ZSupportedLanguageCodeSchema.parse(document.documentMeta?.language);
await dynamicActivate(i18n, documentLanguage);
const auditLogs = await getDocumentCertificateAuditLogs({ const auditLogs = await getDocumentCertificateAuditLogs({
id: documentId, id: documentId,
}); });
@ -98,17 +116,17 @@ export default async function SigningCertificate({ searchParams }: SigningCertif
}); });
let authLevel = match(extractedAuthMethods.derivedRecipientActionAuth) let authLevel = match(extractedAuthMethods.derivedRecipientActionAuth)
.with('ACCOUNT', () => 'Account Re-Authentication') .with('ACCOUNT', () => _(msg`Account Re-Authentication`))
.with('TWO_FACTOR_AUTH', () => 'Two-Factor Re-Authentication') .with('TWO_FACTOR_AUTH', () => _(msg`Two-Factor Re-Authentication`))
.with('PASSKEY', () => 'Passkey Re-Authentication') .with('PASSKEY', () => _(msg`Passkey Re-Authentication`))
.with('EXPLICIT_NONE', () => 'Email') .with('EXPLICIT_NONE', () => _(msg`Email`))
.with(null, () => null) .with(null, () => null)
.exhaustive(); .exhaustive();
if (!authLevel) { if (!authLevel) {
authLevel = match(extractedAuthMethods.derivedRecipientAccessAuth) authLevel = match(extractedAuthMethods.derivedRecipientAccessAuth)
.with('ACCOUNT', () => 'Account Authentication') .with('ACCOUNT', () => _(msg`Account Authentication`))
.with(null, () => 'Email') .with(null, () => _(msg`Email`))
.exhaustive(); .exhaustive();
} }
@ -147,7 +165,7 @@ export default async function SigningCertificate({ searchParams }: SigningCertif
return ( return (
<div className="print-provider pointer-events-none mx-auto max-w-screen-md"> <div className="print-provider pointer-events-none mx-auto max-w-screen-md">
<div className="flex items-center"> <div className="flex items-center">
<h1 className="my-8 text-2xl font-bold">Signing Certificate</h1> <h1 className="my-8 text-2xl font-bold">{_(msg`Signing Certificate`)}</h1>
</div> </div>
<Card> <Card>
@ -155,9 +173,9 @@ export default async function SigningCertificate({ searchParams }: SigningCertif
<Table overflowHidden> <Table overflowHidden>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead>Signer Events</TableHead> <TableHead>{_(msg`Signer Events`)}</TableHead>
<TableHead>Signature</TableHead> <TableHead>{_(msg`Signature`)}</TableHead>
<TableHead>Details</TableHead> <TableHead>{_(msg`Details`)}</TableHead>
{/* <TableHead>Security</TableHead> */} {/* <TableHead>Security</TableHead> */}
</TableRow> </TableRow>
</TableHeader> </TableHeader>
@ -173,11 +191,11 @@ export default async function SigningCertificate({ searchParams }: SigningCertif
<div className="hyphens-auto break-words font-medium">{recipient.name}</div> <div className="hyphens-auto break-words font-medium">{recipient.name}</div>
<div className="break-all">{recipient.email}</div> <div className="break-all">{recipient.email}</div>
<p className="text-muted-foreground mt-2 text-sm print:text-xs"> <p className="text-muted-foreground mt-2 text-sm print:text-xs">
{RECIPIENT_ROLES_DESCRIPTION_ENG[recipient.role].roleName} {_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
</p> </p>
<p className="text-muted-foreground mt-2 text-sm print:text-xs"> <p className="text-muted-foreground mt-2 text-sm print:text-xs">
<span className="font-medium">Authentication Level:</span>{' '} <span className="font-medium">{_(msg`Authentication Level`)}:</span>{' '}
<span className="block">{getAuthenticationLevel(recipient.id)}</span> <span className="block">{getAuthenticationLevel(recipient.id)}</span>
</p> </p>
</TableCell> </TableCell>
@ -199,21 +217,21 @@ export default async function SigningCertificate({ searchParams }: SigningCertif
</div> </div>
<p className="text-muted-foreground mt-2 text-sm print:text-xs"> <p className="text-muted-foreground mt-2 text-sm print:text-xs">
<span className="font-medium">Signature ID:</span>{' '} <span className="font-medium">{_(msg`Signature ID`)}:</span>{' '}
<span className="block font-mono uppercase"> <span className="block font-mono uppercase">
{signature.secondaryId} {signature.secondaryId}
</span> </span>
</p> </p>
<p className="text-muted-foreground mt-2 text-sm print:text-xs"> <p className="text-muted-foreground mt-2 text-sm print:text-xs">
<span className="font-medium">IP Address:</span>{' '} <span className="font-medium">{_(msg`IP Address`)}:</span>{' '}
<span className="inline-block"> <span className="inline-block">
{logs.DOCUMENT_RECIPIENT_COMPLETED[0]?.ipAddress ?? 'Unknown'} {logs.DOCUMENT_RECIPIENT_COMPLETED[0]?.ipAddress ?? _(msg`Unknown`)}
</span> </span>
</p> </p>
<p className="text-muted-foreground mt-1 text-sm print:text-xs"> <p className="text-muted-foreground mt-1 text-sm print:text-xs">
<span className="font-medium">Device:</span>{' '} <span className="font-medium">{_(msg`Device`)}:</span>{' '}
<span className="inline-block"> <span className="inline-block">
{getDevice(logs.DOCUMENT_RECIPIENT_COMPLETED[0]?.userAgent)} {getDevice(logs.DOCUMENT_RECIPIENT_COMPLETED[0]?.userAgent)}
</span> </span>
@ -227,44 +245,46 @@ export default async function SigningCertificate({ searchParams }: SigningCertif
<TableCell truncate={false} className="w-[min-content] align-top"> <TableCell truncate={false} className="w-[min-content] align-top">
<div className="space-y-1"> <div className="space-y-1">
<p className="text-muted-foreground text-sm print:text-xs"> <p className="text-muted-foreground text-sm print:text-xs">
<span className="font-medium">Sent:</span>{' '} <span className="font-medium">{_(msg`Sent`)}:</span>{' '}
<span className="inline-block"> <span className="inline-block">
{logs.EMAIL_SENT[0] {logs.EMAIL_SENT[0]
? DateTime.fromJSDate(logs.EMAIL_SENT[0].createdAt) ? DateTime.fromJSDate(logs.EMAIL_SENT[0].createdAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale) .setLocale(APP_I18N_OPTIONS.defaultLocale)
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)') .toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')
: 'Unknown'} : _(msg`Unknown`)}
</span> </span>
</p> </p>
<p className="text-muted-foreground text-sm print:text-xs"> <p className="text-muted-foreground text-sm print:text-xs">
<span className="font-medium">Viewed:</span>{' '} <span className="font-medium">{_(msg`Viewed`)}:</span>{' '}
<span className="inline-block"> <span className="inline-block">
{logs.DOCUMENT_OPENED[0] {logs.DOCUMENT_OPENED[0]
? DateTime.fromJSDate(logs.DOCUMENT_OPENED[0].createdAt) ? DateTime.fromJSDate(logs.DOCUMENT_OPENED[0].createdAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale) .setLocale(APP_I18N_OPTIONS.defaultLocale)
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)') .toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')
: 'Unknown'} : _(msg`Unknown`)}
</span> </span>
</p> </p>
<p className="text-muted-foreground text-sm print:text-xs"> <p className="text-muted-foreground text-sm print:text-xs">
<span className="font-medium">Signed:</span>{' '} <span className="font-medium">{_(msg`Signed`)}:</span>{' '}
<span className="inline-block"> <span className="inline-block">
{logs.DOCUMENT_RECIPIENT_COMPLETED[0] {logs.DOCUMENT_RECIPIENT_COMPLETED[0]
? DateTime.fromJSDate(logs.DOCUMENT_RECIPIENT_COMPLETED[0].createdAt) ? DateTime.fromJSDate(logs.DOCUMENT_RECIPIENT_COMPLETED[0].createdAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale) .setLocale(APP_I18N_OPTIONS.defaultLocale)
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)') .toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')
: 'Unknown'} : _(msg`Unknown`)}
</span> </span>
</p> </p>
<p className="text-muted-foreground text-sm print:text-xs"> <p className="text-muted-foreground text-sm print:text-xs">
<span className="font-medium">Reason:</span>{' '} <span className="font-medium">{_(msg`Reason`)}:</span>{' '}
<span className="inline-block"> <span className="inline-block">
{isOwner(recipient.email) {_(
? FRIENDLY_SIGNING_REASONS['__OWNER__'] isOwner(recipient.email)
: FRIENDLY_SIGNING_REASONS[recipient.role]} ? FRIENDLY_SIGNING_REASONS['__OWNER__']
: FRIENDLY_SIGNING_REASONS[recipient.role],
)}
</span> </span>
</p> </p>
</div> </div>
@ -280,7 +300,7 @@ export default async function SigningCertificate({ searchParams }: SigningCertif
<div className="my-8 flex-row-reverse"> <div className="my-8 flex-row-reverse">
<div className="flex items-end justify-end gap-x-4"> <div className="flex items-end justify-end gap-x-4">
<p className="flex-shrink-0 text-sm font-medium print:text-xs"> <p className="flex-shrink-0 text-sm font-medium print:text-xs">
Signing certificate provided by: {_(msg`Signing certificate provided by`)}:
</p> </p>
<Logo className="max-h-6 print:max-h-4" /> <Logo className="max-h-6 print:max-h-4" />

View File

@ -0,0 +1,24 @@
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getTeamByUrl } from '@documenso/lib/server-only/team/get-team';
import type { TemplateEditPageViewProps } from '~/app/(dashboard)/templates/[id]/edit/template-edit-page-view';
import { TemplateEditPageView } from '~/app/(dashboard)/templates/[id]/edit/template-edit-page-view';
export type TeamsTemplateEditPageProps = {
params: TemplateEditPageViewProps['params'] & {
teamUrl: string;
};
};
export default async function TeamsTemplateEditPage({ params }: TeamsTemplateEditPageProps) {
await setupI18nSSR();
const { teamUrl } = params;
const { user } = await getRequiredServerComponentSession();
const team = await getTeamByUrl({ userId: user.id, teamUrl });
return <TemplateEditPageView params={params} team={team} />;
}

View File

@ -12,7 +12,7 @@ import { UAParser } from 'ua-parser-js';
import { DOCUMENT_AUDIT_LOG_EMAIL_FORMAT } from '@documenso/lib/constants/document-audit-logs'; import { DOCUMENT_AUDIT_LOG_EMAIL_FORMAT } from '@documenso/lib/constants/document-audit-logs';
import { DOCUMENT_AUTH_TYPES } from '@documenso/lib/constants/document-auth'; import { DOCUMENT_AUTH_TYPES } from '@documenso/lib/constants/document-auth';
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs'; import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
import { formatDocumentAuditLogActionString } from '@documenso/lib/utils/document-audit-logs'; import { formatDocumentAuditLogAction } from '@documenso/lib/utils/document-audit-logs';
import { trpc } from '@documenso/trpc/react'; import { trpc } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils'; import { cn } from '@documenso/ui/lib/utils';
import { Avatar, AvatarFallback } from '@documenso/ui/primitives/avatar'; import { Avatar, AvatarFallback } from '@documenso/ui/primitives/avatar';
@ -37,7 +37,7 @@ export const DocumentHistorySheet = ({
onMenuOpenChange, onMenuOpenChange,
children, children,
}: DocumentHistorySheetProps) => { }: DocumentHistorySheetProps) => {
const { i18n } = useLingui(); const { _, i18n } = useLingui();
const [isUserDetailsVisible, setIsUserDetailsVisible] = useState(false); const [isUserDetailsVisible, setIsUserDetailsVisible] = useState(false);
@ -152,7 +152,7 @@ export const DocumentHistorySheet = ({
<div> <div>
<p className="text-foreground text-xs font-bold"> <p className="text-foreground text-xs font-bold">
{formatDocumentAuditLogActionString(auditLog, userId)} {formatDocumentAuditLogAction(_, auditLog, userId).description}
</p> </p>
<p className="text-foreground/50 text-xs"> <p className="text-foreground/50 text-xs">
{DateTime.fromJSDate(auditLog.createdAt) {DateTime.fromJSDate(auditLog.createdAt)

View File

@ -2,8 +2,9 @@
import { useState } from 'react'; import { useState } from 'react';
import { Trans } from '@lingui/macro';
import { useLingui } from '@lingui/react'; import { useLingui } from '@lingui/react';
import { EyeOffIcon } from 'lucide-react'; import { Clock, EyeOffIcon } from 'lucide-react';
import { P, match } from 'ts-pattern'; import { P, match } from 'ts-pattern';
import { import {
@ -18,8 +19,10 @@ import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
import type { DocumentMeta } from '@documenso/prisma/client'; import type { DocumentMeta } from '@documenso/prisma/client';
import { FieldType, SigningStatus } from '@documenso/prisma/client'; import { FieldType, SigningStatus } from '@documenso/prisma/client';
import { FieldRootContainer } from '@documenso/ui/components/field/field'; import { FieldRootContainer } from '@documenso/ui/components/field/field';
import { SignatureIcon } from '@documenso/ui/icons/signature';
import { cn } from '@documenso/ui/lib/utils'; import { cn } from '@documenso/ui/lib/utils';
import { Avatar, AvatarFallback } from '@documenso/ui/primitives/avatar'; import { Avatar, AvatarFallback } from '@documenso/ui/primitives/avatar';
import { Badge } from '@documenso/ui/primitives/badge';
import { FRIENDLY_FIELD_TYPE } from '@documenso/ui/primitives/document-flow/types'; import { FRIENDLY_FIELD_TYPE } from '@documenso/ui/primitives/document-flow/types';
import { ElementVisible } from '@documenso/ui/primitives/element-visible'; import { ElementVisible } from '@documenso/ui/primitives/element-visible';
import { PopoverHover } from '@documenso/ui/primitives/popover'; import { PopoverHover } from '@documenso/ui/primitives/popover';
@ -27,9 +30,14 @@ import { PopoverHover } from '@documenso/ui/primitives/popover';
export type DocumentReadOnlyFieldsProps = { export type DocumentReadOnlyFieldsProps = {
fields: DocumentField[]; fields: DocumentField[];
documentMeta?: DocumentMeta; documentMeta?: DocumentMeta;
showFieldStatus?: boolean;
}; };
export const DocumentReadOnlyFields = ({ documentMeta, fields }: DocumentReadOnlyFieldsProps) => { export const DocumentReadOnlyFields = ({
documentMeta,
fields,
showFieldStatus = true,
}: DocumentReadOnlyFieldsProps) => {
const { _ } = useLingui(); const { _ } = useLingui();
const [hiddenFieldIds, setHiddenFieldIds] = useState<Record<string, boolean>>({}); const [hiddenFieldIds, setHiddenFieldIds] = useState<Record<string, boolean>>({});
@ -58,15 +66,37 @@ export const DocumentReadOnlyFields = ({ documentMeta, fields }: DocumentReadOnl
</Avatar> </Avatar>
} }
contentProps={{ contentProps={{
className: 'relative flex w-fit flex-col p-2.5 text-sm', className: 'relative flex w-fit flex-col p-4 text-sm',
}} }}
> >
<p className="font-semibold"> {showFieldStatus && (
{field.Recipient.signingStatus === SigningStatus.SIGNED ? 'Signed' : 'Pending'}{' '} <Badge
{parseMessageDescriptor(_, FRIENDLY_FIELD_TYPE[field.type]).toLowerCase()} field className="mx-auto mb-1 py-0.5"
variant={
field.Recipient.signingStatus === SigningStatus.SIGNED
? 'default'
: 'secondary'
}
>
{field.Recipient.signingStatus === SigningStatus.SIGNED ? (
<>
<SignatureIcon className="mr-1 h-3 w-3" />
<Trans>Signed</Trans>
</>
) : (
<>
<Clock className="mr-1 h-3 w-3" />
<Trans>Pending</Trans>
</>
)}
</Badge>
)}
<p className="text-center font-semibold">
<span>{parseMessageDescriptor(_, FRIENDLY_FIELD_TYPE[field.type])} field</span>
</p> </p>
<p className="text-muted-foreground text-xs"> <p className="text-muted-foreground mt-1 text-center text-xs">
{field.Recipient.name {field.Recipient.name
? `${field.Recipient.name} (${field.Recipient.email})` ? `${field.Recipient.name} (${field.Recipient.email})`
: field.Recipient.email}{' '} : field.Recipient.email}{' '}

View File

@ -0,0 +1,151 @@
'use client';
import { useEffect, useState } from 'react';
import { useSearchParams } from 'next/navigation';
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to-clipboard';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import { formatSigningLink } from '@documenso/lib/utils/recipients';
import type { Recipient } from '@documenso/prisma/client';
import { RecipientRole } from '@documenso/prisma/client';
import { CopyTextButton } from '@documenso/ui/components/common/copy-text-button';
import { AvatarWithText } from '@documenso/ui/primitives/avatar';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
export type DocumentRecipientLinkCopyDialogProps = {
trigger?: React.ReactNode;
recipients: Recipient[];
};
export const DocumentRecipientLinkCopyDialog = ({
trigger,
recipients,
}: DocumentRecipientLinkCopyDialogProps) => {
const { _ } = useLingui();
const { toast } = useToast();
const [, copy] = useCopyToClipboard();
const searchParams = useSearchParams();
const updateSearchParams = useUpdateSearchParams();
const [open, setOpen] = useState(false);
const actionSearchParam = searchParams?.get('action');
const onBulkCopy = async () => {
const generatedString = recipients
.filter((recipient) => recipient.role !== RecipientRole.CC)
.map((recipient) => `${recipient.email}\n${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`)
.join('\n\n');
await copy(generatedString).then(() => {
toast({
title: _(msg`Copied to clipboard`),
description: _(msg`All signing links have been copied to your clipboard.`),
});
});
};
useEffect(() => {
if (actionSearchParam === 'view-signing-links') {
setOpen(true);
updateSearchParams({ action: null });
}
}, [actionSearchParam, open, setOpen, updateSearchParams]);
return (
<Dialog open={open} onOpenChange={(value) => setOpen(value)}>
<DialogTrigger asChild onClick={(e) => e.stopPropagation()}>
{trigger}
</DialogTrigger>
<DialogContent position="center">
<DialogHeader>
<DialogTitle className="pb-0.5">
<Trans>Copy Signing Links</Trans>
</DialogTitle>
<DialogDescription>
<Trans>
You can copy and share these links to recipients so they can action the document.
</Trans>
</DialogDescription>
</DialogHeader>
<ul className="text-muted-foreground divide-y rounded-lg border">
{recipients.length === 0 && (
<li className="flex flex-col items-center justify-center py-6 text-sm">
<Trans>No recipients</Trans>
</li>
)}
{recipients.map((recipient) => (
<li key={recipient.id} className="flex items-center justify-between px-4 py-3 text-sm">
<AvatarWithText
avatarFallback={recipient.email.slice(0, 1).toUpperCase()}
primaryText={<p className="text-muted-foreground text-sm">{recipient.email}</p>}
secondaryText={
<p className="text-muted-foreground/70 text-xs">
{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
</p>
}
/>
{recipient.role !== RecipientRole.CC && (
<CopyTextButton
value={formatSigningLink(recipient.token)}
onCopySuccess={() => {
toast({
title: _(msg`Copied to clipboard`),
description: _(msg`The signing link has been copied to your clipboard.`),
});
}}
badgeContentUncopied={
<p className="ml-1 text-xs">
<Trans>Copy</Trans>
</p>
}
badgeContentCopied={
<p className="ml-1 text-xs">
<Trans>Copied</Trans>
</p>
}
/>
)}
</li>
))}
</ul>
<DialogFooter>
<DialogClose asChild>
<Button type="button" variant="secondary">
<Trans>Close</Trans>
</Button>
</DialogClose>
<Button type="button" onClick={onBulkCopy}>
<Trans>Bulk Copy</Trans>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@ -0,0 +1,50 @@
import React, { useMemo } from 'react';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import { Select, SelectContent, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
export type SearchParamSelector = {
paramKey: string;
isValueValid: (value: unknown) => boolean;
children: React.ReactNode;
};
export const SearchParamSelector = ({ children, paramKey, isValueValid }: SearchParamSelector) => {
const pathname = usePathname();
const searchParams = useSearchParams();
const router = useRouter();
const value = useMemo(() => {
const p = searchParams?.get(paramKey) ?? 'all';
return isValueValid(p) ? p : 'all';
}, [searchParams]);
const onValueChange = (newValue: string) => {
if (!pathname) {
return;
}
const params = new URLSearchParams(searchParams?.toString());
params.set(paramKey, newValue);
if (newValue === '' || newValue === 'all') {
params.delete(paramKey);
}
router.push(`${pathname}?${params.toString()}`, { scroll: false });
};
return (
<Select defaultValue={value} onValueChange={onValueChange}>
<SelectTrigger className="text-muted-foreground max-w-[200px]">
<SelectValue />
</SelectTrigger>
<SelectContent position="popper">{children}</SelectContent>
</Select>
);
};

View File

@ -50,6 +50,7 @@ services:
- NEXT_PRIVATE_SMTP_SECURE=${NEXT_PRIVATE_SMTP_SECURE} - NEXT_PRIVATE_SMTP_SECURE=${NEXT_PRIVATE_SMTP_SECURE}
- NEXT_PRIVATE_SMTP_FROM_NAME=${NEXT_PRIVATE_SMTP_FROM_NAME:?err} - NEXT_PRIVATE_SMTP_FROM_NAME=${NEXT_PRIVATE_SMTP_FROM_NAME:?err}
- NEXT_PRIVATE_SMTP_FROM_ADDRESS=${NEXT_PRIVATE_SMTP_FROM_ADDRESS:?err} - NEXT_PRIVATE_SMTP_FROM_ADDRESS=${NEXT_PRIVATE_SMTP_FROM_ADDRESS:?err}
- NEXT_PRIVATE_SMTP_SERVICE=${NEXT_PRIVATE_SMTP_SERVICE}
- NEXT_PRIVATE_RESEND_API_KEY=${NEXT_PRIVATE_RESEND_API_KEY} - NEXT_PRIVATE_RESEND_API_KEY=${NEXT_PRIVATE_RESEND_API_KEY}
- NEXT_PRIVATE_MAILCHANNELS_API_KEY=${NEXT_PRIVATE_MAILCHANNELS_API_KEY} - NEXT_PRIVATE_MAILCHANNELS_API_KEY=${NEXT_PRIVATE_MAILCHANNELS_API_KEY}
- NEXT_PRIVATE_MAILCHANNELS_ENDPOINT=${NEXT_PRIVATE_MAILCHANNELS_ENDPOINT} - NEXT_PRIVATE_MAILCHANNELS_ENDPOINT=${NEXT_PRIVATE_MAILCHANNELS_ENDPOINT}
@ -60,6 +61,7 @@ services:
- NEXT_PUBLIC_POSTHOG_KEY=${NEXT_PUBLIC_POSTHOG_KEY} - NEXT_PUBLIC_POSTHOG_KEY=${NEXT_PUBLIC_POSTHOG_KEY}
- NEXT_PUBLIC_DISABLE_SIGNUP=${NEXT_PUBLIC_DISABLE_SIGNUP} - NEXT_PUBLIC_DISABLE_SIGNUP=${NEXT_PUBLIC_DISABLE_SIGNUP}
- NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=${NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH:-/opt/documenso/cert.p12} - NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=${NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH:-/opt/documenso/cert.p12}
- NEXT_PRIVATE_SIGNING_PASSPHRASE=${NEXT_PRIVATE_SIGNING_PASSPHRASE}
ports: ports:
- ${PORT:-3000}:${PORT:-3000} - ${PORT:-3000}:${PORT:-3000}
volumes: volumes:

8
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "@documenso/root", "name": "@documenso/root",
"version": "1.7.2-rc.3", "version": "1.7.2-rc.4",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@documenso/root", "name": "@documenso/root",
"version": "1.7.2-rc.3", "version": "1.7.2-rc.4",
"workspaces": [ "workspaces": [
"apps/*", "apps/*",
"packages/*" "packages/*"
@ -80,7 +80,7 @@
}, },
"apps/marketing": { "apps/marketing": {
"name": "@documenso/marketing", "name": "@documenso/marketing",
"version": "1.7.2-rc.3", "version": "1.7.2-rc.4",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"@documenso/assets": "*", "@documenso/assets": "*",
@ -441,7 +441,7 @@
}, },
"apps/web": { "apps/web": {
"name": "@documenso/web", "name": "@documenso/web",
"version": "1.7.2-rc.3", "version": "1.7.2-rc.4",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"@documenso/api": "*", "@documenso/api": "*",

View File

@ -1,6 +1,6 @@
{ {
"private": true, "private": true,
"version": "1.7.2-rc.3", "version": "1.7.2-rc.4",
"scripts": { "scripts": {
"build": "turbo run build", "build": "turbo run build",
"build:web": "turbo run build --filter=@documenso/web", "build:web": "turbo run build --filter=@documenso/web",

View File

@ -32,7 +32,7 @@ test.describe('[EE_ONLY]', () => {
await apiSignin({ await apiSignin({
page, page,
email: user.email, email: user.email,
redirectPath: `/templates/${template.id}`, redirectPath: `/templates/${template.id}/edit`,
}); });
// Set EE action auth. // Set EE action auth.
@ -74,7 +74,7 @@ test.describe('[EE_ONLY]', () => {
await apiSignin({ await apiSignin({
page, page,
email: teamMemberUser.email, email: teamMemberUser.email,
redirectPath: `/t/${team.url}/templates/${template.id}`, redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
}); });
// Set EE action auth. // Set EE action auth.
@ -110,7 +110,7 @@ test.describe('[EE_ONLY]', () => {
await apiSignin({ await apiSignin({
page, page,
email: teamMemberUser.email, email: teamMemberUser.email,
redirectPath: `/templates/${template.id}`, redirectPath: `/templates/${template.id}/edit`,
}); });
// Global action auth should not be visible. // Global action auth should not be visible.
@ -132,7 +132,7 @@ test('[TEMPLATE_FLOW]: add settings', async ({ page }) => {
await apiSignin({ await apiSignin({
page, page,
email: user.email, email: user.email,
redirectPath: `/templates/${template.id}`, redirectPath: `/templates/${template.id}/edit`,
}); });
// Set title. // Set title.

View File

@ -31,7 +31,7 @@ test.describe('[EE_ONLY]', () => {
await apiSignin({ await apiSignin({
page, page,
email: user.email, email: user.email,
redirectPath: `/templates/${template.id}`, redirectPath: `/templates/${template.id}/edit`,
}); });
// Save the settings by going to the next step. // Save the settings by going to the next step.
@ -81,7 +81,7 @@ test('[TEMPLATE_FLOW]: add placeholder', async ({ page }) => {
await apiSignin({ await apiSignin({
page, page,
email: user.email, email: user.email,
redirectPath: `/templates/${template.id}`, redirectPath: `/templates/${template.id}/edit`,
}); });
// Save the settings by going to the next step. // Save the settings by going to the next step.

View File

@ -37,7 +37,7 @@ test('[TEMPLATE]: should create a document from a template', async ({ page }) =>
await apiSignin({ await apiSignin({
page, page,
email: user.email, email: user.email,
redirectPath: `/templates/${template.id}`, redirectPath: `/templates/${template.id}/edit`,
}); });
// Set template title. // Set template title.
@ -172,7 +172,7 @@ test('[TEMPLATE]: should create a team document from a team template', async ({
await apiSignin({ await apiSignin({
page, page,
email: owner.email, email: owner.email,
redirectPath: `/t/${team.url}/templates/${template.id}`, redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
}); });
// Set template title. // Set template title.

View File

@ -1,10 +1,56 @@
import type { Transporter } from 'nodemailer';
import { createTransport } from 'nodemailer'; import { createTransport } from 'nodemailer';
import { ResendTransport } from '@documenso/nodemailer-resend'; import { ResendTransport } from '@documenso/nodemailer-resend';
import { MailChannelsTransport } from './transports/mailchannels'; import { MailChannelsTransport } from './transports/mailchannels';
const getTransport = () => { /**
* Creates a Nodemailer transport object for sending emails.
*
* This function uses various environment variables to configure the appropriate
* email transport mechanism. It supports multiple types of email transports,
* including MailChannels, Resend, and different SMTP configurations.
*
* @returns {Transporter} A configured Nodemailer transporter instance.
*
* Supported Transports:
* - **mailchannels**: Uses MailChannelsTransport, requiring:
* - `NEXT_PRIVATE_MAILCHANNELS_API_KEY`: API key for MailChannels
* - `NEXT_PRIVATE_MAILCHANNELS_ENDPOINT`: Endpoint for MailChannels (optional)
* - **resend**: Uses ResendTransport, requiring:
* - `NEXT_PRIVATE_RESEND_API_KEY`: API key for Resend
* - **smtp-api**: Uses a custom SMTP API configuration, requiring:
* - `NEXT_PRIVATE_SMTP_HOST`: The SMTP server host
* - `NEXT_PRIVATE_SMTP_APIKEY`: The API key for SMTP authentication
* - `NEXT_PRIVATE_SMTP_APIKEY_USER`: The username for SMTP authentication (default: 'apikey')
* - **smtp-auth** (default): Uses a standard SMTP configuration, requiring:
* - `NEXT_PRIVATE_SMTP_HOST`: The SMTP server host (default: 'localhost:2500')
* - `NEXT_PRIVATE_SMTP_PORT`: The port to connect to (default: 587)
* - `NEXT_PRIVATE_SMTP_SECURE`: Whether to use SSL/TLS (default: false)
* - `NEXT_PRIVATE_SMTP_UNSAFE_IGNORE_TLS`: Whether to ignore TLS (default: false)
* - `NEXT_PRIVATE_SMTP_USERNAME`: The username for SMTP authentication
* - `NEXT_PRIVATE_SMTP_PASSWORD`: The password for SMTP authentication
* - `NEXT_PRIVATE_SMTP_SERVICE`: The SMTP service provider (e.g., "gmail"). This option is used
* when integrating with well-known services (like Gmail), enabling simplified configuration.
*
* Example Usage:
* ```env
* NEXT_PRIVATE_SMTP_TRANSPORT='smtp-auth';
* NEXT_PRIVATE_SMTP_HOST='smtp.example.com';
* NEXT_PRIVATE_SMTP_PORT=587;
* NEXT_PRIVATE_SMTP_SERVICE='gmail';
* NEXT_PRIVATE_SMTP_SECURE='true';
* NEXT_PRIVATE_SMTP_USERNAME='your-email@gmail.com';
* NEXT_PRIVATE_SMTP_PASSWORD='your-password';
* ```
*
* Notes:
* - Ensure that the required environment variables for each transport type are set.
* - If `NEXT_PRIVATE_SMTP_TRANSPORT` is not specified, the default is `smtp-auth`.
* - `NEXT_PRIVATE_SMTP_SERVICE` is optional and used specifically for well-known services like Gmail.
*/
const getTransport = (): Transporter => {
const transport = process.env.NEXT_PRIVATE_SMTP_TRANSPORT ?? 'smtp-auth'; const transport = process.env.NEXT_PRIVATE_SMTP_TRANSPORT ?? 'smtp-auth';
if (transport === 'mailchannels') { if (transport === 'mailchannels') {
@ -53,6 +99,9 @@ const getTransport = () => {
pass: process.env.NEXT_PRIVATE_SMTP_PASSWORD ?? '', pass: process.env.NEXT_PRIVATE_SMTP_PASSWORD ?? '',
} }
: undefined, : undefined,
...(process.env.NEXT_PRIVATE_SMTP_SERVICE
? { service: process.env.NEXT_PRIVATE_SMTP_SERVICE }
: {}),
}); });
}; };

View File

@ -0,0 +1,18 @@
import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/macro';
import { DocumentStatus } from '@documenso/prisma/client';
export const DOCUMENT_STATUS: {
[status in DocumentStatus]: { description: MessageDescriptor };
} = {
[DocumentStatus.COMPLETED]: {
description: msg`Completed`,
},
[DocumentStatus.DRAFT]: {
description: msg`Draft`,
},
[DocumentStatus.PENDING]: {
description: msg`Pending`,
},
};

View File

@ -78,13 +78,3 @@ export const RECIPIENT_ROLE_SIGNING_REASONS = {
[RecipientRole.CC]: msg`I am required to receive a copy of this document`, [RecipientRole.CC]: msg`I am required to receive a copy of this document`,
[RecipientRole.VIEWER]: msg`I am a viewer of this document`, [RecipientRole.VIEWER]: msg`I am a viewer of this document`,
} satisfies Record<keyof typeof RecipientRole, MessageDescriptor>; } satisfies Record<keyof typeof RecipientRole, MessageDescriptor>;
/**
* Raw english descriptions for certificates.
*/
export const RECIPIENT_ROLE_SIGNING_REASONS_ENG = {
[RecipientRole.SIGNER]: `I am a signer of this document`,
[RecipientRole.APPROVER]: `I am an approver of this document`,
[RecipientRole.CC]: `I am required to receive a copy of this document`,
[RecipientRole.VIEWER]: `I am a viewer of this document`,
} satisfies Record<keyof typeof RecipientRole, string>;

View File

@ -115,9 +115,11 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
if (isTeamDocument && team) { if (isTeamDocument && team) {
emailSubject = i18n._(msg`${team.name} invited you to ${recipientActionVerb} a document`); emailSubject = i18n._(msg`${team.name} invited you to ${recipientActionVerb} a document`);
emailMessage = i18n._( emailMessage =
msg`${user.name} on behalf of ${team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`, customEmail?.message ||
); i18n._(
msg`${user.name} on behalf of ${team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`,
);
} }
const customEmailTemplate = { const customEmailTemplate = {

View File

@ -2,12 +2,15 @@
import { createElement } from 'react'; import { createElement } from 'react';
import { msg } from '@lingui/macro';
import { mailer } from '@documenso/email/mailer'; import { mailer } from '@documenso/email/mailer';
import DocumentCancelTemplate from '@documenso/email/templates/document-cancel'; import DocumentCancelTemplate from '@documenso/email/templates/document-cancel';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import type { Document, DocumentMeta, Recipient, User } from '@documenso/prisma/client'; import type { Document, DocumentMeta, Recipient, User } from '@documenso/prisma/client';
import { DocumentStatus, SendStatus } from '@documenso/prisma/client'; import { DocumentStatus, SendStatus } from '@documenso/prisma/client';
import { getI18nInstance } from '../../client-only/providers/i18n.server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app'; import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../constants/email'; import { FROM_ADDRESS, FROM_NAME } from '../../constants/email';
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs'; import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
@ -192,10 +195,12 @@ const handleDocumentOwnerDelete = async ({
}); });
const [html, text] = await Promise.all([ const [html, text] = await Promise.all([
renderEmailWithI18N(template), renderEmailWithI18N(template, { lang: document.documentMeta?.language }),
renderEmailWithI18N(template, { plainText: true }), renderEmailWithI18N(template, { lang: document.documentMeta?.language, plainText: true }),
]); ]);
const i18n = await getI18nInstance(document.documentMeta?.language);
await mailer.sendMail({ await mailer.sendMail({
to: { to: {
address: recipient.email, address: recipient.email,
@ -205,7 +210,7 @@ const handleDocumentOwnerDelete = async ({
name: FROM_NAME, name: FROM_NAME,
address: FROM_ADDRESS, address: FROM_ADDRESS,
}, },
subject: 'Document Cancelled', subject: i18n._(msg`Document Cancelled`),
html, html,
text, text,
}); });

View File

@ -3,7 +3,14 @@ import { P, match } from 'ts-pattern';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import { RecipientRole, SigningStatus, TeamMemberRole } from '@documenso/prisma/client'; import { RecipientRole, SigningStatus, TeamMemberRole } from '@documenso/prisma/client';
import type { Document, Prisma, Team, TeamEmail, User } from '@documenso/prisma/client'; import type {
Document,
DocumentSource,
Prisma,
Team,
TeamEmail,
User,
} from '@documenso/prisma/client';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status'; import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
import { DocumentVisibility } from '../../types/document-visibility'; import { DocumentVisibility } from '../../types/document-visibility';
@ -16,6 +23,8 @@ export type FindDocumentsOptions = {
userId: number; userId: number;
teamId?: number; teamId?: number;
term?: string; term?: string;
templateId?: number;
source?: DocumentSource;
status?: ExtendedDocumentStatus; status?: ExtendedDocumentStatus;
page?: number; page?: number;
perPage?: number; perPage?: number;
@ -32,6 +41,8 @@ export const findDocuments = async ({
userId, userId,
teamId, teamId,
term, term,
templateId,
source,
status = ExtendedDocumentStatus.ALL, status = ExtendedDocumentStatus.ALL,
page = 1, page = 1,
perPage = 10, perPage = 10,
@ -40,44 +51,37 @@ export const findDocuments = async ({
senderIds, senderIds,
search, search,
}: FindDocumentsOptions) => { }: FindDocumentsOptions) => {
const { user, team } = await prisma.$transaction(async (tx) => { const user = await prisma.user.findFirstOrThrow({
const user = await tx.user.findFirstOrThrow({ where: {
id: userId,
},
});
let team = null;
if (teamId !== undefined) {
team = await prisma.team.findFirstOrThrow({
where: { where: {
id: userId, id: teamId,
members: {
some: {
userId,
},
},
},
include: {
teamEmail: true,
members: {
where: {
userId,
},
select: {
role: true,
},
},
}, },
}); });
}
let team = null;
if (teamId !== undefined) {
team = await tx.team.findFirstOrThrow({
where: {
id: teamId,
members: {
some: {
userId,
},
},
},
include: {
teamEmail: true,
members: {
where: {
userId,
},
select: {
role: true,
},
},
},
});
}
return {
user,
team,
};
});
const orderByColumn = orderBy?.column ?? 'createdAt'; const orderByColumn = orderBy?.column ?? 'createdAt';
const orderByDirection = orderBy?.direction ?? 'desc'; const orderByDirection = orderBy?.direction ?? 'desc';
@ -197,8 +201,27 @@ export const findDocuments = async ({
}; };
} }
const whereAndClause: Prisma.DocumentWhereInput['AND'] = [
{ ...termFilters },
{ ...filters },
{ ...deletedFilter },
{ ...searchFilter },
];
if (templateId) {
whereAndClause.push({
templateId,
});
}
if (source) {
whereAndClause.push({
source,
});
}
const whereClause: Prisma.DocumentWhereInput = { const whereClause: Prisma.DocumentWhereInput = {
AND: [{ ...termFilters }, { ...filters }, { ...deletedFilter }, { ...searchFilter }], AND: whereAndClause,
}; };
if (period) { if (period) {

View File

@ -106,17 +106,25 @@ export const resendDocument = async ({
._(RECIPIENT_ROLES_DESCRIPTION[recipient.role].actionVerb) ._(RECIPIENT_ROLES_DESCRIPTION[recipient.role].actionVerb)
.toLowerCase(); .toLowerCase();
let emailMessage = msg`${customEmail?.message || ''}`; let emailMessage = customEmail?.message || '';
let emailSubject = msg`Reminder: Please ${recipientActionVerb} this document`; let emailSubject = i18n._(msg`Reminder: Please ${recipientActionVerb} this document`);
if (selfSigner) { if (selfSigner) {
emailMessage = msg`You have initiated the document ${`"${document.title}"`} that requires you to ${recipientActionVerb} it.`; emailMessage = i18n._(
emailSubject = msg`Reminder: Please ${recipientActionVerb} your document`; msg`You have initiated the document ${`"${document.title}"`} that requires you to ${recipientActionVerb} it.`,
);
emailSubject = i18n._(msg`Reminder: Please ${recipientActionVerb} your document`);
} }
if (isTeamDocument && document.team) { if (isTeamDocument && document.team) {
emailSubject = msg`Reminder: ${document.team.name} invited you to ${recipientActionVerb} a document`; emailSubject = i18n._(
emailMessage = msg`${user.name} on behalf of ${document.team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`; msg`Reminder: ${document.team.name} invited you to ${recipientActionVerb} a document`,
);
emailMessage =
customEmail?.message ||
i18n._(
msg`${user.name} on behalf of ${document.team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`,
);
} }
const customEmailTemplate = { const customEmailTemplate = {
@ -134,7 +142,7 @@ export const resendDocument = async ({
inviterEmail: isTeamDocument ? document.team?.teamEmail?.email || user.email : user.email, inviterEmail: isTeamDocument ? document.team?.teamEmail?.email || user.email : user.email,
assetBaseUrl, assetBaseUrl,
signDocumentLink, signDocumentLink,
customBody: renderCustomEmailTemplate(i18n._(emailMessage), customEmailTemplate), customBody: renderCustomEmailTemplate(emailMessage, customEmailTemplate),
role: recipient.role, role: recipient.role,
selfSigner, selfSigner,
isTeamInvite: isTeamDocument, isTeamInvite: isTeamDocument,
@ -165,7 +173,7 @@ export const resendDocument = async ({
i18n._(msg`Reminder: ${customEmail.subject}`), i18n._(msg`Reminder: ${customEmail.subject}`),
customEmailTemplate, customEmailTemplate,
) )
: i18n._(emailSubject), : emailSubject,
html, html,
text, text,
}); });

View File

@ -42,6 +42,13 @@ export const getTemplateById = async ({ id, userId, teamId }: GetTemplateByIdOpt
templateMeta: true, templateMeta: true,
Recipient: true, Recipient: true,
Field: true, Field: true,
User: {
select: {
id: true,
name: true,
email: true,
},
},
}, },
}); });

View File

@ -8,7 +8,7 @@ msgstr ""
"Language: de\n" "Language: de\n"
"Project-Id-Version: documenso-app\n" "Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-11-05 02:04\n" "PO-Revision-Date: 2024-11-05 09:34\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: German\n" "Language-Team: German\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
@ -96,6 +96,90 @@ msgstr "{memberEmail} ist dem folgenden Team beigetreten"
msgid "{memberEmail} left the following team" msgid "{memberEmail} left the following team"
msgstr "{memberEmail} hat das folgende Team verlassen" msgstr "{memberEmail} hat das folgende Team verlassen"
#: packages/lib/utils/document-audit-logs.ts:263
msgid "{prefix} added a field"
msgstr "{prefix} hat ein Feld hinzugefügt"
#: packages/lib/utils/document-audit-logs.ts:275
msgid "{prefix} added a recipient"
msgstr "{prefix} hat einen Empfänger hinzugefügt"
#: packages/lib/utils/document-audit-logs.ts:287
msgid "{prefix} created the document"
msgstr "{prefix} hat das Dokument erstellt"
#: packages/lib/utils/document-audit-logs.ts:291
msgid "{prefix} deleted the document"
msgstr "{prefix} hat das Dokument gelöscht"
#: packages/lib/utils/document-audit-logs.ts:335
msgid "{prefix} moved the document to team"
msgstr "{prefix} hat das Dokument ins Team verschoben"
#: packages/lib/utils/document-audit-logs.ts:319
msgid "{prefix} opened the document"
msgstr "{prefix} hat das Dokument geöffnet"
#: packages/lib/utils/document-audit-logs.ts:267
msgid "{prefix} removed a field"
msgstr "{prefix} hat ein Feld entfernt"
#: packages/lib/utils/document-audit-logs.ts:279
msgid "{prefix} removed a recipient"
msgstr "{prefix} hat einen Empfänger entfernt"
#: packages/lib/utils/document-audit-logs.ts:355
msgid "{prefix} resent an email to {0}"
msgstr "{prefix} hat eine E-Mail an {0} erneut gesendet"
#: packages/lib/utils/document-audit-logs.ts:356
msgid "{prefix} sent an email to {0}"
msgstr "{prefix} hat eine E-Mail an {0} gesendet"
#: packages/lib/utils/document-audit-logs.ts:331
msgid "{prefix} sent the document"
msgstr "{prefix} hat das Dokument gesendet"
#: packages/lib/utils/document-audit-logs.ts:295
msgid "{prefix} signed a field"
msgstr "{prefix} hat ein Feld unterschrieben"
#: packages/lib/utils/document-audit-logs.ts:299
msgid "{prefix} unsigned a field"
msgstr "{prefix} hat ein Feld ungültig gemacht"
#: packages/lib/utils/document-audit-logs.ts:271
msgid "{prefix} updated a field"
msgstr "{prefix} hat ein Feld aktualisiert"
#: packages/lib/utils/document-audit-logs.ts:283
msgid "{prefix} updated a recipient"
msgstr "{prefix} hat einen Empfänger aktualisiert"
#: packages/lib/utils/document-audit-logs.ts:315
msgid "{prefix} updated the document"
msgstr "{prefix} hat das Dokument aktualisiert"
#: packages/lib/utils/document-audit-logs.ts:307
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
msgid "{prefix} updated the document external ID"
msgstr "{prefix} hat die externe ID des Dokuments aktualisiert"
#: packages/lib/utils/document-audit-logs.ts:311
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
msgid "{prefix} updated the document title"
msgstr "{prefix} hat den Titel des Dokuments aktualisiert"
#: packages/lib/utils/document-audit-logs.ts:303
msgid "{prefix} updated the document visibility"
msgstr "{prefix} hat die Sichtbarkeit des Dokuments aktualisiert"
#: packages/email/templates/document-created-from-direct-template.tsx:55 #: packages/email/templates/document-created-from-direct-template.tsx:55
msgid "{recipientName} {action} a document by using one of your direct links" msgid "{recipientName} {action} a document by using one of your direct links"
msgstr "{recipientName} {action} ein Dokument, indem Sie einen Ihrer direkten Links verwenden" msgstr "{recipientName} {action} ein Dokument, indem Sie einen Ihrer direkten Links verwenden"
@ -104,6 +188,26 @@ msgstr "{recipientName} {action} ein Dokument, indem Sie einen Ihrer direkten Li
msgid "{teamName} ownership transfer request" msgid "{teamName} ownership transfer request"
msgstr "Anfrage zur Übertragung des Eigentums von {teamName}" msgstr "Anfrage zur Übertragung des Eigentums von {teamName}"
#: packages/lib/utils/document-audit-logs.ts:343
msgid "{userName} approved the document"
msgstr "{userName} hat das Dokument genehmigt"
#: packages/lib/utils/document-audit-logs.ts:344
msgid "{userName} CC'd the document"
msgstr "{userName} hat das Dokument in CC gesetzt"
#: packages/lib/utils/document-audit-logs.ts:345
msgid "{userName} completed their task"
msgstr "{userName} hat ihre Aufgabe abgeschlossen"
#: packages/lib/utils/document-audit-logs.ts:341
msgid "{userName} signed the document"
msgstr "{userName} hat das Dokument unterschrieben"
#: packages/lib/utils/document-audit-logs.ts:342
msgid "{userName} viewed the document"
msgstr "{userName} hat das Dokument angesehen"
#: packages/ui/primitives/data-table-pagination.tsx:41 #: packages/ui/primitives/data-table-pagination.tsx:41
msgid "{visibleRows, plural, one {Showing # result.} other {Showing # results.}}" msgid "{visibleRows, plural, one {Showing # result.} other {Showing # results.}}"
msgstr "{visibleRows, plural, one {Eine # Ergebnis wird angezeigt.} other {# Ergebnisse werden angezeigt.}}" msgstr "{visibleRows, plural, one {Eine # Ergebnis wird angezeigt.} other {# Ergebnisse werden angezeigt.}}"
@ -150,10 +254,34 @@ msgstr "<0>Passkey erforderlich</0> - Der Empfänger muss ein Konto haben und de
msgid "A document was created by your direct template that requires you to {recipientActionVerb} it." msgid "A document was created by your direct template that requires you to {recipientActionVerb} it."
msgstr "Ein Dokument wurde von deiner direkten Vorlage erstellt, das erfordert, dass du {recipientActionVerb}." msgstr "Ein Dokument wurde von deiner direkten Vorlage erstellt, das erfordert, dass du {recipientActionVerb}."
#: packages/lib/utils/document-audit-logs.ts:262
msgid "A field was added"
msgstr "Ein Feld wurde hinzugefügt"
#: packages/lib/utils/document-audit-logs.ts:266
msgid "A field was removed"
msgstr "Ein Feld wurde entfernt"
#: packages/lib/utils/document-audit-logs.ts:270
msgid "A field was updated"
msgstr "Ein Feld wurde aktualisiert"
#: packages/lib/jobs/definitions/emails/send-team-member-joined-email.ts:90 #: packages/lib/jobs/definitions/emails/send-team-member-joined-email.ts:90
msgid "A new member has joined your team" msgid "A new member has joined your team"
msgstr "Ein neues Mitglied ist deinem Team beigetreten" msgstr "Ein neues Mitglied ist deinem Team beigetreten"
#: packages/lib/utils/document-audit-logs.ts:274
msgid "A recipient was added"
msgstr "Ein Empfänger wurde hinzugefügt"
#: packages/lib/utils/document-audit-logs.ts:278
msgid "A recipient was removed"
msgstr "Ein Empfänger wurde entfernt"
#: packages/lib/utils/document-audit-logs.ts:282
msgid "A recipient was updated"
msgstr "Ein Empfänger wurde aktualisiert"
#: packages/lib/server-only/team/create-team-email-verification.ts:142 #: packages/lib/server-only/team/create-team-email-verification.ts:142
msgid "A request to use your email has been initiated by {teamName} on Documenso" msgid "A request to use your email has been initiated by {teamName} on Documenso"
msgstr "Eine Anfrage zur Verwendung deiner E-Mail wurde von {teamName} auf Documenso initiiert" msgstr "Eine Anfrage zur Verwendung deiner E-Mail wurde von {teamName} auf Documenso initiiert"
@ -368,6 +496,7 @@ msgstr "Schließen"
#: packages/email/template-components/template-document-completed.tsx:35 #: packages/email/template-components/template-document-completed.tsx:35
#: packages/email/template-components/template-document-self-signed.tsx:36 #: packages/email/template-components/template-document-self-signed.tsx:36
#: packages/lib/constants/document.ts:10
msgid "Completed" msgid "Completed"
msgstr "Abgeschlossen" msgstr "Abgeschlossen"
@ -450,10 +579,24 @@ msgstr "Empfänger des direkten Links"
msgid "Document access" msgid "Document access"
msgstr "Dokumentenzugriff" msgstr "Dokumentenzugriff"
#: packages/lib/utils/document-audit-logs.ts:306
msgid "Document access auth updated"
msgstr "Die Authentifizierung für den Dokumentenzugriff wurde aktualisiert"
#: packages/lib/server-only/document/delete-document.ts:213
#: packages/lib/server-only/document/super-delete-document.ts:75 #: packages/lib/server-only/document/super-delete-document.ts:75
msgid "Document Cancelled" msgid "Document Cancelled"
msgstr "Dokument storniert" msgstr "Dokument storniert"
#: packages/lib/utils/document-audit-logs.ts:359
#: packages/lib/utils/document-audit-logs.ts:360
msgid "Document completed"
msgstr "Dokument abgeschlossen"
#: packages/lib/utils/document-audit-logs.ts:286
msgid "Document created"
msgstr "Dokument erstellt"
#: packages/email/templates/document-created-from-direct-template.tsx:30 #: packages/email/templates/document-created-from-direct-template.tsx:30
#: packages/lib/server-only/template/create-document-from-direct-template.ts:554 #: packages/lib/server-only/template/create-document-from-direct-template.ts:554
msgid "Document created from direct template" msgid "Document created from direct template"
@ -463,15 +606,55 @@ msgstr "Dokument erstellt aus direkter Vorlage"
msgid "Document Creation" msgid "Document Creation"
msgstr "Dokumenterstellung" msgstr "Dokumenterstellung"
#: packages/lib/utils/document-audit-logs.ts:290
msgid "Document deleted"
msgstr "Dokument gelöscht"
#: packages/lib/server-only/document/send-delete-email.ts:58 #: packages/lib/server-only/document/send-delete-email.ts:58
msgid "Document Deleted!" msgid "Document Deleted!"
msgstr "Dokument gelöscht!" msgstr "Dokument gelöscht!"
#: packages/lib/utils/document-audit-logs.ts:326
msgid "Document external ID updated"
msgstr "Externe ID des Dokuments aktualisiert"
#: packages/lib/utils/document-audit-logs.ts:334
msgid "Document moved to team"
msgstr "Dokument ins Team verschoben"
#: packages/lib/utils/document-audit-logs.ts:318
msgid "Document opened"
msgstr "Dokument geöffnet"
#: packages/lib/utils/document-audit-logs.ts:330
msgid "Document sent"
msgstr "Dokument gesendet"
#: packages/lib/utils/document-audit-logs.ts:310
msgid "Document signing auth updated"
msgstr "Dokument unterzeichnen Authentifizierung aktualisiert"
#: packages/lib/utils/document-audit-logs.ts:322
msgid "Document title updated"
msgstr "Dokumenttitel aktualisiert"
#: packages/lib/utils/document-audit-logs.ts:314
msgid "Document updated"
msgstr "Dokument aktualisiert"
#: packages/lib/utils/document-audit-logs.ts:302
msgid "Document visibility updated"
msgstr "Sichtbarkeit des Dokuments aktualisiert"
#: packages/email/template-components/template-document-completed.tsx:64 #: packages/email/template-components/template-document-completed.tsx:64
#: packages/ui/components/document/document-download-button.tsx:68 #: packages/ui/components/document/document-download-button.tsx:68
msgid "Download" msgid "Download"
msgstr "Herunterladen" msgstr "Herunterladen"
#: packages/lib/constants/document.ts:13
msgid "Draft"
msgstr "Entwurf"
#: packages/ui/primitives/document-dropzone.tsx:162 #: packages/ui/primitives/document-dropzone.tsx:162
msgid "Drag & drop your PDF here." msgid "Drag & drop your PDF here."
msgstr "Ziehen Sie Ihr PDF hierher." msgstr "Ziehen Sie Ihr PDF hierher."
@ -504,6 +687,14 @@ msgstr "E-Mail ist erforderlich"
msgid "Email Options" msgid "Email Options"
msgstr "E-Mail-Optionen" msgstr "E-Mail-Optionen"
#: packages/lib/utils/document-audit-logs.ts:353
msgid "Email resent"
msgstr "E-Mail erneut gesendet"
#: packages/lib/utils/document-audit-logs.ts:353
msgid "Email sent"
msgstr "E-Mail gesendet"
#: packages/ui/primitives/document-flow/add-fields.tsx:1123 #: packages/ui/primitives/document-flow/add-fields.tsx:1123
msgid "Empty field" msgid "Empty field"
msgstr "Leeres Feld" msgstr "Leeres Feld"
@ -564,6 +755,14 @@ msgstr "Feldbeschriftung"
msgid "Field placeholder" msgid "Field placeholder"
msgstr "Feldplatzhalter" msgstr "Feldplatzhalter"
#: packages/lib/utils/document-audit-logs.ts:294
msgid "Field signed"
msgstr "Feld unterschrieben"
#: packages/lib/utils/document-audit-logs.ts:298
msgid "Field unsigned"
msgstr "Feld nicht unterschrieben"
#: packages/ui/primitives/document-flow/field-items-advanced-settings/date-field.tsx:56 #: packages/ui/primitives/document-flow/field-items-advanced-settings/date-field.tsx:56
#: packages/ui/primitives/document-flow/field-items-advanced-settings/email-field.tsx:38 #: packages/ui/primitives/document-flow/field-items-advanced-settings/email-field.tsx:38
#: packages/ui/primitives/document-flow/field-items-advanced-settings/initials-field.tsx:38 #: packages/ui/primitives/document-flow/field-items-advanced-settings/initials-field.tsx:38
@ -774,6 +973,10 @@ msgstr "Passwort erfolgreich zurückgesetzt"
msgid "Password updated!" msgid "Password updated!"
msgstr "Passwort aktualisiert!" msgstr "Passwort aktualisiert!"
#: packages/lib/constants/document.ts:16
msgid "Pending"
msgstr "Ausstehend"
#: packages/email/templates/document-pending.tsx:17 #: packages/email/templates/document-pending.tsx:17
msgid "Pending Document" msgid "Pending Document"
msgstr "Ausstehendes Dokument" msgstr "Ausstehendes Dokument"
@ -841,6 +1044,10 @@ msgstr "Nur lesen"
msgid "Receives copy" msgid "Receives copy"
msgstr "Erhält Kopie" msgstr "Erhält Kopie"
#: packages/lib/utils/document-audit-logs.ts:338
msgid "Recipient"
msgstr "Empfänger"
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:39 #: packages/ui/components/recipient/recipient-action-auth-select.tsx:39
#: packages/ui/primitives/document-flow/add-settings.tsx:257 #: packages/ui/primitives/document-flow/add-settings.tsx:257
#: packages/ui/primitives/template-flow/add-template-settings.tsx:208 #: packages/ui/primitives/template-flow/add-template-settings.tsx:208
@ -1248,6 +1455,10 @@ msgstr "Wir haben dein Passwort wie gewünscht geändert. Du kannst dich jetzt m
msgid "Welcome to Documenso!" msgid "Welcome to Documenso!"
msgstr "Willkommen bei Documenso!" msgstr "Willkommen bei Documenso!"
#: packages/lib/utils/document-audit-logs.ts:258
msgid "You"
msgstr "Du"
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:44 #: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:44
msgid "You are about to send this document to the recipients. Are you sure you want to continue?" msgid "You are about to send this document to the recipients. Are you sure you want to continue?"
msgstr "Sie sind dabei, dieses Dokument an die Empfänger zu senden. Sind Sie sicher, dass Sie fortfahren möchten?" msgstr "Sie sind dabei, dieses Dokument an die Empfänger zu senden. Sind Sie sicher, dass Sie fortfahren möchten?"
@ -1309,4 +1520,3 @@ msgstr "Dein Passwort wurde aktualisiert."
#: packages/email/templates/team-delete.tsx:30 #: packages/email/templates/team-delete.tsx:30
msgid "Your team has been deleted" msgid "Your team has been deleted"
msgstr "Dein Team wurde gelöscht" msgstr "Dein Team wurde gelöscht"

View File

@ -8,7 +8,7 @@ msgstr ""
"Language: de\n" "Language: de\n"
"Project-Id-Version: documenso-app\n" "Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-11-05 02:04\n" "PO-Revision-Date: 2024-11-05 09:34\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: German\n" "Language-Team: German\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
@ -42,7 +42,7 @@ msgstr "Dokument hinzufügen"
msgid "Add More Users for {0}" msgid "Add More Users for {0}"
msgstr "Mehr Benutzer hinzufügen für {0}" msgstr "Mehr Benutzer hinzufügen für {0}"
#: apps/marketing/src/app/(marketing)/open/page.tsx:164 #: apps/marketing/src/app/(marketing)/open/page.tsx:165
msgid "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>" msgid "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>"
msgstr "Alle unsere Kennzahlen, Finanzen und Erkenntnisse sind öffentlich. Wir glauben an Transparenz und möchten unsere Reise mit Ihnen teilen. Mehr erfahren Sie hier: <0>Ankündigung Offene Kennzahlen</0>" msgstr "Alle unsere Kennzahlen, Finanzen und Erkenntnisse sind öffentlich. Wir glauben an Transparenz und möchten unsere Reise mit Ihnen teilen. Mehr erfahren Sie hier: <0>Ankündigung Offene Kennzahlen</0>"
@ -90,7 +90,7 @@ msgstr "Änderungsprotokoll"
msgid "Choose a template from the community app store. Or submit your own template for others to use." msgid "Choose a template from the community app store. Or submit your own template for others to use."
msgstr "Wählen Sie eine Vorlage aus dem Community-App-Store. Oder reichen Sie Ihre eigene Vorlage ein, damit andere sie benutzen können." msgstr "Wählen Sie eine Vorlage aus dem Community-App-Store. Oder reichen Sie Ihre eigene Vorlage ein, damit andere sie benutzen können."
#: apps/marketing/src/app/(marketing)/open/page.tsx:218 #: apps/marketing/src/app/(marketing)/open/page.tsx:219
msgid "Community" msgid "Community"
msgstr "Gemeinschaft" msgstr "Gemeinschaft"
@ -193,7 +193,7 @@ msgstr "Schnell."
msgid "Faster, smarter and more beautiful." msgid "Faster, smarter and more beautiful."
msgstr "Schneller, intelligenter und schöner." msgstr "Schneller, intelligenter und schöner."
#: apps/marketing/src/app/(marketing)/open/page.tsx:209 #: apps/marketing/src/app/(marketing)/open/page.tsx:210
msgid "Finances" msgid "Finances"
msgstr "Finanzen" msgstr "Finanzen"
@ -246,15 +246,15 @@ msgstr "Fangen Sie heute an."
msgid "Get the latest news from Documenso, including product updates, team announcements and more!" msgid "Get the latest news from Documenso, including product updates, team announcements and more!"
msgstr "Erhalten Sie die neuesten Nachrichten von Documenso, einschließlich Produkt-Updates, Team-Ankündigungen und mehr!" msgstr "Erhalten Sie die neuesten Nachrichten von Documenso, einschließlich Produkt-Updates, Team-Ankündigungen und mehr!"
#: apps/marketing/src/app/(marketing)/open/page.tsx:232 #: apps/marketing/src/app/(marketing)/open/page.tsx:233
msgid "GitHub: Total Merged PRs" msgid "GitHub: Total Merged PRs"
msgstr "GitHub: Gesamte PRs zusammengeführt" msgstr "GitHub: Gesamte PRs zusammengeführt"
#: apps/marketing/src/app/(marketing)/open/page.tsx:250 #: apps/marketing/src/app/(marketing)/open/page.tsx:251
msgid "GitHub: Total Open Issues" msgid "GitHub: Total Open Issues"
msgstr "GitHub: Gesamte offene Issues" msgstr "GitHub: Gesamte offene Issues"
#: apps/marketing/src/app/(marketing)/open/page.tsx:224 #: apps/marketing/src/app/(marketing)/open/page.tsx:225
msgid "GitHub: Total Stars" msgid "GitHub: Total Stars"
msgstr "GitHub: Gesamtanzahl Sterne" msgstr "GitHub: Gesamtanzahl Sterne"
@ -262,7 +262,7 @@ msgstr "GitHub: Gesamtanzahl Sterne"
msgid "Global Salary Bands" msgid "Global Salary Bands"
msgstr "Globale Gehaltsbänder" msgstr "Globale Gehaltsbänder"
#: apps/marketing/src/app/(marketing)/open/page.tsx:260 #: apps/marketing/src/app/(marketing)/open/page.tsx:261
msgid "Growth" msgid "Growth"
msgstr "Wachstum" msgstr "Wachstum"
@ -286,7 +286,7 @@ msgstr "Integrierte Zahlungen mit Stripe, sodass Sie sich keine Sorgen ums Bezah
msgid "Integrates with all your favourite tools." msgid "Integrates with all your favourite tools."
msgstr "Integriert sich mit all Ihren Lieblingstools." msgstr "Integriert sich mit all Ihren Lieblingstools."
#: apps/marketing/src/app/(marketing)/open/page.tsx:288 #: apps/marketing/src/app/(marketing)/open/page.tsx:289
msgid "Is there more?" msgid "Is there more?"
msgstr "Gibt es mehr?" msgstr "Gibt es mehr?"
@ -310,11 +310,11 @@ msgstr "Standort"
msgid "Make it your own through advanced customization and adjustability." msgid "Make it your own through advanced customization and adjustability."
msgstr "Machen Sie es zu Ihrem eigenen durch erweiterte Anpassung und Einstellbarkeit." msgstr "Machen Sie es zu Ihrem eigenen durch erweiterte Anpassung und Einstellbarkeit."
#: apps/marketing/src/app/(marketing)/open/page.tsx:198 #: apps/marketing/src/app/(marketing)/open/page.tsx:199
msgid "Merged PR's" msgid "Merged PR's"
msgstr "Zusammengeführte PRs" msgstr "Zusammengeführte PRs"
#: apps/marketing/src/app/(marketing)/open/page.tsx:233 #: apps/marketing/src/app/(marketing)/open/page.tsx:234
msgid "Merged PRs" msgid "Merged PRs"
msgstr "Zusammengeführte PRs" msgstr "Zusammengeführte PRs"
@ -345,8 +345,8 @@ msgstr "Keine Kreditkarte erforderlich"
msgid "None of these work for you? Try self-hosting!" msgid "None of these work for you? Try self-hosting!"
msgstr "Keines dieser Angebote passt zu Ihnen? Versuchen Sie das Selbst-Hosting!" msgstr "Keines dieser Angebote passt zu Ihnen? Versuchen Sie das Selbst-Hosting!"
#: apps/marketing/src/app/(marketing)/open/page.tsx:193 #: apps/marketing/src/app/(marketing)/open/page.tsx:194
#: apps/marketing/src/app/(marketing)/open/page.tsx:251 #: apps/marketing/src/app/(marketing)/open/page.tsx:252
msgid "Open Issues" msgid "Open Issues"
msgstr "Offene Issues" msgstr "Offene Issues"
@ -354,7 +354,7 @@ msgstr "Offene Issues"
msgid "Open Source or Hosted." msgid "Open Source or Hosted."
msgstr "Open Source oder Hosted." msgstr "Open Source oder Hosted."
#: apps/marketing/src/app/(marketing)/open/page.tsx:160 #: apps/marketing/src/app/(marketing)/open/page.tsx:161
#: apps/marketing/src/components/(marketing)/footer.tsx:37 #: apps/marketing/src/components/(marketing)/footer.tsx:37
#: apps/marketing/src/components/(marketing)/header.tsx:64 #: apps/marketing/src/components/(marketing)/header.tsx:64
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:40 #: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:40
@ -466,7 +466,7 @@ msgstr "Intelligent."
msgid "Star on GitHub" msgid "Star on GitHub"
msgstr "Auf GitHub favorisieren" msgstr "Auf GitHub favorisieren"
#: apps/marketing/src/app/(marketing)/open/page.tsx:225 #: apps/marketing/src/app/(marketing)/open/page.tsx:226
msgid "Stars" msgid "Stars"
msgstr "Favoriten" msgstr "Favoriten"
@ -501,7 +501,7 @@ msgstr "Vorlagen-Shop (Demnächst)."
msgid "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️" msgid "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️"
msgstr "Das ist großartig. Sie können sich die aktuellen <0>Issues</0> ansehen und unserer <1>Discord-Community</1> beitreten, um auf dem neuesten Stand zu bleiben, was die aktuellen Prioritäten sind. In jedem Fall sind wir eine offene Gemeinschaft und begrüßen jegliche Beiträge, technische und nicht-technische ❤️" msgstr "Das ist großartig. Sie können sich die aktuellen <0>Issues</0> ansehen und unserer <1>Discord-Community</1> beitreten, um auf dem neuesten Stand zu bleiben, was die aktuellen Prioritäten sind. In jedem Fall sind wir eine offene Gemeinschaft und begrüßen jegliche Beiträge, technische und nicht-technische ❤️"
#: apps/marketing/src/app/(marketing)/open/page.tsx:292 #: apps/marketing/src/app/(marketing)/open/page.tsx:293
msgid "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share." msgid "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share."
msgstr "Diese Seite entwickelt sich weiter, während wir lernen, was ein großartiges Signing-Unternehmen ausmacht. Wir werden sie aktualisieren, wenn wir mehr zu teilen haben." msgstr "Diese Seite entwickelt sich weiter, während wir lernen, was ein großartiges Signing-Unternehmen ausmacht. Wir werden sie aktualisieren, wenn wir mehr zu teilen haben."
@ -514,8 +514,8 @@ msgstr "Titel"
msgid "Total Completed Documents" msgid "Total Completed Documents"
msgstr "Insgesamt Abgeschlossene Dokumente" msgstr "Insgesamt Abgeschlossene Dokumente"
#: apps/marketing/src/app/(marketing)/open/page.tsx:266
#: apps/marketing/src/app/(marketing)/open/page.tsx:267 #: apps/marketing/src/app/(marketing)/open/page.tsx:267
#: apps/marketing/src/app/(marketing)/open/page.tsx:268
msgid "Total Customers" msgid "Total Customers"
msgstr "Insgesamt Kunden" msgstr "Insgesamt Kunden"
@ -602,4 +602,3 @@ msgstr "Sie können Documenso kostenlos selbst hosten oder unsere sofort einsatz
#: apps/marketing/src/components/(marketing)/carousel.tsx:272 #: apps/marketing/src/components/(marketing)/carousel.tsx:272
msgid "Your browser does not support the video tag." msgid "Your browser does not support the video tag."
msgstr "Ihr Browser unterstützt das Video-Tag nicht." msgstr "Ihr Browser unterstützt das Video-Tag nicht."

File diff suppressed because it is too large Load Diff

View File

@ -91,6 +91,90 @@ msgstr "{memberEmail} joined the following team"
msgid "{memberEmail} left the following team" msgid "{memberEmail} left the following team"
msgstr "{memberEmail} left the following team" msgstr "{memberEmail} left the following team"
#: packages/lib/utils/document-audit-logs.ts:263
msgid "{prefix} added a field"
msgstr "{prefix} added a field"
#: packages/lib/utils/document-audit-logs.ts:275
msgid "{prefix} added a recipient"
msgstr "{prefix} added a recipient"
#: packages/lib/utils/document-audit-logs.ts:287
msgid "{prefix} created the document"
msgstr "{prefix} created the document"
#: packages/lib/utils/document-audit-logs.ts:291
msgid "{prefix} deleted the document"
msgstr "{prefix} deleted the document"
#: packages/lib/utils/document-audit-logs.ts:335
msgid "{prefix} moved the document to team"
msgstr "{prefix} moved the document to team"
#: packages/lib/utils/document-audit-logs.ts:319
msgid "{prefix} opened the document"
msgstr "{prefix} opened the document"
#: packages/lib/utils/document-audit-logs.ts:267
msgid "{prefix} removed a field"
msgstr "{prefix} removed a field"
#: packages/lib/utils/document-audit-logs.ts:279
msgid "{prefix} removed a recipient"
msgstr "{prefix} removed a recipient"
#: packages/lib/utils/document-audit-logs.ts:355
msgid "{prefix} resent an email to {0}"
msgstr "{prefix} resent an email to {0}"
#: packages/lib/utils/document-audit-logs.ts:356
msgid "{prefix} sent an email to {0}"
msgstr "{prefix} sent an email to {0}"
#: packages/lib/utils/document-audit-logs.ts:331
msgid "{prefix} sent the document"
msgstr "{prefix} sent the document"
#: packages/lib/utils/document-audit-logs.ts:295
msgid "{prefix} signed a field"
msgstr "{prefix} signed a field"
#: packages/lib/utils/document-audit-logs.ts:299
msgid "{prefix} unsigned a field"
msgstr "{prefix} unsigned a field"
#: packages/lib/utils/document-audit-logs.ts:271
msgid "{prefix} updated a field"
msgstr "{prefix} updated a field"
#: packages/lib/utils/document-audit-logs.ts:283
msgid "{prefix} updated a recipient"
msgstr "{prefix} updated a recipient"
#: packages/lib/utils/document-audit-logs.ts:315
msgid "{prefix} updated the document"
msgstr "{prefix} updated the document"
#: packages/lib/utils/document-audit-logs.ts:307
msgid "{prefix} updated the document access auth requirements"
msgstr "{prefix} updated the document access auth requirements"
#: packages/lib/utils/document-audit-logs.ts:327
msgid "{prefix} updated the document external ID"
msgstr "{prefix} updated the document external ID"
#: packages/lib/utils/document-audit-logs.ts:311
msgid "{prefix} updated the document signing auth requirements"
msgstr "{prefix} updated the document signing auth requirements"
#: packages/lib/utils/document-audit-logs.ts:323
msgid "{prefix} updated the document title"
msgstr "{prefix} updated the document title"
#: packages/lib/utils/document-audit-logs.ts:303
msgid "{prefix} updated the document visibility"
msgstr "{prefix} updated the document visibility"
#: packages/email/templates/document-created-from-direct-template.tsx:55 #: packages/email/templates/document-created-from-direct-template.tsx:55
msgid "{recipientName} {action} a document by using one of your direct links" msgid "{recipientName} {action} a document by using one of your direct links"
msgstr "{recipientName} {action} a document by using one of your direct links" msgstr "{recipientName} {action} a document by using one of your direct links"
@ -99,6 +183,26 @@ msgstr "{recipientName} {action} a document by using one of your direct links"
msgid "{teamName} ownership transfer request" msgid "{teamName} ownership transfer request"
msgstr "{teamName} ownership transfer request" msgstr "{teamName} ownership transfer request"
#: packages/lib/utils/document-audit-logs.ts:343
msgid "{userName} approved the document"
msgstr "{userName} approved the document"
#: packages/lib/utils/document-audit-logs.ts:344
msgid "{userName} CC'd the document"
msgstr "{userName} CC'd the document"
#: packages/lib/utils/document-audit-logs.ts:345
msgid "{userName} completed their task"
msgstr "{userName} completed their task"
#: packages/lib/utils/document-audit-logs.ts:341
msgid "{userName} signed the document"
msgstr "{userName} signed the document"
#: packages/lib/utils/document-audit-logs.ts:342
msgid "{userName} viewed the document"
msgstr "{userName} viewed the document"
#: packages/ui/primitives/data-table-pagination.tsx:41 #: packages/ui/primitives/data-table-pagination.tsx:41
msgid "{visibleRows, plural, one {Showing # result.} other {Showing # results.}}" msgid "{visibleRows, plural, one {Showing # result.} other {Showing # results.}}"
msgstr "{visibleRows, plural, one {Showing # result.} other {Showing # results.}}" msgstr "{visibleRows, plural, one {Showing # result.} other {Showing # results.}}"
@ -145,10 +249,34 @@ msgstr "<0>Require passkey</0> - The recipient must have an account and passkey
msgid "A document was created by your direct template that requires you to {recipientActionVerb} it." msgid "A document was created by your direct template that requires you to {recipientActionVerb} it."
msgstr "A document was created by your direct template that requires you to {recipientActionVerb} it." msgstr "A document was created by your direct template that requires you to {recipientActionVerb} it."
#: packages/lib/utils/document-audit-logs.ts:262
msgid "A field was added"
msgstr "A field was added"
#: packages/lib/utils/document-audit-logs.ts:266
msgid "A field was removed"
msgstr "A field was removed"
#: packages/lib/utils/document-audit-logs.ts:270
msgid "A field was updated"
msgstr "A field was updated"
#: packages/lib/jobs/definitions/emails/send-team-member-joined-email.ts:90 #: packages/lib/jobs/definitions/emails/send-team-member-joined-email.ts:90
msgid "A new member has joined your team" msgid "A new member has joined your team"
msgstr "A new member has joined your team" msgstr "A new member has joined your team"
#: packages/lib/utils/document-audit-logs.ts:274
msgid "A recipient was added"
msgstr "A recipient was added"
#: packages/lib/utils/document-audit-logs.ts:278
msgid "A recipient was removed"
msgstr "A recipient was removed"
#: packages/lib/utils/document-audit-logs.ts:282
msgid "A recipient was updated"
msgstr "A recipient was updated"
#: packages/lib/server-only/team/create-team-email-verification.ts:142 #: packages/lib/server-only/team/create-team-email-verification.ts:142
msgid "A request to use your email has been initiated by {teamName} on Documenso" msgid "A request to use your email has been initiated by {teamName} on Documenso"
msgstr "A request to use your email has been initiated by {teamName} on Documenso" msgstr "A request to use your email has been initiated by {teamName} on Documenso"
@ -363,6 +491,7 @@ msgstr "Close"
#: packages/email/template-components/template-document-completed.tsx:35 #: packages/email/template-components/template-document-completed.tsx:35
#: packages/email/template-components/template-document-self-signed.tsx:36 #: packages/email/template-components/template-document-self-signed.tsx:36
#: packages/lib/constants/document.ts:10
msgid "Completed" msgid "Completed"
msgstr "Completed" msgstr "Completed"
@ -445,10 +574,24 @@ msgstr "Direct link receiver"
msgid "Document access" msgid "Document access"
msgstr "Document access" msgstr "Document access"
#: packages/lib/utils/document-audit-logs.ts:306
msgid "Document access auth updated"
msgstr "Document access auth updated"
#: packages/lib/server-only/document/delete-document.ts:213
#: packages/lib/server-only/document/super-delete-document.ts:75 #: packages/lib/server-only/document/super-delete-document.ts:75
msgid "Document Cancelled" msgid "Document Cancelled"
msgstr "Document Cancelled" msgstr "Document Cancelled"
#: packages/lib/utils/document-audit-logs.ts:359
#: packages/lib/utils/document-audit-logs.ts:360
msgid "Document completed"
msgstr "Document completed"
#: packages/lib/utils/document-audit-logs.ts:286
msgid "Document created"
msgstr "Document created"
#: packages/email/templates/document-created-from-direct-template.tsx:30 #: packages/email/templates/document-created-from-direct-template.tsx:30
#: packages/lib/server-only/template/create-document-from-direct-template.ts:554 #: packages/lib/server-only/template/create-document-from-direct-template.ts:554
msgid "Document created from direct template" msgid "Document created from direct template"
@ -458,15 +601,55 @@ msgstr "Document created from direct template"
msgid "Document Creation" msgid "Document Creation"
msgstr "Document Creation" msgstr "Document Creation"
#: packages/lib/utils/document-audit-logs.ts:290
msgid "Document deleted"
msgstr "Document deleted"
#: packages/lib/server-only/document/send-delete-email.ts:58 #: packages/lib/server-only/document/send-delete-email.ts:58
msgid "Document Deleted!" msgid "Document Deleted!"
msgstr "Document Deleted!" msgstr "Document Deleted!"
#: packages/lib/utils/document-audit-logs.ts:326
msgid "Document external ID updated"
msgstr "Document external ID updated"
#: packages/lib/utils/document-audit-logs.ts:334
msgid "Document moved to team"
msgstr "Document moved to team"
#: packages/lib/utils/document-audit-logs.ts:318
msgid "Document opened"
msgstr "Document opened"
#: packages/lib/utils/document-audit-logs.ts:330
msgid "Document sent"
msgstr "Document sent"
#: packages/lib/utils/document-audit-logs.ts:310
msgid "Document signing auth updated"
msgstr "Document signing auth updated"
#: packages/lib/utils/document-audit-logs.ts:322
msgid "Document title updated"
msgstr "Document title updated"
#: packages/lib/utils/document-audit-logs.ts:314
msgid "Document updated"
msgstr "Document updated"
#: packages/lib/utils/document-audit-logs.ts:302
msgid "Document visibility updated"
msgstr "Document visibility updated"
#: packages/email/template-components/template-document-completed.tsx:64 #: packages/email/template-components/template-document-completed.tsx:64
#: packages/ui/components/document/document-download-button.tsx:68 #: packages/ui/components/document/document-download-button.tsx:68
msgid "Download" msgid "Download"
msgstr "Download" msgstr "Download"
#: packages/lib/constants/document.ts:13
msgid "Draft"
msgstr "Draft"
#: packages/ui/primitives/document-dropzone.tsx:162 #: packages/ui/primitives/document-dropzone.tsx:162
msgid "Drag & drop your PDF here." msgid "Drag & drop your PDF here."
msgstr "Drag & drop your PDF here." msgstr "Drag & drop your PDF here."
@ -499,6 +682,14 @@ msgstr "Email is required"
msgid "Email Options" msgid "Email Options"
msgstr "Email Options" msgstr "Email Options"
#: packages/lib/utils/document-audit-logs.ts:353
msgid "Email resent"
msgstr "Email resent"
#: packages/lib/utils/document-audit-logs.ts:353
msgid "Email sent"
msgstr "Email sent"
#: packages/ui/primitives/document-flow/add-fields.tsx:1123 #: packages/ui/primitives/document-flow/add-fields.tsx:1123
msgid "Empty field" msgid "Empty field"
msgstr "Empty field" msgstr "Empty field"
@ -559,6 +750,14 @@ msgstr "Field label"
msgid "Field placeholder" msgid "Field placeholder"
msgstr "Field placeholder" msgstr "Field placeholder"
#: packages/lib/utils/document-audit-logs.ts:294
msgid "Field signed"
msgstr "Field signed"
#: packages/lib/utils/document-audit-logs.ts:298
msgid "Field unsigned"
msgstr "Field unsigned"
#: packages/ui/primitives/document-flow/field-items-advanced-settings/date-field.tsx:56 #: packages/ui/primitives/document-flow/field-items-advanced-settings/date-field.tsx:56
#: packages/ui/primitives/document-flow/field-items-advanced-settings/email-field.tsx:38 #: packages/ui/primitives/document-flow/field-items-advanced-settings/email-field.tsx:38
#: packages/ui/primitives/document-flow/field-items-advanced-settings/initials-field.tsx:38 #: packages/ui/primitives/document-flow/field-items-advanced-settings/initials-field.tsx:38
@ -769,6 +968,10 @@ msgstr "Password Reset Successful"
msgid "Password updated!" msgid "Password updated!"
msgstr "Password updated!" msgstr "Password updated!"
#: packages/lib/constants/document.ts:16
msgid "Pending"
msgstr "Pending"
#: packages/email/templates/document-pending.tsx:17 #: packages/email/templates/document-pending.tsx:17
msgid "Pending Document" msgid "Pending Document"
msgstr "Pending Document" msgstr "Pending Document"
@ -836,6 +1039,10 @@ msgstr "Read only"
msgid "Receives copy" msgid "Receives copy"
msgstr "Receives copy" msgstr "Receives copy"
#: packages/lib/utils/document-audit-logs.ts:338
msgid "Recipient"
msgstr "Recipient"
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:39 #: packages/ui/components/recipient/recipient-action-auth-select.tsx:39
#: packages/ui/primitives/document-flow/add-settings.tsx:257 #: packages/ui/primitives/document-flow/add-settings.tsx:257
#: packages/ui/primitives/template-flow/add-template-settings.tsx:208 #: packages/ui/primitives/template-flow/add-template-settings.tsx:208
@ -1243,6 +1450,10 @@ msgstr "We've changed your password as you asked. You can now sign in with your
msgid "Welcome to Documenso!" msgid "Welcome to Documenso!"
msgstr "Welcome to Documenso!" msgstr "Welcome to Documenso!"
#: packages/lib/utils/document-audit-logs.ts:258
msgid "You"
msgstr "You"
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:44 #: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:44
msgid "You are about to send this document to the recipients. Are you sure you want to continue?" msgid "You are about to send this document to the recipients. Are you sure you want to continue?"
msgstr "You are about to send this document to the recipients. Are you sure you want to continue?" msgstr "You are about to send this document to the recipients. Are you sure you want to continue?"

View File

@ -37,7 +37,7 @@ msgstr "Add document"
msgid "Add More Users for {0}" msgid "Add More Users for {0}"
msgstr "Add More Users for {0}" msgstr "Add More Users for {0}"
#: apps/marketing/src/app/(marketing)/open/page.tsx:164 #: apps/marketing/src/app/(marketing)/open/page.tsx:165
msgid "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>" msgid "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>"
msgstr "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>" msgstr "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>"
@ -85,7 +85,7 @@ msgstr "Changelog"
msgid "Choose a template from the community app store. Or submit your own template for others to use." msgid "Choose a template from the community app store. Or submit your own template for others to use."
msgstr "Choose a template from the community app store. Or submit your own template for others to use." msgstr "Choose a template from the community app store. Or submit your own template for others to use."
#: apps/marketing/src/app/(marketing)/open/page.tsx:218 #: apps/marketing/src/app/(marketing)/open/page.tsx:219
msgid "Community" msgid "Community"
msgstr "Community" msgstr "Community"
@ -188,7 +188,7 @@ msgstr "Fast."
msgid "Faster, smarter and more beautiful." msgid "Faster, smarter and more beautiful."
msgstr "Faster, smarter and more beautiful." msgstr "Faster, smarter and more beautiful."
#: apps/marketing/src/app/(marketing)/open/page.tsx:209 #: apps/marketing/src/app/(marketing)/open/page.tsx:210
msgid "Finances" msgid "Finances"
msgstr "Finances" msgstr "Finances"
@ -241,15 +241,15 @@ msgstr "Get started today."
msgid "Get the latest news from Documenso, including product updates, team announcements and more!" msgid "Get the latest news from Documenso, including product updates, team announcements and more!"
msgstr "Get the latest news from Documenso, including product updates, team announcements and more!" msgstr "Get the latest news from Documenso, including product updates, team announcements and more!"
#: apps/marketing/src/app/(marketing)/open/page.tsx:232 #: apps/marketing/src/app/(marketing)/open/page.tsx:233
msgid "GitHub: Total Merged PRs" msgid "GitHub: Total Merged PRs"
msgstr "GitHub: Total Merged PRs" msgstr "GitHub: Total Merged PRs"
#: apps/marketing/src/app/(marketing)/open/page.tsx:250 #: apps/marketing/src/app/(marketing)/open/page.tsx:251
msgid "GitHub: Total Open Issues" msgid "GitHub: Total Open Issues"
msgstr "GitHub: Total Open Issues" msgstr "GitHub: Total Open Issues"
#: apps/marketing/src/app/(marketing)/open/page.tsx:224 #: apps/marketing/src/app/(marketing)/open/page.tsx:225
msgid "GitHub: Total Stars" msgid "GitHub: Total Stars"
msgstr "GitHub: Total Stars" msgstr "GitHub: Total Stars"
@ -257,7 +257,7 @@ msgstr "GitHub: Total Stars"
msgid "Global Salary Bands" msgid "Global Salary Bands"
msgstr "Global Salary Bands" msgstr "Global Salary Bands"
#: apps/marketing/src/app/(marketing)/open/page.tsx:260 #: apps/marketing/src/app/(marketing)/open/page.tsx:261
msgid "Growth" msgid "Growth"
msgstr "Growth" msgstr "Growth"
@ -281,7 +281,7 @@ msgstr "Integrated payments with Stripe so you dont have to worry about getti
msgid "Integrates with all your favourite tools." msgid "Integrates with all your favourite tools."
msgstr "Integrates with all your favourite tools." msgstr "Integrates with all your favourite tools."
#: apps/marketing/src/app/(marketing)/open/page.tsx:288 #: apps/marketing/src/app/(marketing)/open/page.tsx:289
msgid "Is there more?" msgid "Is there more?"
msgstr "Is there more?" msgstr "Is there more?"
@ -305,11 +305,11 @@ msgstr "Location"
msgid "Make it your own through advanced customization and adjustability." msgid "Make it your own through advanced customization and adjustability."
msgstr "Make it your own through advanced customization and adjustability." msgstr "Make it your own through advanced customization and adjustability."
#: apps/marketing/src/app/(marketing)/open/page.tsx:198 #: apps/marketing/src/app/(marketing)/open/page.tsx:199
msgid "Merged PR's" msgid "Merged PR's"
msgstr "Merged PR's" msgstr "Merged PR's"
#: apps/marketing/src/app/(marketing)/open/page.tsx:233 #: apps/marketing/src/app/(marketing)/open/page.tsx:234
msgid "Merged PRs" msgid "Merged PRs"
msgstr "Merged PRs" msgstr "Merged PRs"
@ -340,8 +340,8 @@ msgstr "No Credit Card required"
msgid "None of these work for you? Try self-hosting!" msgid "None of these work for you? Try self-hosting!"
msgstr "None of these work for you? Try self-hosting!" msgstr "None of these work for you? Try self-hosting!"
#: apps/marketing/src/app/(marketing)/open/page.tsx:193 #: apps/marketing/src/app/(marketing)/open/page.tsx:194
#: apps/marketing/src/app/(marketing)/open/page.tsx:251 #: apps/marketing/src/app/(marketing)/open/page.tsx:252
msgid "Open Issues" msgid "Open Issues"
msgstr "Open Issues" msgstr "Open Issues"
@ -349,7 +349,7 @@ msgstr "Open Issues"
msgid "Open Source or Hosted." msgid "Open Source or Hosted."
msgstr "Open Source or Hosted." msgstr "Open Source or Hosted."
#: apps/marketing/src/app/(marketing)/open/page.tsx:160 #: apps/marketing/src/app/(marketing)/open/page.tsx:161
#: apps/marketing/src/components/(marketing)/footer.tsx:37 #: apps/marketing/src/components/(marketing)/footer.tsx:37
#: apps/marketing/src/components/(marketing)/header.tsx:64 #: apps/marketing/src/components/(marketing)/header.tsx:64
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:40 #: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:40
@ -461,7 +461,7 @@ msgstr "Smart."
msgid "Star on GitHub" msgid "Star on GitHub"
msgstr "Star on GitHub" msgstr "Star on GitHub"
#: apps/marketing/src/app/(marketing)/open/page.tsx:225 #: apps/marketing/src/app/(marketing)/open/page.tsx:226
msgid "Stars" msgid "Stars"
msgstr "Stars" msgstr "Stars"
@ -496,7 +496,7 @@ msgstr "Template Store (Soon)."
msgid "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️" msgid "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️"
msgstr "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️" msgstr "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️"
#: apps/marketing/src/app/(marketing)/open/page.tsx:292 #: apps/marketing/src/app/(marketing)/open/page.tsx:293
msgid "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share." msgid "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share."
msgstr "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share." msgstr "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share."
@ -509,8 +509,8 @@ msgstr "Title"
msgid "Total Completed Documents" msgid "Total Completed Documents"
msgstr "Total Completed Documents" msgstr "Total Completed Documents"
#: apps/marketing/src/app/(marketing)/open/page.tsx:266
#: apps/marketing/src/app/(marketing)/open/page.tsx:267 #: apps/marketing/src/app/(marketing)/open/page.tsx:267
#: apps/marketing/src/app/(marketing)/open/page.tsx:268
msgid "Total Customers" msgid "Total Customers"
msgstr "Total Customers" msgstr "Total Customers"

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@ msgstr ""
"Language: es\n" "Language: es\n"
"Project-Id-Version: documenso-app\n" "Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-11-05 02:04\n" "PO-Revision-Date: 2024-11-05 09:34\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Spanish\n" "Language-Team: Spanish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
@ -96,6 +96,90 @@ msgstr "{memberEmail} se unió al siguiente equipo"
msgid "{memberEmail} left the following team" msgid "{memberEmail} left the following team"
msgstr "{memberEmail} dejó el siguiente equipo" msgstr "{memberEmail} dejó el siguiente equipo"
#: packages/lib/utils/document-audit-logs.ts:263
msgid "{prefix} added a field"
msgstr "{prefix} agregó un campo"
#: packages/lib/utils/document-audit-logs.ts:275
msgid "{prefix} added a recipient"
msgstr "{prefix} agregó un destinatario"
#: packages/lib/utils/document-audit-logs.ts:287
msgid "{prefix} created the document"
msgstr "{prefix} creó el documento"
#: packages/lib/utils/document-audit-logs.ts:291
msgid "{prefix} deleted the document"
msgstr "{prefix} eliminó el documento"
#: packages/lib/utils/document-audit-logs.ts:335
msgid "{prefix} moved the document to team"
msgstr "{prefix} movió el documento al equipo"
#: packages/lib/utils/document-audit-logs.ts:319
msgid "{prefix} opened the document"
msgstr "{prefix} abrió el documento"
#: packages/lib/utils/document-audit-logs.ts:267
msgid "{prefix} removed a field"
msgstr "{prefix} eliminó un campo"
#: packages/lib/utils/document-audit-logs.ts:279
msgid "{prefix} removed a recipient"
msgstr "{prefix} eliminó un destinatario"
#: packages/lib/utils/document-audit-logs.ts:355
msgid "{prefix} resent an email to {0}"
msgstr "{prefix} reenviaron un correo electrónico a {0}"
#: packages/lib/utils/document-audit-logs.ts:356
msgid "{prefix} sent an email to {0}"
msgstr "{prefix} envió un correo electrónico a {0}"
#: packages/lib/utils/document-audit-logs.ts:331
msgid "{prefix} sent the document"
msgstr "{prefix} envió el documento"
#: packages/lib/utils/document-audit-logs.ts:295
msgid "{prefix} signed a field"
msgstr "{prefix} firmó un campo"
#: packages/lib/utils/document-audit-logs.ts:299
msgid "{prefix} unsigned a field"
msgstr "{prefix} no firmó un campo"
#: packages/lib/utils/document-audit-logs.ts:271
msgid "{prefix} updated a field"
msgstr "{prefix} actualizó un campo"
#: packages/lib/utils/document-audit-logs.ts:283
msgid "{prefix} updated a recipient"
msgstr "{prefix} actualizó un destinatario"
#: packages/lib/utils/document-audit-logs.ts:315
msgid "{prefix} updated the document"
msgstr "{prefix} actualizó el documento"
#: packages/lib/utils/document-audit-logs.ts:307
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
msgid "{prefix} updated the document external ID"
msgstr "{prefix} actualizó el ID externo del documento"
#: packages/lib/utils/document-audit-logs.ts:311
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
msgid "{prefix} updated the document title"
msgstr "{prefix} actualizó el título del documento"
#: packages/lib/utils/document-audit-logs.ts:303
msgid "{prefix} updated the document visibility"
msgstr "{prefix} actualizó la visibilidad del documento"
#: packages/email/templates/document-created-from-direct-template.tsx:55 #: packages/email/templates/document-created-from-direct-template.tsx:55
msgid "{recipientName} {action} a document by using one of your direct links" msgid "{recipientName} {action} a document by using one of your direct links"
msgstr "{recipientName} {action} un documento utilizando uno de tus enlaces directos" msgstr "{recipientName} {action} un documento utilizando uno de tus enlaces directos"
@ -104,6 +188,26 @@ msgstr "{recipientName} {action} un documento utilizando uno de tus enlaces dire
msgid "{teamName} ownership transfer request" msgid "{teamName} ownership transfer request"
msgstr "solicitud de transferencia de propiedad de {teamName}" msgstr "solicitud de transferencia de propiedad de {teamName}"
#: packages/lib/utils/document-audit-logs.ts:343
msgid "{userName} approved the document"
msgstr "{userName} aprobó el documento"
#: packages/lib/utils/document-audit-logs.ts:344
msgid "{userName} CC'd the document"
msgstr "{userName} envió una copia del documento"
#: packages/lib/utils/document-audit-logs.ts:345
msgid "{userName} completed their task"
msgstr "{userName} completó su tarea"
#: packages/lib/utils/document-audit-logs.ts:341
msgid "{userName} signed the document"
msgstr "{userName} firmó el documento"
#: packages/lib/utils/document-audit-logs.ts:342
msgid "{userName} viewed the document"
msgstr "{userName} vio el documento"
#: packages/ui/primitives/data-table-pagination.tsx:41 #: packages/ui/primitives/data-table-pagination.tsx:41
msgid "{visibleRows, plural, one {Showing # result.} other {Showing # results.}}" msgid "{visibleRows, plural, one {Showing # result.} other {Showing # results.}}"
msgstr "{visibleRows, plural, one {Mostrando # resultado.} other {Mostrando # resultados.}}" msgstr "{visibleRows, plural, one {Mostrando # resultado.} other {Mostrando # resultados.}}"
@ -150,10 +254,34 @@ msgstr "<0>Requerir clave de acceso</0> - El destinatario debe tener una cuenta
msgid "A document was created by your direct template that requires you to {recipientActionVerb} it." msgid "A document was created by your direct template that requires you to {recipientActionVerb} it."
msgstr "Se creó un documento a partir de tu plantilla directa que requiere que {recipientActionVerb}." msgstr "Se creó un documento a partir de tu plantilla directa que requiere que {recipientActionVerb}."
#: packages/lib/utils/document-audit-logs.ts:262
msgid "A field was added"
msgstr "Se añadió un campo"
#: packages/lib/utils/document-audit-logs.ts:266
msgid "A field was removed"
msgstr "Se eliminó un campo"
#: packages/lib/utils/document-audit-logs.ts:270
msgid "A field was updated"
msgstr "Se actualizó un campo"
#: packages/lib/jobs/definitions/emails/send-team-member-joined-email.ts:90 #: packages/lib/jobs/definitions/emails/send-team-member-joined-email.ts:90
msgid "A new member has joined your team" msgid "A new member has joined your team"
msgstr "Un nuevo miembro se ha unido a tu equipo" msgstr "Un nuevo miembro se ha unido a tu equipo"
#: packages/lib/utils/document-audit-logs.ts:274
msgid "A recipient was added"
msgstr "Se añadió un destinatario"
#: packages/lib/utils/document-audit-logs.ts:278
msgid "A recipient was removed"
msgstr "Se eliminó un destinatario"
#: packages/lib/utils/document-audit-logs.ts:282
msgid "A recipient was updated"
msgstr "Se actualizó un destinatario"
#: packages/lib/server-only/team/create-team-email-verification.ts:142 #: packages/lib/server-only/team/create-team-email-verification.ts:142
msgid "A request to use your email has been initiated by {teamName} on Documenso" msgid "A request to use your email has been initiated by {teamName} on Documenso"
msgstr "Se ha iniciado una solicitud para utilizar tu correo electrónico por {teamName} en Documenso" msgstr "Se ha iniciado una solicitud para utilizar tu correo electrónico por {teamName} en Documenso"
@ -368,6 +496,7 @@ msgstr "Cerrar"
#: packages/email/template-components/template-document-completed.tsx:35 #: packages/email/template-components/template-document-completed.tsx:35
#: packages/email/template-components/template-document-self-signed.tsx:36 #: packages/email/template-components/template-document-self-signed.tsx:36
#: packages/lib/constants/document.ts:10
msgid "Completed" msgid "Completed"
msgstr "Completado" msgstr "Completado"
@ -450,10 +579,24 @@ msgstr "Receptor de enlace directo"
msgid "Document access" msgid "Document access"
msgstr "Acceso al documento" msgstr "Acceso al documento"
#: packages/lib/utils/document-audit-logs.ts:306
msgid "Document access auth updated"
msgstr "Se actualizó la autenticación de acceso al documento"
#: packages/lib/server-only/document/delete-document.ts:213
#: packages/lib/server-only/document/super-delete-document.ts:75 #: packages/lib/server-only/document/super-delete-document.ts:75
msgid "Document Cancelled" msgid "Document Cancelled"
msgstr "Documento cancelado" msgstr "Documento cancelado"
#: packages/lib/utils/document-audit-logs.ts:359
#: packages/lib/utils/document-audit-logs.ts:360
msgid "Document completed"
msgstr "Documento completado"
#: packages/lib/utils/document-audit-logs.ts:286
msgid "Document created"
msgstr "Documento creado"
#: packages/email/templates/document-created-from-direct-template.tsx:30 #: packages/email/templates/document-created-from-direct-template.tsx:30
#: packages/lib/server-only/template/create-document-from-direct-template.ts:554 #: packages/lib/server-only/template/create-document-from-direct-template.ts:554
msgid "Document created from direct template" msgid "Document created from direct template"
@ -463,15 +606,55 @@ msgstr "Documento creado a partir de plantilla directa"
msgid "Document Creation" msgid "Document Creation"
msgstr "Creación de documento" msgstr "Creación de documento"
#: packages/lib/utils/document-audit-logs.ts:290
msgid "Document deleted"
msgstr "Documento eliminado"
#: packages/lib/server-only/document/send-delete-email.ts:58 #: packages/lib/server-only/document/send-delete-email.ts:58
msgid "Document Deleted!" msgid "Document Deleted!"
msgstr "¡Documento eliminado!" msgstr "¡Documento eliminado!"
#: packages/lib/utils/document-audit-logs.ts:326
msgid "Document external ID updated"
msgstr "ID externo del documento actualizado"
#: packages/lib/utils/document-audit-logs.ts:334
msgid "Document moved to team"
msgstr "Documento movido al equipo"
#: packages/lib/utils/document-audit-logs.ts:318
msgid "Document opened"
msgstr "Documento abierto"
#: packages/lib/utils/document-audit-logs.ts:330
msgid "Document sent"
msgstr "Documento enviado"
#: packages/lib/utils/document-audit-logs.ts:310
msgid "Document signing auth updated"
msgstr "Se actualizó la autenticación de firma del documento"
#: packages/lib/utils/document-audit-logs.ts:322
msgid "Document title updated"
msgstr "Título del documento actualizado"
#: packages/lib/utils/document-audit-logs.ts:314
msgid "Document updated"
msgstr "Documento actualizado"
#: packages/lib/utils/document-audit-logs.ts:302
msgid "Document visibility updated"
msgstr "Visibilidad del documento actualizada"
#: packages/email/template-components/template-document-completed.tsx:64 #: packages/email/template-components/template-document-completed.tsx:64
#: packages/ui/components/document/document-download-button.tsx:68 #: packages/ui/components/document/document-download-button.tsx:68
msgid "Download" msgid "Download"
msgstr "Descargar" msgstr "Descargar"
#: packages/lib/constants/document.ts:13
msgid "Draft"
msgstr "Borrador"
#: packages/ui/primitives/document-dropzone.tsx:162 #: packages/ui/primitives/document-dropzone.tsx:162
msgid "Drag & drop your PDF here." msgid "Drag & drop your PDF here."
msgstr "Arrastre y suelte su PDF aquí." msgstr "Arrastre y suelte su PDF aquí."
@ -504,6 +687,14 @@ msgstr "Se requiere email"
msgid "Email Options" msgid "Email Options"
msgstr "Opciones de correo electrónico" msgstr "Opciones de correo electrónico"
#: packages/lib/utils/document-audit-logs.ts:353
msgid "Email resent"
msgstr "Correo electrónico reeenviado"
#: packages/lib/utils/document-audit-logs.ts:353
msgid "Email sent"
msgstr "Correo electrónico enviado"
#: packages/ui/primitives/document-flow/add-fields.tsx:1123 #: packages/ui/primitives/document-flow/add-fields.tsx:1123
msgid "Empty field" msgid "Empty field"
msgstr "Campo vacío" msgstr "Campo vacío"
@ -564,6 +755,14 @@ msgstr "Etiqueta de campo"
msgid "Field placeholder" msgid "Field placeholder"
msgstr "Marcador de posición de campo" msgstr "Marcador de posición de campo"
#: packages/lib/utils/document-audit-logs.ts:294
msgid "Field signed"
msgstr "Campo firmado"
#: packages/lib/utils/document-audit-logs.ts:298
msgid "Field unsigned"
msgstr "Campo no firmado"
#: packages/ui/primitives/document-flow/field-items-advanced-settings/date-field.tsx:56 #: packages/ui/primitives/document-flow/field-items-advanced-settings/date-field.tsx:56
#: packages/ui/primitives/document-flow/field-items-advanced-settings/email-field.tsx:38 #: packages/ui/primitives/document-flow/field-items-advanced-settings/email-field.tsx:38
#: packages/ui/primitives/document-flow/field-items-advanced-settings/initials-field.tsx:38 #: packages/ui/primitives/document-flow/field-items-advanced-settings/initials-field.tsx:38
@ -774,6 +973,10 @@ msgstr "Restablecimiento de contraseña exitoso"
msgid "Password updated!" msgid "Password updated!"
msgstr "¡Contraseña actualizada!" msgstr "¡Contraseña actualizada!"
#: packages/lib/constants/document.ts:16
msgid "Pending"
msgstr "Pendiente"
#: packages/email/templates/document-pending.tsx:17 #: packages/email/templates/document-pending.tsx:17
msgid "Pending Document" msgid "Pending Document"
msgstr "Documento pendiente" msgstr "Documento pendiente"
@ -841,6 +1044,10 @@ msgstr "Solo lectura"
msgid "Receives copy" msgid "Receives copy"
msgstr "Recibe copia" msgstr "Recibe copia"
#: packages/lib/utils/document-audit-logs.ts:338
msgid "Recipient"
msgstr "Destinatario"
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:39 #: packages/ui/components/recipient/recipient-action-auth-select.tsx:39
#: packages/ui/primitives/document-flow/add-settings.tsx:257 #: packages/ui/primitives/document-flow/add-settings.tsx:257
#: packages/ui/primitives/template-flow/add-template-settings.tsx:208 #: packages/ui/primitives/template-flow/add-template-settings.tsx:208
@ -1248,6 +1455,10 @@ msgstr "Hemos cambiado tu contraseña como solicitaste. Ahora puedes iniciar ses
msgid "Welcome to Documenso!" msgid "Welcome to Documenso!"
msgstr "¡Bienvenido a Documenso!" msgstr "¡Bienvenido a Documenso!"
#: packages/lib/utils/document-audit-logs.ts:258
msgid "You"
msgstr "Tú"
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:44 #: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:44
msgid "You are about to send this document to the recipients. Are you sure you want to continue?" msgid "You are about to send this document to the recipients. Are you sure you want to continue?"
msgstr "Está a punto de enviar este documento a los destinatarios. ¿Está seguro de que desea continuar?" msgstr "Está a punto de enviar este documento a los destinatarios. ¿Está seguro de que desea continuar?"
@ -1309,4 +1520,3 @@ msgstr "Tu contraseña ha sido actualizada."
#: packages/email/templates/team-delete.tsx:30 #: packages/email/templates/team-delete.tsx:30
msgid "Your team has been deleted" msgid "Your team has been deleted"
msgstr "Tu equipo ha sido eliminado" msgstr "Tu equipo ha sido eliminado"

View File

@ -8,7 +8,7 @@ msgstr ""
"Language: es\n" "Language: es\n"
"Project-Id-Version: documenso-app\n" "Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-11-05 02:04\n" "PO-Revision-Date: 2024-11-05 09:34\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Spanish\n" "Language-Team: Spanish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
@ -42,7 +42,7 @@ msgstr "Agregar documento"
msgid "Add More Users for {0}" msgid "Add More Users for {0}"
msgstr "Agregar más usuarios por {0}" msgstr "Agregar más usuarios por {0}"
#: apps/marketing/src/app/(marketing)/open/page.tsx:164 #: apps/marketing/src/app/(marketing)/open/page.tsx:165
msgid "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>" msgid "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>"
msgstr "Todos nuestros métricas, finanzas y aprendizajes son públicos. Creemos en la transparencia y queremos compartir nuestro viaje contigo. Puedes leer más sobre por qué aquí: <0>Anunciando Métricas Abiertas</0>" msgstr "Todos nuestros métricas, finanzas y aprendizajes son públicos. Creemos en la transparencia y queremos compartir nuestro viaje contigo. Puedes leer más sobre por qué aquí: <0>Anunciando Métricas Abiertas</0>"
@ -90,7 +90,7 @@ msgstr "Registro de cambios"
msgid "Choose a template from the community app store. Or submit your own template for others to use." msgid "Choose a template from the community app store. Or submit your own template for others to use."
msgstr "Elige una plantilla de la tienda de aplicaciones de la comunidad. O envía tu propia plantilla para que otros la usen." msgstr "Elige una plantilla de la tienda de aplicaciones de la comunidad. O envía tu propia plantilla para que otros la usen."
#: apps/marketing/src/app/(marketing)/open/page.tsx:218 #: apps/marketing/src/app/(marketing)/open/page.tsx:219
msgid "Community" msgid "Community"
msgstr "Comunidad" msgstr "Comunidad"
@ -193,7 +193,7 @@ msgstr "Rápido."
msgid "Faster, smarter and more beautiful." msgid "Faster, smarter and more beautiful."
msgstr "Más rápido, más inteligente y más hermoso." msgstr "Más rápido, más inteligente y más hermoso."
#: apps/marketing/src/app/(marketing)/open/page.tsx:209 #: apps/marketing/src/app/(marketing)/open/page.tsx:210
msgid "Finances" msgid "Finances"
msgstr "Finanzas" msgstr "Finanzas"
@ -246,15 +246,15 @@ msgstr "Comienza hoy."
msgid "Get the latest news from Documenso, including product updates, team announcements and more!" msgid "Get the latest news from Documenso, including product updates, team announcements and more!"
msgstr "¡Obtén las últimas noticias de Documenso, incluidas actualizaciones de productos, anuncios del equipo y más!" msgstr "¡Obtén las últimas noticias de Documenso, incluidas actualizaciones de productos, anuncios del equipo y más!"
#: apps/marketing/src/app/(marketing)/open/page.tsx:232 #: apps/marketing/src/app/(marketing)/open/page.tsx:233
msgid "GitHub: Total Merged PRs" msgid "GitHub: Total Merged PRs"
msgstr "GitHub: Total de PRs fusionados" msgstr "GitHub: Total de PRs fusionados"
#: apps/marketing/src/app/(marketing)/open/page.tsx:250 #: apps/marketing/src/app/(marketing)/open/page.tsx:251
msgid "GitHub: Total Open Issues" msgid "GitHub: Total Open Issues"
msgstr "GitHub: Total de problemas abiertos" msgstr "GitHub: Total de problemas abiertos"
#: apps/marketing/src/app/(marketing)/open/page.tsx:224 #: apps/marketing/src/app/(marketing)/open/page.tsx:225
msgid "GitHub: Total Stars" msgid "GitHub: Total Stars"
msgstr "GitHub: Total de estrellas" msgstr "GitHub: Total de estrellas"
@ -262,7 +262,7 @@ msgstr "GitHub: Total de estrellas"
msgid "Global Salary Bands" msgid "Global Salary Bands"
msgstr "Bandas salariales globales" msgstr "Bandas salariales globales"
#: apps/marketing/src/app/(marketing)/open/page.tsx:260 #: apps/marketing/src/app/(marketing)/open/page.tsx:261
msgid "Growth" msgid "Growth"
msgstr "Crecimiento" msgstr "Crecimiento"
@ -286,7 +286,7 @@ msgstr "Pagos integrados con Stripe para que no tengas que preocuparte por cobra
msgid "Integrates with all your favourite tools." msgid "Integrates with all your favourite tools."
msgstr "Se integra con todas tus herramientas favoritas." msgstr "Se integra con todas tus herramientas favoritas."
#: apps/marketing/src/app/(marketing)/open/page.tsx:288 #: apps/marketing/src/app/(marketing)/open/page.tsx:289
msgid "Is there more?" msgid "Is there more?"
msgstr "¿Hay más?" msgstr "¿Hay más?"
@ -310,11 +310,11 @@ msgstr "Ubicación"
msgid "Make it your own through advanced customization and adjustability." msgid "Make it your own through advanced customization and adjustability."
msgstr "Hazlo tuyo a través de personalización y ajustabilidad avanzadas." msgstr "Hazlo tuyo a través de personalización y ajustabilidad avanzadas."
#: apps/marketing/src/app/(marketing)/open/page.tsx:198 #: apps/marketing/src/app/(marketing)/open/page.tsx:199
msgid "Merged PR's" msgid "Merged PR's"
msgstr "PRs fusionados" msgstr "PRs fusionados"
#: apps/marketing/src/app/(marketing)/open/page.tsx:233 #: apps/marketing/src/app/(marketing)/open/page.tsx:234
msgid "Merged PRs" msgid "Merged PRs"
msgstr "PRs fusionados" msgstr "PRs fusionados"
@ -345,8 +345,8 @@ msgstr "No se requiere tarjeta de crédito"
msgid "None of these work for you? Try self-hosting!" msgid "None of these work for you? Try self-hosting!"
msgstr "¿Ninguna de estas opciones funciona para ti? ¡Prueba el autoalojamiento!" msgstr "¿Ninguna de estas opciones funciona para ti? ¡Prueba el autoalojamiento!"
#: apps/marketing/src/app/(marketing)/open/page.tsx:193 #: apps/marketing/src/app/(marketing)/open/page.tsx:194
#: apps/marketing/src/app/(marketing)/open/page.tsx:251 #: apps/marketing/src/app/(marketing)/open/page.tsx:252
msgid "Open Issues" msgid "Open Issues"
msgstr "Problemas Abiertos" msgstr "Problemas Abiertos"
@ -354,7 +354,7 @@ msgstr "Problemas Abiertos"
msgid "Open Source or Hosted." msgid "Open Source or Hosted."
msgstr "Código Abierto o Alojado." msgstr "Código Abierto o Alojado."
#: apps/marketing/src/app/(marketing)/open/page.tsx:160 #: apps/marketing/src/app/(marketing)/open/page.tsx:161
#: apps/marketing/src/components/(marketing)/footer.tsx:37 #: apps/marketing/src/components/(marketing)/footer.tsx:37
#: apps/marketing/src/components/(marketing)/header.tsx:64 #: apps/marketing/src/components/(marketing)/header.tsx:64
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:40 #: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:40
@ -466,7 +466,7 @@ msgstr "Inteligente."
msgid "Star on GitHub" msgid "Star on GitHub"
msgstr "Estrella en GitHub" msgstr "Estrella en GitHub"
#: apps/marketing/src/app/(marketing)/open/page.tsx:225 #: apps/marketing/src/app/(marketing)/open/page.tsx:226
msgid "Stars" msgid "Stars"
msgstr "Estrellas" msgstr "Estrellas"
@ -501,7 +501,7 @@ msgstr "Tienda de Plantillas (Pronto)."
msgid "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️" msgid "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️"
msgstr "Eso es increíble. Puedes echar un vistazo a los <0>Problemas</0> actuales y unirte a nuestra <1>Comunidad de Discord</1> para mantenerte al día sobre cuáles son las prioridades actuales. En cualquier caso, somos una comunidad abierta y agradecemos todas las aportaciones, técnicas y no técnicas ❤️" msgstr "Eso es increíble. Puedes echar un vistazo a los <0>Problemas</0> actuales y unirte a nuestra <1>Comunidad de Discord</1> para mantenerte al día sobre cuáles son las prioridades actuales. En cualquier caso, somos una comunidad abierta y agradecemos todas las aportaciones, técnicas y no técnicas ❤️"
#: apps/marketing/src/app/(marketing)/open/page.tsx:292 #: apps/marketing/src/app/(marketing)/open/page.tsx:293
msgid "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share." msgid "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share."
msgstr "Esta página está evolucionando a medida que aprendemos lo que hace una gran empresa de firmas. La actualizaremos cuando tengamos más para compartir." msgstr "Esta página está evolucionando a medida que aprendemos lo que hace una gran empresa de firmas. La actualizaremos cuando tengamos más para compartir."
@ -514,8 +514,8 @@ msgstr "Título"
msgid "Total Completed Documents" msgid "Total Completed Documents"
msgstr "Total de Documentos Completados" msgstr "Total de Documentos Completados"
#: apps/marketing/src/app/(marketing)/open/page.tsx:266
#: apps/marketing/src/app/(marketing)/open/page.tsx:267 #: apps/marketing/src/app/(marketing)/open/page.tsx:267
#: apps/marketing/src/app/(marketing)/open/page.tsx:268
msgid "Total Customers" msgid "Total Customers"
msgstr "Total de Clientes" msgstr "Total de Clientes"
@ -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 #: apps/marketing/src/components/(marketing)/carousel.tsx:272
msgid "Your browser does not support the video tag." msgid "Your browser does not support the video tag."
msgstr "Tu navegador no soporta la etiqueta de video." msgstr "Tu navegador no soporta la etiqueta de video."

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@ msgstr ""
"Language: fr\n" "Language: fr\n"
"Project-Id-Version: documenso-app\n" "Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-11-05 02:04\n" "PO-Revision-Date: 2024-11-05 09:34\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: French\n" "Language-Team: French\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n"
@ -96,6 +96,90 @@ msgstr "{memberEmail} a rejoint l'équipe suivante"
msgid "{memberEmail} left the following team" msgid "{memberEmail} left the following team"
msgstr "{memberEmail} a quitté l'équipe suivante" msgstr "{memberEmail} a quitté l'équipe suivante"
#: packages/lib/utils/document-audit-logs.ts:263
msgid "{prefix} added a field"
msgstr "{prefix} a ajouté un champ"
#: packages/lib/utils/document-audit-logs.ts:275
msgid "{prefix} added a recipient"
msgstr "{prefix} a ajouté un destinataire"
#: packages/lib/utils/document-audit-logs.ts:287
msgid "{prefix} created the document"
msgstr "{prefix} a créé le document"
#: packages/lib/utils/document-audit-logs.ts:291
msgid "{prefix} deleted the document"
msgstr "{prefix} a supprimé le document"
#: packages/lib/utils/document-audit-logs.ts:335
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
msgid "{prefix} opened the document"
msgstr "{prefix} a ouvert le document"
#: packages/lib/utils/document-audit-logs.ts:267
msgid "{prefix} removed a field"
msgstr "{prefix} a supprimé un champ"
#: packages/lib/utils/document-audit-logs.ts:279
msgid "{prefix} removed a recipient"
msgstr "{prefix} a supprimé un destinataire"
#: packages/lib/utils/document-audit-logs.ts:355
msgid "{prefix} resent an email to {0}"
msgstr "{prefix} a renvoyé un e-mail à {0}"
#: packages/lib/utils/document-audit-logs.ts:356
msgid "{prefix} sent an email to {0}"
msgstr "{prefix} a envoyé un email à {0}"
#: packages/lib/utils/document-audit-logs.ts:331
msgid "{prefix} sent the document"
msgstr "{prefix} a envoyé le document"
#: packages/lib/utils/document-audit-logs.ts:295
msgid "{prefix} signed a field"
msgstr "{prefix} a signé un champ"
#: packages/lib/utils/document-audit-logs.ts:299
msgid "{prefix} unsigned a field"
msgstr "{prefix} n'a pas signé un champ"
#: packages/lib/utils/document-audit-logs.ts:271
msgid "{prefix} updated a field"
msgstr "{prefix} a mis à jour un champ"
#: packages/lib/utils/document-audit-logs.ts:283
msgid "{prefix} updated a recipient"
msgstr "{prefix} a mis à jour un destinataire"
#: packages/lib/utils/document-audit-logs.ts:315
msgid "{prefix} updated the document"
msgstr "{prefix} a mis à jour le document"
#: packages/lib/utils/document-audit-logs.ts:307
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
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
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
msgid "{prefix} updated the document title"
msgstr "{prefix} a mis à jour le titre du document"
#: packages/lib/utils/document-audit-logs.ts:303
msgid "{prefix} updated the document visibility"
msgstr "{prefix} a mis à jour la visibilité du document"
#: packages/email/templates/document-created-from-direct-template.tsx:55 #: packages/email/templates/document-created-from-direct-template.tsx:55
msgid "{recipientName} {action} a document by using one of your direct links" msgid "{recipientName} {action} a document by using one of your direct links"
msgstr "{recipientName} {action} un document en utilisant l'un de vos liens directs" msgstr "{recipientName} {action} un document en utilisant l'un de vos liens directs"
@ -104,6 +188,26 @@ msgstr "{recipientName} {action} un document en utilisant l'un de vos liens dire
msgid "{teamName} ownership transfer request" msgid "{teamName} ownership transfer request"
msgstr "Demande de transfert de propriété de {teamName}" msgstr "Demande de transfert de propriété de {teamName}"
#: packages/lib/utils/document-audit-logs.ts:343
msgid "{userName} approved the document"
msgstr "{userName} a approuvé le document"
#: packages/lib/utils/document-audit-logs.ts:344
msgid "{userName} CC'd the document"
msgstr "{userName} a mis en copie le document"
#: packages/lib/utils/document-audit-logs.ts:345
msgid "{userName} completed their task"
msgstr "{userName} a complété sa tâche"
#: packages/lib/utils/document-audit-logs.ts:341
msgid "{userName} signed the document"
msgstr "{userName} a signé le document"
#: packages/lib/utils/document-audit-logs.ts:342
msgid "{userName} viewed the document"
msgstr "{userName} a consulté le document"
#: packages/ui/primitives/data-table-pagination.tsx:41 #: packages/ui/primitives/data-table-pagination.tsx:41
msgid "{visibleRows, plural, one {Showing # result.} other {Showing # results.}}" msgid "{visibleRows, plural, one {Showing # result.} other {Showing # results.}}"
msgstr "{visibleRows, plural, one {Affichage de # résultat.} other {Affichage de # résultats.}}" msgstr "{visibleRows, plural, one {Affichage de # résultat.} other {Affichage de # résultats.}}"
@ -150,10 +254,34 @@ msgstr "<0>Exiger une clé d'accès</0> - Le destinataire doit avoir un compte e
msgid "A document was created by your direct template that requires you to {recipientActionVerb} it." msgid "A document was created by your direct template that requires you to {recipientActionVerb} it."
msgstr "Un document a été créé par votre modèle direct qui nécessite que vous {recipientActionVerb} celui-ci." msgstr "Un document a été créé par votre modèle direct qui nécessite que vous {recipientActionVerb} celui-ci."
#: packages/lib/utils/document-audit-logs.ts:262
msgid "A field was added"
msgstr "Un champ a été ajouté"
#: packages/lib/utils/document-audit-logs.ts:266
msgid "A field was removed"
msgstr "Un champ a été supprimé"
#: packages/lib/utils/document-audit-logs.ts:270
msgid "A field was updated"
msgstr "Un champ a été mis à jour"
#: packages/lib/jobs/definitions/emails/send-team-member-joined-email.ts:90 #: packages/lib/jobs/definitions/emails/send-team-member-joined-email.ts:90
msgid "A new member has joined your team" msgid "A new member has joined your team"
msgstr "Un nouveau membre a rejoint votre équipe" msgstr "Un nouveau membre a rejoint votre équipe"
#: packages/lib/utils/document-audit-logs.ts:274
msgid "A recipient was added"
msgstr "Un destinataire a été ajouté"
#: packages/lib/utils/document-audit-logs.ts:278
msgid "A recipient was removed"
msgstr "Un destinataire a été supprimé"
#: packages/lib/utils/document-audit-logs.ts:282
msgid "A recipient was updated"
msgstr "Un destinataire a été mis à jour"
#: packages/lib/server-only/team/create-team-email-verification.ts:142 #: packages/lib/server-only/team/create-team-email-verification.ts:142
msgid "A request to use your email has been initiated by {teamName} on Documenso" msgid "A request to use your email has been initiated by {teamName} on Documenso"
msgstr "Une demande d'utilisation de votre email a été initiée par {teamName} sur Documenso" msgstr "Une demande d'utilisation de votre email a été initiée par {teamName} sur Documenso"
@ -368,6 +496,7 @@ msgstr "Fermer"
#: packages/email/template-components/template-document-completed.tsx:35 #: packages/email/template-components/template-document-completed.tsx:35
#: packages/email/template-components/template-document-self-signed.tsx:36 #: packages/email/template-components/template-document-self-signed.tsx:36
#: packages/lib/constants/document.ts:10
msgid "Completed" msgid "Completed"
msgstr "Terminé" msgstr "Terminé"
@ -450,10 +579,24 @@ msgstr "Receveur de lien direct"
msgid "Document access" msgid "Document access"
msgstr "Accès au document" msgstr "Accès au document"
#: packages/lib/utils/document-audit-logs.ts:306
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:213
#: packages/lib/server-only/document/super-delete-document.ts:75 #: packages/lib/server-only/document/super-delete-document.ts:75
msgid "Document Cancelled" msgid "Document Cancelled"
msgstr "Document Annulé" msgstr "Document Annulé"
#: packages/lib/utils/document-audit-logs.ts:359
#: packages/lib/utils/document-audit-logs.ts:360
msgid "Document completed"
msgstr "Document terminé"
#: packages/lib/utils/document-audit-logs.ts:286
msgid "Document created"
msgstr "Document créé"
#: packages/email/templates/document-created-from-direct-template.tsx:30 #: packages/email/templates/document-created-from-direct-template.tsx:30
#: packages/lib/server-only/template/create-document-from-direct-template.ts:554 #: packages/lib/server-only/template/create-document-from-direct-template.ts:554
msgid "Document created from direct template" msgid "Document created from direct template"
@ -463,15 +606,55 @@ msgstr "Document créé à partir d'un modèle direct"
msgid "Document Creation" msgid "Document Creation"
msgstr "Création de document" msgstr "Création de document"
#: packages/lib/utils/document-audit-logs.ts:290
msgid "Document deleted"
msgstr "Document supprimé"
#: packages/lib/server-only/document/send-delete-email.ts:58 #: packages/lib/server-only/document/send-delete-email.ts:58
msgid "Document Deleted!" msgid "Document Deleted!"
msgstr "Document Supprimé !" msgstr "Document Supprimé !"
#: packages/lib/utils/document-audit-logs.ts:326
msgid "Document external ID updated"
msgstr "ID externe du document mis à jour"
#: packages/lib/utils/document-audit-logs.ts:334
msgid "Document moved to team"
msgstr "Document déplacé vers l'équipe"
#: packages/lib/utils/document-audit-logs.ts:318
msgid "Document opened"
msgstr "Document ouvert"
#: packages/lib/utils/document-audit-logs.ts:330
msgid "Document sent"
msgstr "Document envoyé"
#: packages/lib/utils/document-audit-logs.ts:310
msgid "Document signing auth updated"
msgstr "Authentification de signature de document mise à jour"
#: packages/lib/utils/document-audit-logs.ts:322
msgid "Document title updated"
msgstr "Titre du document mis à jour"
#: packages/lib/utils/document-audit-logs.ts:314
msgid "Document updated"
msgstr "Document mis à jour"
#: packages/lib/utils/document-audit-logs.ts:302
msgid "Document visibility updated"
msgstr "Visibilité du document mise à jour"
#: packages/email/template-components/template-document-completed.tsx:64 #: packages/email/template-components/template-document-completed.tsx:64
#: packages/ui/components/document/document-download-button.tsx:68 #: packages/ui/components/document/document-download-button.tsx:68
msgid "Download" msgid "Download"
msgstr "Télécharger" msgstr "Télécharger"
#: packages/lib/constants/document.ts:13
msgid "Draft"
msgstr "Brouillon"
#: packages/ui/primitives/document-dropzone.tsx:162 #: packages/ui/primitives/document-dropzone.tsx:162
msgid "Drag & drop your PDF here." msgid "Drag & drop your PDF here."
msgstr "Faites glisser et déposez votre PDF ici." msgstr "Faites glisser et déposez votre PDF ici."
@ -504,6 +687,14 @@ msgstr "L'email est requis"
msgid "Email Options" msgid "Email Options"
msgstr "Options d'email" msgstr "Options d'email"
#: packages/lib/utils/document-audit-logs.ts:353
msgid "Email resent"
msgstr "Email renvoyé"
#: packages/lib/utils/document-audit-logs.ts:353
msgid "Email sent"
msgstr "Email envoyé"
#: packages/ui/primitives/document-flow/add-fields.tsx:1123 #: packages/ui/primitives/document-flow/add-fields.tsx:1123
msgid "Empty field" msgid "Empty field"
msgstr "Champ vide" msgstr "Champ vide"
@ -564,6 +755,14 @@ msgstr "Étiquette du champ"
msgid "Field placeholder" msgid "Field placeholder"
msgstr "Espace réservé du champ" msgstr "Espace réservé du champ"
#: packages/lib/utils/document-audit-logs.ts:294
msgid "Field signed"
msgstr "Champ signé"
#: packages/lib/utils/document-audit-logs.ts:298
msgid "Field unsigned"
msgstr "Champ non signé"
#: packages/ui/primitives/document-flow/field-items-advanced-settings/date-field.tsx:56 #: packages/ui/primitives/document-flow/field-items-advanced-settings/date-field.tsx:56
#: packages/ui/primitives/document-flow/field-items-advanced-settings/email-field.tsx:38 #: packages/ui/primitives/document-flow/field-items-advanced-settings/email-field.tsx:38
#: packages/ui/primitives/document-flow/field-items-advanced-settings/initials-field.tsx:38 #: packages/ui/primitives/document-flow/field-items-advanced-settings/initials-field.tsx:38
@ -774,6 +973,10 @@ msgstr "Réinitialisation du mot de passe réussie"
msgid "Password updated!" msgid "Password updated!"
msgstr "Mot de passe mis à jour !" msgstr "Mot de passe mis à jour !"
#: packages/lib/constants/document.ts:16
msgid "Pending"
msgstr "En attente"
#: packages/email/templates/document-pending.tsx:17 #: packages/email/templates/document-pending.tsx:17
msgid "Pending Document" msgid "Pending Document"
msgstr "Document En Attente" msgstr "Document En Attente"
@ -841,6 +1044,10 @@ msgstr "Lecture seule"
msgid "Receives copy" msgid "Receives copy"
msgstr "Recevoir une copie" msgstr "Recevoir une copie"
#: packages/lib/utils/document-audit-logs.ts:338
msgid "Recipient"
msgstr "Destinataire"
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:39 #: packages/ui/components/recipient/recipient-action-auth-select.tsx:39
#: packages/ui/primitives/document-flow/add-settings.tsx:257 #: packages/ui/primitives/document-flow/add-settings.tsx:257
#: packages/ui/primitives/template-flow/add-template-settings.tsx:208 #: packages/ui/primitives/template-flow/add-template-settings.tsx:208
@ -1248,6 +1455,10 @@ msgstr "Nous avons changé votre mot de passe comme demandé. Vous pouvez mainte
msgid "Welcome to Documenso!" msgid "Welcome to Documenso!"
msgstr "Bienvenue sur Documenso !" msgstr "Bienvenue sur Documenso !"
#: packages/lib/utils/document-audit-logs.ts:258
msgid "You"
msgstr "Vous"
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:44 #: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:44
msgid "You are about to send this document to the recipients. Are you sure you want to continue?" msgid "You are about to send this document to the recipients. Are you sure you want to continue?"
msgstr "Vous êtes sur le point d'envoyer ce document aux destinataires. Êtes-vous sûr de vouloir continuer ?" msgstr "Vous êtes sur le point d'envoyer ce document aux destinataires. Êtes-vous sûr de vouloir continuer ?"
@ -1309,4 +1520,3 @@ msgstr "Votre mot de passe a été mis à jour."
#: packages/email/templates/team-delete.tsx:30 #: packages/email/templates/team-delete.tsx:30
msgid "Your team has been deleted" msgid "Your team has been deleted"
msgstr "Votre équipe a été supprimée" msgstr "Votre équipe a été supprimée"

View File

@ -8,7 +8,7 @@ msgstr ""
"Language: fr\n" "Language: fr\n"
"Project-Id-Version: documenso-app\n" "Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-11-05 02:04\n" "PO-Revision-Date: 2024-11-05 09:34\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: French\n" "Language-Team: French\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n"
@ -42,7 +42,7 @@ msgstr "Ajouter un document"
msgid "Add More Users for {0}" msgid "Add More Users for {0}"
msgstr "Ajouter plus d'utilisateurs pour {0}" msgstr "Ajouter plus d'utilisateurs pour {0}"
#: apps/marketing/src/app/(marketing)/open/page.tsx:164 #: apps/marketing/src/app/(marketing)/open/page.tsx:165
msgid "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>" msgid "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>"
msgstr "Tous nos indicateurs, finances et apprentissages sont publics. Nous croyons en la transparence et souhaitons partager notre parcours avec vous. Vous pouvez en lire plus sur pourquoi ici : <0>Annonce de Open Metrics</0>" msgstr "Tous nos indicateurs, finances et apprentissages sont publics. Nous croyons en la transparence et souhaitons partager notre parcours avec vous. Vous pouvez en lire plus sur pourquoi ici : <0>Annonce de Open Metrics</0>"
@ -90,7 +90,7 @@ msgstr "Changelog"
msgid "Choose a template from the community app store. Or submit your own template for others to use." msgid "Choose a template from the community app store. Or submit your own template for others to use."
msgstr "Choisissez un modèle dans la boutique d'applications communautaires. Ou soumettez votre propre modèle pour que d'autres puissent l'utiliser." msgstr "Choisissez un modèle dans la boutique d'applications communautaires. Ou soumettez votre propre modèle pour que d'autres puissent l'utiliser."
#: apps/marketing/src/app/(marketing)/open/page.tsx:218 #: apps/marketing/src/app/(marketing)/open/page.tsx:219
msgid "Community" msgid "Community"
msgstr "Communauté" msgstr "Communauté"
@ -193,7 +193,7 @@ msgstr "Rapide."
msgid "Faster, smarter and more beautiful." msgid "Faster, smarter and more beautiful."
msgstr "Plus rapide, plus intelligent et plus beau." msgstr "Plus rapide, plus intelligent et plus beau."
#: apps/marketing/src/app/(marketing)/open/page.tsx:209 #: apps/marketing/src/app/(marketing)/open/page.tsx:210
msgid "Finances" msgid "Finances"
msgstr "Finances" msgstr "Finances"
@ -246,15 +246,15 @@ msgstr "Commencez aujourd'hui."
msgid "Get the latest news from Documenso, including product updates, team announcements and more!" msgid "Get the latest news from Documenso, including product updates, team announcements and more!"
msgstr "Obtenez les dernières nouvelles de Documenso, y compris les mises à jour de produits, les annonces d'équipe et plus encore !" msgstr "Obtenez les dernières nouvelles de Documenso, y compris les mises à jour de produits, les annonces d'équipe et plus encore !"
#: apps/marketing/src/app/(marketing)/open/page.tsx:232 #: apps/marketing/src/app/(marketing)/open/page.tsx:233
msgid "GitHub: Total Merged PRs" msgid "GitHub: Total Merged PRs"
msgstr "GitHub : Total des PRs fusionnées" msgstr "GitHub : Total des PRs fusionnées"
#: apps/marketing/src/app/(marketing)/open/page.tsx:250 #: apps/marketing/src/app/(marketing)/open/page.tsx:251
msgid "GitHub: Total Open Issues" msgid "GitHub: Total Open Issues"
msgstr "GitHub : Total des problèmes ouverts" msgstr "GitHub : Total des problèmes ouverts"
#: apps/marketing/src/app/(marketing)/open/page.tsx:224 #: apps/marketing/src/app/(marketing)/open/page.tsx:225
msgid "GitHub: Total Stars" msgid "GitHub: Total Stars"
msgstr "GitHub : Nombre total d'étoiles" msgstr "GitHub : Nombre total d'étoiles"
@ -262,7 +262,7 @@ msgstr "GitHub : Nombre total d'étoiles"
msgid "Global Salary Bands" msgid "Global Salary Bands"
msgstr "Bandes de salaire globales" msgstr "Bandes de salaire globales"
#: apps/marketing/src/app/(marketing)/open/page.tsx:260 #: apps/marketing/src/app/(marketing)/open/page.tsx:261
msgid "Growth" msgid "Growth"
msgstr "Croissance" msgstr "Croissance"
@ -286,7 +286,7 @@ msgstr "Paiements intégrés avec Stripe afin que vous n'ayez pas à vous soucie
msgid "Integrates with all your favourite tools." msgid "Integrates with all your favourite tools."
msgstr "S'intègre à tous vos outils préférés." msgstr "S'intègre à tous vos outils préférés."
#: apps/marketing/src/app/(marketing)/open/page.tsx:288 #: apps/marketing/src/app/(marketing)/open/page.tsx:289
msgid "Is there more?" msgid "Is there more?"
msgstr "Y a-t-il plus ?" msgstr "Y a-t-il plus ?"
@ -310,11 +310,11 @@ msgstr "Emplacement"
msgid "Make it your own through advanced customization and adjustability." msgid "Make it your own through advanced customization and adjustability."
msgstr "Faites-en votre propre grâce à une personnalisation avancée et un ajustement." msgstr "Faites-en votre propre grâce à une personnalisation avancée et un ajustement."
#: apps/marketing/src/app/(marketing)/open/page.tsx:198 #: apps/marketing/src/app/(marketing)/open/page.tsx:199
msgid "Merged PR's" msgid "Merged PR's"
msgstr "PRs fusionnées" msgstr "PRs fusionnées"
#: apps/marketing/src/app/(marketing)/open/page.tsx:233 #: apps/marketing/src/app/(marketing)/open/page.tsx:234
msgid "Merged PRs" msgid "Merged PRs"
msgstr "PRs fusionnées" msgstr "PRs fusionnées"
@ -345,8 +345,8 @@ msgstr "Aucune carte de crédit requise"
msgid "None of these work for you? Try self-hosting!" msgid "None of these work for you? Try self-hosting!"
msgstr "Aucune de ces options ne fonctionne pour vous ? Essayez l'hébergement autonome !" msgstr "Aucune de ces options ne fonctionne pour vous ? Essayez l'hébergement autonome !"
#: apps/marketing/src/app/(marketing)/open/page.tsx:193 #: apps/marketing/src/app/(marketing)/open/page.tsx:194
#: apps/marketing/src/app/(marketing)/open/page.tsx:251 #: apps/marketing/src/app/(marketing)/open/page.tsx:252
msgid "Open Issues" msgid "Open Issues"
msgstr "Problèmes ouverts" msgstr "Problèmes ouverts"
@ -354,7 +354,7 @@ msgstr "Problèmes ouverts"
msgid "Open Source or Hosted." msgid "Open Source or Hosted."
msgstr "Open Source ou hébergé." msgstr "Open Source ou hébergé."
#: apps/marketing/src/app/(marketing)/open/page.tsx:160 #: apps/marketing/src/app/(marketing)/open/page.tsx:161
#: apps/marketing/src/components/(marketing)/footer.tsx:37 #: apps/marketing/src/components/(marketing)/footer.tsx:37
#: apps/marketing/src/components/(marketing)/header.tsx:64 #: apps/marketing/src/components/(marketing)/header.tsx:64
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:40 #: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:40
@ -466,7 +466,7 @@ msgstr "Intelligent."
msgid "Star on GitHub" msgid "Star on GitHub"
msgstr "Étoile sur GitHub" msgstr "Étoile sur GitHub"
#: apps/marketing/src/app/(marketing)/open/page.tsx:225 #: apps/marketing/src/app/(marketing)/open/page.tsx:226
msgid "Stars" msgid "Stars"
msgstr "Étoiles" msgstr "Étoiles"
@ -501,7 +501,7 @@ msgstr "Boutique de modèles (Bientôt)."
msgid "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️" msgid "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️"
msgstr "C'est génial. Vous pouvez consulter les <0>Problèmes</0> actuels et rejoindre notre <1>Communauté Discord</1> pour rester à jour sur ce qui est actuellement prioritaire. Dans tous les cas, nous sommes une communauté ouverte et accueillons toutes les contributions, techniques et non techniques ❤️" msgstr "C'est génial. Vous pouvez consulter les <0>Problèmes</0> actuels et rejoindre notre <1>Communauté Discord</1> pour rester à jour sur ce qui est actuellement prioritaire. Dans tous les cas, nous sommes une communauté ouverte et accueillons toutes les contributions, techniques et non techniques ❤️"
#: apps/marketing/src/app/(marketing)/open/page.tsx:292 #: apps/marketing/src/app/(marketing)/open/page.tsx:293
msgid "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share." msgid "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share."
msgstr "Cette page évolue à mesure que nous apprenons ce qui fait une grande entreprise de signature. Nous la mettrons à jour lorsque nous aurons plus à partager." msgstr "Cette page évolue à mesure que nous apprenons ce qui fait une grande entreprise de signature. Nous la mettrons à jour lorsque nous aurons plus à partager."
@ -514,8 +514,8 @@ msgstr "Titre"
msgid "Total Completed Documents" msgid "Total Completed Documents"
msgstr "Documents totalisés complétés" msgstr "Documents totalisés complétés"
#: apps/marketing/src/app/(marketing)/open/page.tsx:266
#: apps/marketing/src/app/(marketing)/open/page.tsx:267 #: apps/marketing/src/app/(marketing)/open/page.tsx:267
#: apps/marketing/src/app/(marketing)/open/page.tsx:268
msgid "Total Customers" msgid "Total Customers"
msgstr "Total des clients" msgstr "Total des clients"
@ -602,4 +602,3 @@ msgstr "Vous pouvez auto-héberger Documenso gratuitement ou utiliser notre vers
#: apps/marketing/src/components/(marketing)/carousel.tsx:272 #: apps/marketing/src/components/(marketing)/carousel.tsx:272
msgid "Your browser does not support the video tag." msgid "Your browser does not support the video tag."
msgstr "Votre navigateur ne prend pas en charge la balise vidéo." msgstr "Votre navigateur ne prend pas en charge la balise vidéo."

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,10 @@
import type { I18n } from '@lingui/core';
import { msg } from '@lingui/macro';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import type { import type { DocumentAuditLog, DocumentMeta, Field, Recipient } from '@documenso/prisma/client';
DocumentAuditLog, import { RecipientRole } from '@documenso/prisma/client';
DocumentMeta,
Field,
Recipient,
RecipientRole,
} from '@documenso/prisma/client';
import { RECIPIENT_ROLES_DESCRIPTION_ENG } from '../constants/recipient-roles';
import type { import type {
TDocumentAuditLog, TDocumentAuditLog,
TDocumentAuditLogDocumentMetaDiffSchema, TDocumentAuditLogDocumentMetaDiffSchema,
@ -254,129 +250,119 @@ export const diffDocumentMetaChanges = (
* *
* Provide a userId to prefix the action with the user, example 'X did Y'. * Provide a userId to prefix the action with the user, example 'X did Y'.
*/ */
export const formatDocumentAuditLogActionString = ( export const formatDocumentAuditLogAction = (
_: I18n['_'],
auditLog: TDocumentAuditLog, auditLog: TDocumentAuditLog,
userId?: number, userId?: number,
) => { ) => {
const { prefix, description } = formatDocumentAuditLogAction(auditLog, userId); const prefix = userId === auditLog.userId ? _(msg`You`) : auditLog.name || auditLog.email || '';
return prefix ? `${prefix} ${description}` : description;
};
/**
* Formats the audit log into a description of the action.
*
* Provide a userId to prefix the action with the user, example 'X did Y'.
*/
// Todo: Translations.
export const formatDocumentAuditLogAction = (auditLog: TDocumentAuditLog, userId?: number) => {
let prefix = userId === auditLog.userId ? 'You' : auditLog.name || auditLog.email || '';
const description = match(auditLog) const description = match(auditLog)
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_CREATED }, () => ({ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_CREATED }, () => ({
anonymous: 'A field was added', anonymous: msg`A field was added`,
identified: 'added a field', identified: msg`${prefix} added a field`,
})) }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_DELETED }, () => ({ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_DELETED }, () => ({
anonymous: 'A field was removed', anonymous: msg`A field was removed`,
identified: 'removed a field', identified: msg`${prefix} removed a field`,
})) }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_UPDATED }, () => ({ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_UPDATED }, () => ({
anonymous: 'A field was updated', anonymous: msg`A field was updated`,
identified: 'updated a field', identified: msg`${prefix} updated a field`,
})) }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_CREATED }, () => ({ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_CREATED }, () => ({
anonymous: 'A recipient was added', anonymous: msg`A recipient was added`,
identified: 'added a recipient', identified: msg`${prefix} added a recipient`,
})) }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_DELETED }, () => ({ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_DELETED }, () => ({
anonymous: 'A recipient was removed', anonymous: msg`A recipient was removed`,
identified: 'removed a recipient', identified: msg`${prefix} removed a recipient`,
})) }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_UPDATED }, () => ({ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_UPDATED }, () => ({
anonymous: 'A recipient was updated', anonymous: msg`A recipient was updated`,
identified: 'updated a recipient', identified: msg`${prefix} updated a recipient`,
})) }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_CREATED }, () => ({ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_CREATED }, () => ({
anonymous: 'Document created', anonymous: msg`Document created`,
identified: 'created the document', identified: msg`${prefix} created the document`,
})) }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED }, () => ({ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED }, () => ({
anonymous: 'Document deleted', anonymous: msg`Document deleted`,
identified: 'deleted the document', identified: msg`${prefix} deleted the document`,
})) }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_INSERTED }, () => ({ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_INSERTED }, () => ({
anonymous: 'Field signed', anonymous: msg`Field signed`,
identified: 'signed a field', identified: msg`${prefix} signed a field`,
})) }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_UNINSERTED }, () => ({ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_UNINSERTED }, () => ({
anonymous: 'Field unsigned', anonymous: msg`Field unsigned`,
identified: 'unsigned a field', identified: msg`${prefix} unsigned a field`,
})) }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_VISIBILITY_UPDATED }, () => ({ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_VISIBILITY_UPDATED }, () => ({
anonymous: 'Document visibility updated', anonymous: msg`Document visibility updated`,
identified: 'updated the document visibility', identified: msg`${prefix} updated the document visibility`,
})) }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_GLOBAL_AUTH_ACCESS_UPDATED }, () => ({ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_GLOBAL_AUTH_ACCESS_UPDATED }, () => ({
anonymous: 'Document access auth updated', anonymous: msg`Document access auth updated`,
identified: 'updated the document access auth requirements', identified: msg`${prefix} updated the document access auth requirements`,
})) }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_GLOBAL_AUTH_ACTION_UPDATED }, () => ({ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_GLOBAL_AUTH_ACTION_UPDATED }, () => ({
anonymous: 'Document signing auth updated', anonymous: msg`Document signing auth updated`,
identified: 'updated the document signing auth requirements', identified: msg`${prefix} updated the document signing auth requirements`,
})) }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_META_UPDATED }, () => ({ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_META_UPDATED }, () => ({
anonymous: 'Document updated', anonymous: msg`Document updated`,
identified: 'updated the document', identified: msg`${prefix} updated the document`,
})) }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED }, () => ({ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED }, () => ({
anonymous: 'Document opened', anonymous: msg`Document opened`,
identified: 'opened the document', identified: msg`${prefix} opened the document`,
})) }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_TITLE_UPDATED }, () => ({ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_TITLE_UPDATED }, () => ({
anonymous: 'Document title updated', anonymous: msg`Document title updated`,
identified: 'updated the document title', identified: msg`${prefix} updated the document title`,
})) }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_EXTERNAL_ID_UPDATED }, () => ({ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_EXTERNAL_ID_UPDATED }, () => ({
anonymous: 'Document external ID updated', anonymous: msg`Document external ID updated`,
identified: 'updated the document external ID', identified: msg`${prefix} updated the document external ID`,
})) }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT }, () => ({ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT }, () => ({
anonymous: 'Document sent', anonymous: msg`Document sent`,
identified: 'sent the document', identified: msg`${prefix} sent the document`,
})) }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_MOVED_TO_TEAM }, () => ({ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_MOVED_TO_TEAM }, () => ({
anonymous: 'Document moved to team', anonymous: msg`Document moved to team`,
identified: 'moved the document to team', identified: msg`${prefix} moved the document to team`,
})) }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED }, ({ data }) => { .with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED }, ({ data }) => {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions const userName = prefix || _(msg`Recipient`);
const action = RECIPIENT_ROLES_DESCRIPTION_ENG[data.recipientRole as RecipientRole]?.actioned;
const value = action ? `${action.toLowerCase()} the document` : 'completed their task'; const result = match(data.recipientRole)
.with(RecipientRole.SIGNER, () => msg`${userName} signed the document`)
.with(RecipientRole.VIEWER, () => msg`${userName} viewed the document`)
.with(RecipientRole.APPROVER, () => msg`${userName} approved the document`)
.with(RecipientRole.CC, () => msg`${userName} CC'd the document`)
.otherwise(() => msg`${userName} completed their task`);
return { return {
anonymous: `Recipient ${value}`, anonymous: result,
identified: value, identified: result,
}; };
}) })
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT }, ({ data }) => ({ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT }, ({ data }) => ({
anonymous: `Email ${data.isResending ? 'resent' : 'sent'}`, anonymous: data.isResending ? msg`Email resent` : msg`Email sent`,
identified: `${data.isResending ? 'resent' : 'sent'} an email to ${data.recipientEmail}`, identified: data.isResending
? msg`${prefix} resent an email to ${data.recipientEmail}`
: msg`${prefix} sent an email to ${data.recipientEmail}`,
}))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED }, () => ({
anonymous: msg`Document completed`,
identified: msg`Document completed`,
})) }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED }, () => {
// Clear the prefix since this should be considered an 'anonymous' event.
prefix = '';
return {
anonymous: 'Document completed',
identified: 'Document completed',
};
})
.exhaustive(); .exhaustive();
return { return {
prefix, prefix,
description: prefix ? description.identified : description.anonymous, description: _(prefix ? description.identified : description.anonymous),
}; };
}; };

View File

@ -1,5 +1,9 @@
import { type Field, type Recipient, RecipientRole, SigningStatus } from '@documenso/prisma/client'; import { type Field, type Recipient, RecipientRole, SigningStatus } from '@documenso/prisma/client';
import { NEXT_PUBLIC_WEBAPP_URL } from '../constants/app';
export const formatSigningLink = (token: string) => `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${token}`;
/** /**
* Whether a recipient can be modified by the document owner. * Whether a recipient can be modified by the document owner.
*/ */

View File

@ -11,6 +11,7 @@ import { createDocument } from '@documenso/lib/server-only/document/create-docum
import { deleteDocument } from '@documenso/lib/server-only/document/delete-document'; import { deleteDocument } from '@documenso/lib/server-only/document/delete-document';
import { duplicateDocumentById } from '@documenso/lib/server-only/document/duplicate-document-by-id'; import { duplicateDocumentById } from '@documenso/lib/server-only/document/duplicate-document-by-id';
import { findDocumentAuditLogs } from '@documenso/lib/server-only/document/find-document-audit-logs'; import { findDocumentAuditLogs } from '@documenso/lib/server-only/document/find-document-audit-logs';
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id'; import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token'; import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id'; import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
@ -31,6 +32,7 @@ import {
ZDownloadAuditLogsMutationSchema, ZDownloadAuditLogsMutationSchema,
ZDownloadCertificateMutationSchema, ZDownloadCertificateMutationSchema,
ZFindDocumentAuditLogsQuerySchema, ZFindDocumentAuditLogsQuerySchema,
ZFindDocumentsQuerySchema,
ZGetDocumentByIdQuerySchema, ZGetDocumentByIdQuerySchema,
ZGetDocumentByTokenQuerySchema, ZGetDocumentByTokenQuerySchema,
ZGetDocumentWithDetailsByIdQuerySchema, ZGetDocumentWithDetailsByIdQuerySchema,
@ -190,6 +192,37 @@ export const documentRouter = router({
} }
}), }),
findDocuments: authenticatedProcedure
.input(ZFindDocumentsQuerySchema)
.query(async ({ input, ctx }) => {
const { user } = ctx;
const { search, teamId, templateId, page, perPage, orderBy, source, status } = input;
try {
const documents = await findDocuments({
userId: user.id,
teamId,
templateId,
search,
source,
status,
page,
perPage,
orderBy,
});
return documents;
} catch (err) {
console.error(err);
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'We are unable to search for documents. Please try again later.',
});
}
}),
findDocumentAuditLogs: authenticatedProcedure findDocumentAuditLogs: authenticatedProcedure
.input(ZFindDocumentAuditLogsQuerySchema) .input(ZFindDocumentAuditLogsQuerySchema)
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {

View File

@ -7,7 +7,30 @@ import {
} from '@documenso/lib/types/document-auth'; } from '@documenso/lib/types/document-auth';
import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params'; import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params';
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url'; import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
import { DocumentSigningOrder, FieldType, RecipientRole } from '@documenso/prisma/client'; import {
DocumentSigningOrder,
DocumentSource,
DocumentStatus,
FieldType,
RecipientRole,
} from '@documenso/prisma/client';
export const ZFindDocumentsQuerySchema = ZBaseTableSearchParamsSchema.extend({
teamId: z.number().min(1).optional(),
templateId: z.number().min(1).optional(),
search: z
.string()
.optional()
.catch(() => undefined),
source: z.nativeEnum(DocumentSource).optional(),
status: z.nativeEnum(DocumentStatus).optional(),
orderBy: z
.object({
column: z.enum(['createdAt']),
direction: z.enum(['asc', 'desc']),
})
.optional(),
}).omit({ query: true });
export const ZFindDocumentAuditLogsQuerySchema = ZBaseTableSearchParamsSchema.extend({ export const ZFindDocumentAuditLogsQuerySchema = ZBaseTableSearchParamsSchema.extend({
documentId: z.number().min(1), documentId: z.number().min(1),

View File

@ -0,0 +1,82 @@
'use client';
import { useState } from 'react';
import { motion } from 'framer-motion';
import { AnimatePresence } from 'framer-motion';
import { CheckSquareIcon, CopyIcon } from 'lucide-react';
import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to-clipboard';
import { Button } from '@documenso/ui/primitives/button';
import { cn } from '../../lib/utils';
export type CopyTextButtonProps = {
value: string;
badgeContentUncopied?: React.ReactNode;
badgeContentCopied?: React.ReactNode;
onCopySuccess?: () => void;
};
export const CopyTextButton = ({
value,
onCopySuccess,
badgeContentUncopied,
badgeContentCopied,
}: CopyTextButtonProps) => {
const [, copy] = useCopyToClipboard();
const [copiedTimeout, setCopiedTimeout] = useState<NodeJS.Timeout | null>(null);
const onCopy = async () => {
await copy(value).then(() => onCopySuccess?.());
if (copiedTimeout) {
clearTimeout(copiedTimeout);
}
setCopiedTimeout(
setTimeout(() => {
setCopiedTimeout(null);
}, 2000),
);
};
return (
<Button
type="button"
variant="none"
className="ml-2 h-7 rounded border bg-neutral-50 px-0.5 font-normal dark:border dark:border-neutral-500 dark:bg-neutral-600"
onClick={async () => onCopy()}
>
<AnimatePresence mode="wait" initial={false}>
<motion.div
className="flex flex-row items-center"
key={copiedTimeout ? 'copied' : 'copy'}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0, transition: { duration: 0.1 } }}
>
{copiedTimeout ? badgeContentCopied : badgeContentUncopied}
<div
className={cn(
'flex h-6 w-6 items-center justify-center rounded transition-all hover:bg-neutral-200 hover:active:bg-neutral-300 dark:hover:bg-neutral-500 dark:hover:active:bg-neutral-400',
{
'ml-1': Boolean(badgeContentCopied || badgeContentUncopied),
},
)}
>
<div className="absolute">
{copiedTimeout ? (
<CheckSquareIcon className="h-3.5 w-3.5" />
) : (
<CopyIcon className="h-3.5 w-3.5" />
)}
</div>
</div>
</motion.div>
</AnimatePresence>
</Button>
);
};

View File

@ -105,6 +105,7 @@
"NEXT_PRIVATE_SMTP_UNSAFE_IGNORE_TLS", "NEXT_PRIVATE_SMTP_UNSAFE_IGNORE_TLS",
"NEXT_PRIVATE_SMTP_FROM_NAME", "NEXT_PRIVATE_SMTP_FROM_NAME",
"NEXT_PRIVATE_SMTP_FROM_ADDRESS", "NEXT_PRIVATE_SMTP_FROM_ADDRESS",
"NEXT_PRIVATE_SMTP_SERVICE",
"NEXT_PRIVATE_STRIPE_API_KEY", "NEXT_PRIVATE_STRIPE_API_KEY",
"NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET", "NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET",
"NEXT_PRIVATE_GITHUB_TOKEN", "NEXT_PRIVATE_GITHUB_TOKEN",