Compare commits

..

30 Commits

Author SHA1 Message Date
22665543c0 fix: refactor api routes 2024-12-30 21:01:03 +11:00
df33fbf91b feat: admin ui for disabling users (#1547) 2024-12-30 14:45:33 +11:00
ee6efc4cca fix: avoid having a drawn and typed signature at the same time (#1516) 2024-12-27 20:29:12 +11:00
6da15ab12b feat: open the advanced settings automatically (#1508) 2024-12-27 19:57:24 +11:00
Tom
7ef2a8769b chore: update French translations (#1555) 2024-12-27 19:39:57 +11:00
487f52e194 feat: enable optional fields (#1470) 2024-12-27 19:30:44 +11:00
39b1c5bbec feat: additional valid password (#1456) 2024-12-27 16:02:45 +11:00
32857bbfeb fix: make small fields draggable (#1551) 2024-12-27 15:22:56 +11:00
41218e2585 chore: extract translations 2024-12-26 22:08:52 +11:00
a1a2d0801b feat: notify owner when a recipient signs (#1549) 2024-12-26 22:04:13 +11:00
c588c09b26 fix: remove unwanted semicolon (#1545) 2024-12-26 17:28:22 +11:00
74382e21e7 feat: add get recipient route (#1553) 2024-12-26 17:25:14 +11:00
8a7ec7e982 fix: billing page formatting (#1554) 2024-12-26 17:20:08 +11:00
2948a33bf9 fix: tests (#1556) 2024-12-26 17:00:55 +11:00
98b2da5018 v1.9.0-rc.5 2024-12-26 13:41:04 +11:00
fc1f76b543 fix: checkbox logic (#1537)
## Description

<!--- Describe the changes introduced by this pull request. -->
<!--- Explain what problem it solves or what feature/fix it adds. -->

## Related Issue

<!--- If this pull request is related to a specific issue, reference it
here using #issue_number. -->
<!--- For example, "Fixes #123" or "Addresses #456". -->

## Changes Made

<!--- Provide a summary of the changes made in this pull request. -->
<!--- Include any relevant technical details or architecture changes.
-->

- Change 1
- Change 2
- ...

## Testing Performed

<!--- Describe the testing that you have performed to validate these
changes. -->
<!--- Include information about test cases, testing environments, and
results. -->

- Tested feature X in scenario Y.
- Ran unit tests for component Z.
- Tested on browsers A, B, and C.
- ...

## Checklist

<!--- Please check the boxes that apply to this pull request. -->
<!--- You can add or remove items as needed. -->

- [ ] I have tested these changes locally and they work as expected.
- [ ] I have added/updated tests that prove the effectiveness of these
changes.
- [ ] I have updated the documentation to reflect these changes, if
applicable.
- [ ] I have followed the project's coding style guidelines.
- [ ] I have addressed the code review feedback from the previous
submission, if applicable.

## Additional Notes

<!--- Provide any additional context or notes for the reviewers. -->
<!--- This might include details about design decisions, potential
concerns, or anything else relevant. -->
2024-12-23 12:06:47 +02:00
22c9fb777b fix: perf improvements 2024-12-18 15:01:57 +11:00
2da051a7f9 v1.9.0-rc.4 2024-12-18 08:14:50 +11:00
390a317bd3 fix: normalize pdf on the server 2024-12-18 08:14:14 +11:00
c161553d1d feat: add disabled property for user (#1546) 2024-12-17 22:35:59 +11:00
c960a48b4f fix: z-index of field settings (#1469) 2024-12-17 17:09:58 +11:00
9502f4361d fix: fieldtooltip blocking the field click (#1538)
## Before (error)


https://github.com/user-attachments/assets/525e6c04-fc03-41a7-8299-2a753e9e9fa6

## After (fixed)


https://github.com/user-attachments/assets/67f7e592-c5ca-47f4-962c-e4a848522d43
2024-12-17 17:04:20 +11:00
82deab41f4 fix: move permission check outside the document visibility component (#1543)
PR created because of this comment
https://github.com/documenso/documenso/pull/1521#discussion_r1881895305.
2024-12-17 17:03:08 +11:00
2245812f0b fix: document visibility logic (#1521)
Update the logic of document visibility logic and added some tests &
updated some existing ones.
2024-12-16 09:10:40 +02:00
861e9c976b v1.9.0-rc.3 2024-12-16 09:35:33 +11:00
f55808199b feat: make enterprise billing dynamic (#1539) 2024-12-14 13:44:25 +09:00
b4a7f1887d feat: add trpc openapi (#1535) 2024-12-14 01:23:35 +09:00
f73441ee85 chore: prevent user selection within signature pad (#1530)
adds a `select-none` class to the signature pad in order to
prevent iPadOS from becoming too trigger happy with the context menu and
auto corrects. Please ensure this doesn't break anything by accident.
2024-12-13 16:02:26 +11:00
d7de3b08c1 fix: darkmode on radio button and checkbox labels (#1518)
Fixed Radio Button and Checkbox Appearance in Dark Mode
2024-12-13 15:55:40 +11:00
7d201f05d9 fix: admin leaderboard query (#1522)
Co-authored-by: Lucas Smith <me@lucasjamessmith.me>
2024-12-13 15:50:52 +11:00
215 changed files with 6655 additions and 4301 deletions

View File

@ -8,7 +8,7 @@ jobs:
e2e_tests:
name: 'E2E Tests'
timeout-minutes: 60
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4

View File

@ -4,7 +4,7 @@ import '../styles.css';
export default function App({ Component, pageProps }) {
return (
<PlausibleProvider>
<Component {...pageProps} />;
<Component {...pageProps} />
</PlausibleProvider>
);
}

View File

@ -17,23 +17,25 @@ The default document visibility option allows you to control who can view and ac
The default document visibility is set to "_EVERYONE_" by default. You can change this setting by going to the [team's general preferences page](/users/teams/preferences) and selecting a different visibility option.
<Callout type="warning">
If the team member uploading the document has a role lower than the default document visibility,
the document visibility will be set to a lower visibility level matching the team member's role.
</Callout>
Here's how it works:
- If a user with the "_Member_" role creates a document and the default document visibility is set to "_Admin_" or "_Managers and above_", the document's visibility is set to "_Everyone_".
- If a user with the "_Manager_" role creates a document and the default document visibility is set to "_Admin_", the document's visibility is set to "_Managers and above_".
- Otherwise, the document's visibility is set to the default document visibility.
- If a user with the "_Member_" role creates a document and the default document visibility is set to "_Everyone_", the document's visibility is set to "_EVERYONE_".
- The user can't change the visibility of the document in the document editor.
- If a user with the "_Member_" role creates a document and the default document visibility is set to "_Admin_" or "_Managers and above_", the document's visibility is set to the default document visibility ("_Admin_" or "_Managers and above_" in this case).
- The user can't change the visibility of the document in the document editor.
- If a user with the "_Manager_" role creates a document and the default document visibility is set to "_Everyone_" or "_Managers and above_", the document's visibility is set to the default document visibility ("_Everyone_" or "_Managers and above_" in this case).
- The user can change the visibility of the document to any of these options, except "_Admin_", in the document editor.
- If a user with the "_Manager_" role creates a document and the default document visibility is set to "_Admin_", the document's visibility is set to "_Admin_".
- The user can't change the visibility of the document in the document editor.
- If a user with the "_Admin_" role creates a document, and the default document visibility is set to "_Everyone_", "_Managers and above_", or "_Admin_", the document's visibility is set to the default document visibility.
- The user can change the visibility of the document to any of these options in the document editor.
You can change the visibility of a document at any time by editing the document and selecting a different visibility option.
![A screenshot of the Documenso's document editor page where you can update the document visibility](/teams/document-visibility-settings.webp)
<Callout type="warning">
Updating the default document visibility in the team's general settings will not affect the
Updating the default document visibility in the team's general preferences will not affect the
visibility of existing documents. You will need to update the visibility of each document
individually.
</Callout>

View File

@ -1,6 +1,6 @@
{
"name": "@documenso/marketing",
"version": "1.9.0-rc.2",
"version": "1.9.0-rc.5",
"private": true,
"license": "AGPL-3.0",
"scripts": {
@ -47,7 +47,7 @@
"recharts": "^2.7.2",
"sharp": "0.32.6",
"typescript": "5.2.2",
"zod": "^3.23.8"
"zod": "3.24.1"
},
"devDependencies": {
"@lingui/loader": "^4.11.3",

View File

@ -10,16 +10,13 @@ import { msg } from '@lingui/macro';
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { base64 } from '@documenso/lib/universal/base64';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
import type { Field, Recipient } from '@documenso/prisma/client';
import { DocumentDataType, Prisma } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import { DocumentDropzone } from '@documenso/ui/primitives/document-dropzone';
import { AddFieldsFormPartial } from '@documenso/ui/primitives/document-flow/add-fields';
import type { TAddFieldsFormSchema } from '@documenso/ui/primitives/document-flow/add-fields.types';
import { AddSignatureFormPartial } from '@documenso/ui/primitives/document-flow/add-signature';
import type { TAddSignatureFormSchema } from '@documenso/ui/primitives/document-flow/add-signature.types';
import { DocumentFlowFormContainer } from '@documenso/ui/primitives/document-flow/document-flow-root';
import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types';
import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
@ -43,9 +40,6 @@ export const SinglePlayerClient = () => {
const [step, setStep] = useState<SinglePlayerModeStep>('fields');
const [fields, setFields] = useState<Field[]>([]);
const { mutateAsync: createSinglePlayerDocument } =
trpc.singleplayer.createSinglePlayerDocument.useMutation();
const documentFlow: Record<SinglePlayerModeStep, DocumentFlowStep> = {
fields: {
title: msg`Add document`,
@ -112,38 +106,35 @@ export const SinglePlayerClient = () => {
/**
* Upload, create, sign and send the document.
*/
const onSignSubmit = async (data: TAddSignatureFormSchema) => {
const onSignSubmit = (data: unknown) => {
if (!uploadedFile) {
return;
}
try {
const putFileData = await putPdfFile(uploadedFile.file);
const documentToken = await createSinglePlayerDocument({
documentData: {
type: putFileData.type,
data: putFileData.data,
},
documentName: uploadedFile.file.name,
signer: data,
fields: fields.map((field) => ({
page: field.page,
type: field.type,
positionX: field.positionX.toNumber(),
positionY: field.positionY.toNumber(),
width: field.width.toNumber(),
height: field.height.toNumber(),
fieldMeta: field.fieldMeta,
})),
fieldMeta: { type: undefined },
});
analytics.capture('Marketing: SPM - Document signed', {
signer: data.email,
});
router.push(`/singleplayer/${documentToken}/success`);
// const putFileData = await putPdfFile(uploadedFile.file);
// const documentToken = await createSinglePlayerDocument({
// documentData: {
// type: putFileData.type,
// data: putFileData.data,
// },
// documentName: uploadedFile.file.name,
// signer: data,
// fields: fields.map((field) => ({
// page: field.page,
// type: field.type,
// positionX: field.positionX.toNumber(),
// positionY: field.positionY.toNumber(),
// width: field.width.toNumber(),
// height: field.height.toNumber(),
// fieldMeta: field.fieldMeta,
// })),
// fieldMeta: { type: undefined },
// });
// analytics.capture('Marketing: SPM - Document signed', {
// signer: data.email,
// });
// router.push(`/singleplayer/${documentToken}/success`);
} catch {
toast({
title: 'Something went wrong',

View File

@ -2,7 +2,7 @@ import cors from '@/lib/cors';
import { getUserMonthlyGrowth } from '@/lib/growth/get-user-monthly-growth';
export async function GET(request: Request) {
const totalUsers = await getUserMonthlyGrowth("cumulative");
const totalUsers = await getUserMonthlyGrowth('cumulative');
return cors(
request,

View File

@ -30,10 +30,10 @@ function isOriginAllowed(origin: string, allowed: StaticOrigin): boolean {
return Array.isArray(allowed)
? allowed.some((o) => isOriginAllowed(origin, o))
: typeof allowed === 'string'
? origin === allowed
: allowed instanceof RegExp
? allowed.test(origin)
: !!allowed;
? origin === allowed
: allowed instanceof RegExp
? allowed.test(origin)
: !!allowed;
}
function getOriginHeaders(reqOrigin: string | undefined, origin: StaticOrigin) {

View File

@ -1,6 +1,6 @@
{
"name": "@documenso/web",
"version": "1.9.0-rc.2",
"version": "1.9.0-rc.5",
"private": true,
"license": "AGPL-3.0",
"scripts": {
@ -56,10 +56,11 @@
"recharts": "^2.7.2",
"remeda": "^2.17.3",
"sharp": "0.32.6",
"trpc-openapi": "^1.2.0",
"ts-pattern": "^5.0.5",
"ua-parser-js": "^1.0.37",
"uqr": "^0.1.2",
"zod": "^3.23.8"
"zod": "3.24.1"
},
"devDependencies": {
"@documenso/tailwind-config": "*",

View File

@ -134,7 +134,7 @@ export const LeaderboardTable = ({
startTransition(() => {
updateSearchParams({
sortBy: column,
sortOrder: sortOrder === 'asc' ? 'desc' : 'asc',
sortOrder: sortBy === column && sortOrder === 'asc' ? 'desc' : 'asc',
});
});
};

View File

@ -30,8 +30,8 @@ export type DeleteUserDialogProps = {
};
export const DeleteUserDialog = ({ className, user }: DeleteUserDialogProps) => {
const { toast } = useToast();
const { _ } = useLingui();
const { toast } = useToast();
const router = useRouter();
@ -44,7 +44,6 @@ export const DeleteUserDialog = ({ className, user }: DeleteUserDialogProps) =>
try {
await deleteUser({
id: user.id,
email,
});
toast({
@ -78,7 +77,7 @@ export const DeleteUserDialog = ({ className, user }: DeleteUserDialogProps) =>
return (
<div className={className}>
<Alert
className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row "
className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row"
variant="neutral"
>
<div>

View File

@ -0,0 +1,141 @@
'use client';
import { useState } from 'react';
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { match } from 'ts-pattern';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import type { User } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react';
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
export type DisableUserDialogProps = {
className?: string;
userToDisable: User;
};
export const DisableUserDialog = ({ className, userToDisable }: DisableUserDialogProps) => {
const { _ } = useLingui();
const { toast } = useToast();
const [email, setEmail] = useState('');
const { mutateAsync: disableUser, isLoading: isDisablingUser } =
trpc.admin.disableUser.useMutation();
const onDisableAccount = async () => {
try {
await disableUser({
id: userToDisable.id,
});
toast({
title: _(msg`Account disabled`),
description: _(msg`The account has been disabled successfully.`),
duration: 5000,
});
} catch (err) {
const error = AppError.parseError(err);
const errorMessage = match(error.code)
.with(AppErrorCode.NOT_FOUND, () => msg`User not found.`)
.with(AppErrorCode.UNAUTHORIZED, () => msg`You are not authorized to disable this user.`)
.otherwise(() => msg`An error occurred while disabling the user.`);
toast({
title: _(msg`Error`),
description: _(errorMessage),
variant: 'destructive',
duration: 7500,
});
}
};
return (
<div className={className}>
<Alert
className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row"
variant="neutral"
>
<div>
<AlertTitle>Disable Account</AlertTitle>
<AlertDescription className="mr-2">
<Trans>
Disabling the user results in the user not being able to use the account. It also
disables all the related contents such as subscription, webhooks, teams, and API keys.
</Trans>
</AlertDescription>
</div>
<div className="flex-shrink-0">
<Dialog>
<DialogTrigger asChild>
<Button variant="destructive">
<Trans>Disable Account</Trans>
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader className="space-y-4">
<DialogTitle>
<Trans>Disable Account</Trans>
</DialogTitle>
<Alert variant="destructive">
<AlertDescription className="selection:bg-red-100">
<Trans>
This action is reversible, but please be careful as the account may be
affected permanently (e.g. their settings and contents not being restored
properly).
</Trans>
</AlertDescription>
</Alert>
</DialogHeader>
<div>
<DialogDescription>
<Trans>
To confirm, please enter the accounts email address <br />({userToDisable.email}
).
</Trans>
</DialogDescription>
<Input
className="mt-2"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<DialogFooter>
<Button
onClick={onDisableAccount}
loading={isDisablingUser}
variant="destructive"
disabled={email !== userToDisable.email}
>
<Trans>Disable account</Trans>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</Alert>
</div>
);
};

View File

@ -0,0 +1,130 @@
'use client';
import { useState } from 'react';
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { match } from 'ts-pattern';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import type { User } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react';
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
export type EnableUserDialogProps = {
className?: string;
userToEnable: User;
};
export const EnableUserDialog = ({ className, userToEnable }: EnableUserDialogProps) => {
const { toast } = useToast();
const { _ } = useLingui();
const [email, setEmail] = useState('');
const { mutateAsync: enableUser, isLoading: isEnablingUser } =
trpc.admin.enableUser.useMutation();
const onEnableAccount = async () => {
try {
await enableUser({
id: userToEnable.id,
});
toast({
title: _(msg`Account enabled`),
description: _(msg`The account has been enabled successfully.`),
duration: 5000,
});
} catch (err) {
const error = AppError.parseError(err);
const errorMessage = match(error.code)
.with(AppErrorCode.NOT_FOUND, () => msg`User not found.`)
.with(AppErrorCode.UNAUTHORIZED, () => msg`You are not authorized to enable this user.`)
.otherwise(() => msg`An error occurred while enabling the user.`);
toast({
title: _(msg`Error`),
description: _(errorMessage),
variant: 'destructive',
duration: 7500,
});
}
};
return (
<div className={className}>
<Alert
className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row"
variant="neutral"
>
<div>
<AlertTitle>Enable Account</AlertTitle>
<AlertDescription className="mr-2">
<Trans>
Enabling the account results in the user being able to use the account again, and all
the related features such as webhooks, teams, and API keys for example.
</Trans>
</AlertDescription>
</div>
<div className="flex-shrink-0">
<Dialog>
<DialogTrigger asChild>
<Button>
<Trans>Enable Account</Trans>
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader className="space-y-4">
<DialogTitle>
<Trans>Enable Account</Trans>
</DialogTitle>
</DialogHeader>
<div>
<DialogDescription>
<Trans>
To confirm, please enter the accounts email address <br />({userToEnable.email}
).
</Trans>
</DialogDescription>
<Input
className="mt-2"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<DialogFooter>
<Button
onClick={onEnableAccount}
loading={isEnablingUser}
disabled={email !== userToEnable.email}
>
<Trans>Enable account</Trans>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</Alert>
</div>
);
};

View File

@ -23,6 +23,8 @@ import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { DeleteUserDialog } from './delete-user-dialog';
import { DisableUserDialog } from './disable-user-dialog';
import { EnableUserDialog } from './enable-user-dialog';
import { MultiSelectRoleCombobox } from './multiselect-role-combobox';
const ZUserFormSchema = ZAdminUpdateProfileMutationSchema.omit({ id: true });
@ -35,7 +37,7 @@ export default function UserPage({ params }: { params: { id: number } }) {
const router = useRouter();
const { data: user } = trpc.profile.getUser.useQuery(
const { data: user } = trpc.admin.getUser.useQuery(
{
id: Number(params.id),
},
@ -153,7 +155,11 @@ export default function UserPage({ params }: { params: { id: number } }) {
<hr className="my-4" />
{user && <DeleteUserDialog user={user} />}
<div className="flex flex-col items-center gap-4">
{user && <DeleteUserDialog user={user} />}
{user && user.disabled && <EnableUserDialog userToEnable={user} />}
{user && !user.disabled && <DisableUserDialog userToDisable={user} />}
</div>
</div>
);
}

View File

@ -37,10 +37,8 @@ export const DocumentPageViewRecentActivity = ({
{
documentId,
filterForRecentActivity: true,
orderBy: {
column: 'createdAt',
direction: 'asc',
},
orderByColumn: 'createdAt',
orderByDirection: 'asc',
perPage: 10,
},
{

View File

@ -12,8 +12,8 @@ import {
DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
SKIP_QUERY_BATCH_META,
} from '@documenso/lib/constants/trpc';
import type { TGetDocumentWithDetailsByIdResponse } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
import { DocumentDistributionMethod, DocumentStatus } from '@documenso/prisma/client';
import type { DocumentWithDetails } from '@documenso/prisma/types/document';
import { trpc } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils';
import { Card, CardContent } from '@documenso/ui/primitives/card';
@ -35,7 +35,7 @@ import { useOptionalCurrentTeam } from '~/providers/team';
export type EditDocumentFormProps = {
className?: string;
initialDocument: DocumentWithDetails;
initialDocument: TGetDocumentWithDetailsByIdResponse;
documentRootPath: string;
isDocumentEnterprise: boolean;
};
@ -103,7 +103,7 @@ export const EditDocumentForm = ({
const { mutateAsync: addFields } = trpc.field.addFields.useMutation({
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
onSuccess: (newFields) => {
onSuccess: ({ fields: newFields }) => {
utils.document.getDocumentWithDetailsById.setData(
{
documentId: initialDocument.id,
@ -132,9 +132,9 @@ export const EditDocumentForm = ({
},
});
const { mutateAsync: addSigners } = trpc.recipient.addSigners.useMutation({
const { mutateAsync: addSigners } = trpc.recipient.setDocumentRecipients.useMutation({
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
onSuccess: (newRecipients) => {
onSuccess: ({ recipients: newRecipients }) => {
utils.document.getDocumentWithDetailsById.setData(
{
documentId: initialDocument.id,

View File

@ -9,8 +9,8 @@ import { DateTime } from 'luxon';
import { useSession } from 'next-auth/react';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import type { FindResultResponse } from '@documenso/lib/types/search-params';
import type { Document, Recipient, Team, User } from '@documenso/prisma/client';
import type { TFindDocumentsResponse } from '@documenso/lib/server-only/document/find-documents';
import type { Team } from '@documenso/prisma/client';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
import { DataTable } from '@documenso/ui/primitives/data-table';
@ -24,13 +24,7 @@ import { DataTableActionDropdown } from './data-table-action-dropdown';
import { DataTableTitle } from './data-table-title';
export type DocumentsDataTableProps = {
results: FindResultResponse<
Document & {
Recipient: Recipient[];
User: Pick<User, 'id' | 'name' | 'email'>;
team: Pick<Team, 'id' | 'url'> | null;
}
>;
results: TFindDocumentsResponse;
showSenderColumn?: boolean;
team?: Pick<Team, 'id' | 'url'> & { teamEmail?: string };
};

View File

@ -26,10 +26,8 @@ export const TemplatePageViewRecentActivity = ({
const { data, isLoading, isLoadingError, refetch } = trpc.document.findDocuments.useQuery({
templateId,
teamId,
orderBy: {
column: 'createdAt',
direction: 'asc',
},
orderByColumn: 'createdAt',
orderByDirection: 'asc',
perPage: 5,
});

View File

@ -4,8 +4,10 @@ import { useRouter } from 'next/navigation';
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { match } from 'ts-pattern';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
import { Avatar, AvatarFallback, AvatarImage } from '@documenso/ui/primitives/avatar';
import { Button } from '@documenso/ui/primitives/button';
@ -51,10 +53,20 @@ export const MoveTemplateDialog = ({ templateId, open, onOpenChange }: MoveTempl
});
onOpenChange(false);
},
onError: (error) => {
onError: (err) => {
const error = AppError.parseError(err);
const errorMessage = match(error.code)
.with(
AppErrorCode.NOT_FOUND,
() => msg`Template not found or already associated with a team.`,
)
.with(AppErrorCode.UNAUTHORIZED, () => msg`You are not a member of this team.`)
.otherwise(() => msg`An error occurred while moving the template.`);
toast({
title: _(msg`Error`),
description: error.message || _(msg`An error occurred while moving the template.`),
description: _(errorMessage),
variant: 'destructive',
duration: 7500,
});

View File

@ -12,6 +12,7 @@ import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/tr
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
import { ZCheckboxFieldMeta } from '@documenso/lib/types/field-meta';
import { fromCheckboxValue, toCheckboxValue } from '@documenso/lib/universal/field-checkbox';
import type { Recipient } from '@documenso/prisma/client';
import type { FieldWithSignatureAndFieldMeta } from '@documenso/prisma/types/field-with-signature-and-fieldmeta';
import { trpc } from '@documenso/trpc/react';
@ -54,6 +55,7 @@ export const CheckboxField = ({
...item,
value: item.value.length > 0 ? item.value : `empty-value-${item.id}`,
}));
const [checkedValues, setCheckedValues] = useState(
values
?.map((item) =>
@ -97,7 +99,7 @@ export const CheckboxField = ({
const payload: TSignFieldWithTokenMutationSchema = {
token: recipient.token,
fieldId: field.id,
value: checkedValues.join(','),
value: toCheckboxValue(checkedValues),
isBase64: true,
authOptions,
};
@ -191,7 +193,7 @@ export const CheckboxField = ({
await signFieldWithToken({
token: recipient.token,
fieldId: field.id,
value: updatedValues.join(','),
value: toCheckboxValue(checkedValues),
isBase64: true,
});
}
@ -228,6 +230,11 @@ export const CheckboxField = ({
}
}, [checkedValues, isLengthConditionMet, field.inserted]);
const parsedCheckedValues = useMemo(
() => fromCheckboxValue(field.customText),
[field.customText],
);
return (
<SigningFieldContainer field={field} onSign={onSign} onRemove={onRemove} type="Checkbox">
{isLoading && (
@ -277,9 +284,7 @@ export const CheckboxField = ({
className="h-3 w-3"
checkClassName="text-white"
id={`checkbox-${index}`}
checked={field.customText
.split(',')
.some((customValue) => customValue === itemValue)}
checked={parsedCheckedValues.includes(itemValue)}
disabled={isLoading}
onCheckedChange={() => void handleCheckboxOptionClick(item)}
/>

View File

@ -178,7 +178,7 @@ export const DropdownField = ({
)}
{!field.inserted && (
<p className="group-hover:text-primary text-muted-foreground flex flex-col items-center justify-center duration-200 ">
<p className="group-hover:text-primary text-muted-foreground flex flex-col items-center justify-center duration-200">
<Select value={localChoice} onValueChange={handleSelectItem}>
<SelectTrigger
className={cn(

View File

@ -11,6 +11,7 @@ import { useForm } from 'react-hook-form';
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
import type { DocumentAndSender } from '@documenso/lib/server-only/document/get-document-by-token';
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
import { isFieldUnsignedAndRequired } from '@documenso/lib/utils/advanced-fields-helpers';
import { sortFieldsByPosition, validateFieldsInserted } from '@documenso/lib/utils/fields';
import { type Field, FieldType, type Recipient, RecipientRole } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react';
@ -57,26 +58,31 @@ export const SigningForm = ({
// Keep the loading state going if successful since the redirect may take some time.
const isSubmitting = formState.isSubmitting || formState.isSubmitSuccessful;
const fieldsRequiringValidation = useMemo(
() => fields.filter(isFieldUnsignedAndRequired),
[fields],
);
const hasSignatureField = fields.some((field) => field.type === FieldType.SIGNATURE);
const uninsertedFields = useMemo(() => {
return sortFieldsByPosition(fields.filter((field) => !field.inserted));
return sortFieldsByPosition(fieldsRequiringValidation.filter((field) => !field.inserted));
}, [fields]);
const fieldsValidated = () => {
setValidateUninsertedFields(true);
validateFieldsInserted(fields);
validateFieldsInserted(fieldsRequiringValidation);
};
const onFormSubmit = async () => {
setValidateUninsertedFields(true);
const isFieldsValid = validateFieldsInserted(fieldsRequiringValidation);
if (hasSignatureField && !signatureValid) {
return;
}
const isFieldsValid = validateFieldsInserted(fields);
if (!isFieldsValid) {
return;
}

View File

@ -205,7 +205,7 @@ export const NameField = ({ field, recipient, onSignField, onUnsignField }: Name
<div className="flex w-full flex-1 flex-nowrap gap-4">
<Button
type="button"
className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10"
className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10"
variant="secondary"
onClick={() => {
setShowFullNameModal(false);

View File

@ -318,7 +318,7 @@ export const NumberField = ({ field, recipient, onSignField, onUnsignField }: Nu
<div className="flex w-full flex-1 flex-nowrap gap-4">
<Button
type="button"
className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10"
className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10"
variant="secondary"
onClick={() => {
setShowRadioModal(false);

View File

@ -1,7 +1,8 @@
import { useState } from 'react';
import { useMemo, useState } from 'react';
import { Trans } from '@lingui/macro';
import { fieldsContainUnsignedRequiredField } from '@documenso/lib/utils/advanced-fields-helpers';
import type { Field } from '@documenso/prisma/client';
import { RecipientRole } from '@documenso/prisma/client';
import { Button } from '@documenso/ui/primitives/button';
@ -36,7 +37,7 @@ export const SignDialog = ({
}: SignDialogProps) => {
const [showDialog, setShowDialog] = useState(false);
const isComplete = fields.every((field) => field.inserted);
const isComplete = useMemo(() => !fieldsContainUnsignedRequiredField(fields), [fields]);
const handleOpenChange = (open: boolean) => {
if (isSubmitting || !isComplete) {

View File

@ -214,15 +214,15 @@ export const TextField = ({ field, recipient, onSignField, onUnsignField }: Text
parsedField?.label && parsedField.label.length < 20
? parsedField.label
: parsedField?.label
? parsedField?.label.substring(0, 20) + '...'
: undefined;
? parsedField?.label.substring(0, 20) + '...'
: undefined;
const textDisplay =
parsedField?.text && parsedField.text.length < 20
? parsedField.text
: parsedField?.text
? parsedField?.text.substring(0, 20) + '...'
: undefined;
? parsedField?.text.substring(0, 20) + '...'
: undefined;
const fieldDisplayName = labelDisplay ? labelDisplay : textDisplay;
const charactersRemaining = (parsedFieldMeta?.characterLimit ?? 0) - (localText.length ?? 0);
@ -325,7 +325,7 @@ export const TextField = ({ field, recipient, onSignField, onUnsignField }: Text
<div className="mt-4 flex w-full flex-1 flex-nowrap gap-4">
<Button
type="button"
className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10"
className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10"
variant="secondary"
onClick={() => {
setShowCustomTextModal(false);

View File

@ -2,6 +2,7 @@ import { Plural, Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { DateTime } from 'luxon';
import type Stripe from 'stripe';
import { match } from 'ts-pattern';
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
@ -44,15 +45,24 @@ export default async function TeamsSettingBillingPage({ params }: TeamsSettingsB
const numberOfSeats = subscription.items.data[0].quantity ?? 0;
const formattedTeamMemberQuanity = (
<Plural value={numberOfSeats} one="# member" other="# members" />
);
const formattedDate = DateTime.fromSeconds(subscription.current_period_end).toFormat(
'LLL dd, yyyy',
);
return _(msg`${formattedTeamMemberQuanity} • Monthly • Renews: ${formattedDate}`);
const subscriptionInterval = match(subscription?.items.data[0].plan.interval)
.with('year', () => _(msg`Yearly`))
.with('month', () => _(msg`Monthly`))
.otherwise(() => _(msg`Unknown`));
return (
<span>
<Plural value={numberOfSeats} one="# member" other="# members" />
{' • '}
<span>{subscriptionInterval}</span>
{' • '}
<Trans>Renews: {formattedDate}</Trans>
</span>
);
};
return (
@ -66,10 +76,6 @@ export default async function TeamsSettingBillingPage({ params }: TeamsSettingsB
<CardContent className="flex flex-row items-center justify-between p-4">
<div className="flex flex-col text-sm">
<p className="text-foreground font-semibold">
<Trans>Current plan: {teamSubscription ? 'Team' : 'Early Adopter Team'}</Trans>
</p>
<p className="text-muted-foreground mt-0.5">
{formatTeamSubscriptionDetails(teamSubscription)}
</p>
</div>

View File

@ -47,10 +47,7 @@ export const StackAvatar = ({ first, zIndex, fallbackText = '', type }: StackAva
return (
<Avatar
className={`
${zIndexClass}
${firstClass}
dark:border-border h-10 w-10 border-2 border-solid border-white`}
className={` ${zIndexClass} ${firstClass} dark:border-border h-10 w-10 border-2 border-solid border-white`}
>
<AvatarFallback className={classes}>{fallbackText}</AvatarFallback>
</Avatar>

View File

@ -14,7 +14,7 @@ import type { z } from 'zod';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
import { ZCreateTeamEmailVerificationMutationSchema } from '@documenso/trpc/server/team-router/schema';
import { ZCreateTeamEmailVerificationRequestSchema } from '@documenso/trpc/server/team-router/create-team-email-verification-route';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
@ -41,7 +41,7 @@ export type AddTeamEmailDialogProps = {
trigger?: React.ReactNode;
} & Omit<DialogPrimitive.DialogProps, 'children'>;
const ZCreateTeamEmailFormSchema = ZCreateTeamEmailVerificationMutationSchema.pick({
const ZCreateTeamEmailFormSchema = ZCreateTeamEmailVerificationRequestSchema.pick({
name: true,
email: true,
});

View File

@ -15,7 +15,7 @@ import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-upda
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
import { ZCreateTeamMutationSchema } from '@documenso/trpc/server/team-router/schema';
import { ZCreateTeamRequestSchema } from '@documenso/trpc/server/team-router/create-team-route';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
@ -41,7 +41,7 @@ export type CreateTeamDialogProps = {
trigger?: React.ReactNode;
} & Omit<DialogPrimitive.DialogProps, 'children'>;
const ZCreateTeamFormSchema = ZCreateTeamMutationSchema.pick({
const ZCreateTeamFormSchema = ZCreateTeamRequestSchema.pick({
teamName: true,
teamUrl: true,
});

View File

@ -15,7 +15,7 @@ import { downloadFile } from '@documenso/lib/client-only/download-file';
import { TEAM_MEMBER_ROLE_HIERARCHY, TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams';
import { TeamMemberRole } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react';
import { ZCreateTeamMemberInvitesMutationSchema } from '@documenso/trpc/server/team-router/schema';
import { ZCreateTeamMemberInvitesRequestSchema } from '@documenso/trpc/server/team-router/create-team-member-invites-route';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { Card, CardContent } from '@documenso/ui/primitives/card';
@ -55,7 +55,7 @@ export type InviteTeamMembersDialogProps = {
const ZInviteTeamMembersFormSchema = z
.object({
invitations: ZCreateTeamMemberInvitesMutationSchema.shape.invitations,
invitations: ZCreateTeamMemberInvitesRequestSchema.shape.invitations,
})
// Display exactly which rows are duplicates.
.superRefine((items, ctx) => {

View File

@ -12,7 +12,7 @@ import type { z } from 'zod';
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
import { ZUpdateTeamMutationSchema } from '@documenso/trpc/server/team-router/schema';
import { ZUpdateTeamRequestSchema } from '@documenso/trpc/server/team-router/update-team-route';
import { Button } from '@documenso/ui/primitives/button';
import {
Form,
@ -31,7 +31,7 @@ export type UpdateTeamDialogProps = {
teamUrl: string;
};
const ZUpdateTeamFormSchema = ZUpdateTeamMutationSchema.shape.data.pick({
const ZUpdateTeamFormSchema = ZUpdateTeamRequestSchema.shape.data.pick({
name: true,
url: true,
});

View File

@ -17,8 +17,8 @@ import { formatUserProfilePath } from '@documenso/lib/utils/public-profiles';
import type { TeamProfile, UserProfile } from '@documenso/prisma/client';
import {
MAX_PROFILE_BIO_LENGTH,
ZUpdatePublicProfileMutationSchema,
} from '@documenso/trpc/server/profile-router/schema';
ZUpdatePublicProfileRequestSchema,
} from '@documenso/trpc/server/profile-router/update-public-profile-route';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import {
@ -33,7 +33,7 @@ import { Input } from '@documenso/ui/primitives/input';
import { Textarea } from '@documenso/ui/primitives/textarea';
import { useToast } from '@documenso/ui/primitives/use-toast';
export const ZPublicProfileFormSchema = ZUpdatePublicProfileMutationSchema.pick({
export const ZPublicProfileFormSchema = ZUpdatePublicProfileRequestSchema.pick({
bio: true,
enabled: true,
url: true,

View File

@ -53,6 +53,7 @@ const ERROR_MESSAGES: Partial<Record<keyof typeof ErrorCode, string>> = {
[ErrorCode.INCORRECT_TWO_FACTOR_BACKUP_CODE]: 'The backup code provided is incorrect',
[ErrorCode.UNVERIFIED_EMAIL]:
'This account has not been verified. Please verify your account before signing in.',
[ErrorCode.ACCOUNT_DISABLED]: 'This account has been disabled. Please contact support.',
};
const TwoFactorEnabledErrorCode = ErrorCode.TWO_FACTOR_MISSING_CREDENTIALS;

View File

@ -70,6 +70,7 @@ export const ZSignUpFormV2Schema = z
},
{
message: msg`Password should not be common or based on personal information`.id,
path: ['password'],
},
);

View File

@ -0,0 +1,47 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { createOpenApiNextHandler } from 'trpc-openapi';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { buildLogger } from '@documenso/lib/utils/logger';
import type { TRPCError } from '@documenso/trpc/server';
import { createTrpcContext } from '@documenso/trpc/server/context';
import { appRouter } from '@documenso/trpc/server/router';
const logger = buildLogger();
export default createOpenApiNextHandler<typeof appRouter>({
router: appRouter,
createContext: async ({ req, res }: { req: NextApiRequest; res: NextApiResponse }) =>
createTrpcContext({ req, res }),
onError: ({ error, path }: { error: TRPCError; path?: string }) => {
// Always log the error for now.
console.error(error.message);
const appError = AppError.parseError(error.cause || error);
const isAppError = error.cause instanceof AppError;
// Only log AppErrors that are explicitly set to 500 or the error code
// is in the errorCodesToAlertOn list.
const isLoggableAppError =
isAppError && (appError.statusCode === 500 || errorCodesToAlertOn.includes(appError.code));
// Only log TRPC errors that are in the `errorCodesToAlertOn` list and is
// not an AppError.
const isLoggableTrpcError = !isAppError && errorCodesToAlertOn.includes(error.code);
if (isLoggableAppError || isLoggableTrpcError) {
logger.error(error, {
method: path,
context: {
source: '/v2/api',
appError: AppError.toJSON(appError),
},
});
}
},
responseMeta: () => {},
});
const errorCodesToAlertOn = [AppErrorCode.UNKNOWN_ERROR, 'INTERNAL_SERVER_ERROR'];

View File

@ -0,0 +1,9 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { openApiDocument } from '@documenso/trpc/server/open-api';
const handler = (_req: NextApiRequest, res: NextApiResponse) => {
res.status(200).send(openApiDocument);
};
export default handler;

View File

@ -45,6 +45,7 @@ export default trpcNext.createNextApiHandler({
logger.error(error, {
method: path,
context: {
source: 'trpc',
appError: AppError.toJSON(appError),
},
});

388
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "@documenso/root",
"version": "1.9.0-rc.2",
"version": "1.9.0-rc.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@documenso/root",
"version": "1.9.0-rc.2",
"version": "1.9.0-rc.5",
"workspaces": [
"apps/*",
"packages/*"
@ -17,8 +17,10 @@
"@lingui/core": "^4.11.3",
"inngest-cli": "^0.29.1",
"luxon": "^3.5.0",
"mupdf": "^1.0.0",
"next-runtime-env": "^3.2.0",
"react": "^18"
"react": "^18",
"zod": "3.24.1"
},
"devDependencies": {
"@commitlint/cli": "^17.7.1",
@ -79,7 +81,7 @@
},
"apps/marketing": {
"name": "@documenso/marketing",
"version": "1.9.0-rc.2",
"version": "1.9.0-rc.5",
"license": "AGPL-3.0",
"dependencies": {
"@documenso/assets": "*",
@ -115,7 +117,7 @@
"recharts": "^2.7.2",
"sharp": "0.32.6",
"typescript": "5.2.2",
"zod": "^3.23.8"
"zod": "3.24.1"
},
"devDependencies": {
"@lingui/loader": "^4.11.3",
@ -492,7 +494,7 @@
},
"apps/web": {
"name": "@documenso/web",
"version": "1.9.0-rc.2",
"version": "1.9.0-rc.5",
"license": "AGPL-3.0",
"dependencies": {
"@documenso/api": "*",
@ -536,10 +538,11 @@
"recharts": "^2.7.2",
"remeda": "^2.17.3",
"sharp": "0.32.6",
"trpc-openapi": "^1.2.0",
"ts-pattern": "^5.0.5",
"ua-parser-js": "^1.0.37",
"uqr": "^0.1.2",
"zod": "^3.23.8"
"zod": "3.24.1"
},
"devDependencies": {
"@documenso/tailwind-config": "*",
@ -3494,6 +3497,11 @@
"node": ">=6"
}
},
"node_modules/@hapi/bourne": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz",
"integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w=="
},
"node_modules/@hapi/hoek": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
@ -10717,15 +10725,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@trigger.dev/cli/node_modules/zod": {
"version": "3.22.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
"integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/@trigger.dev/core": {
"version": "2.3.18",
"resolved": "https://registry.npmjs.org/@trigger.dev/core/-/core-2.3.18.tgz",
@ -10747,14 +10746,6 @@
"node": ">=18.0.0"
}
},
"node_modules/@trigger.dev/core/node_modules/zod": {
"version": "3.22.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
"integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/@trigger.dev/nextjs": {
"version": "2.3.18",
"resolved": "https://registry.npmjs.org/@trigger.dev/nextjs/-/nextjs-2.3.18.tgz",
@ -10818,14 +10809,6 @@
"uuid": "dist/bin/uuid"
}
},
"node_modules/@trigger.dev/sdk/node_modules/zod": {
"version": "3.22.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
"integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/@trigger.dev/yalt": {
"version": "2.3.18",
"resolved": "https://registry.npmjs.org/@trigger.dev/yalt/-/yalt-2.3.18.tgz",
@ -10842,15 +10825,6 @@
"node": ">=18.0.0"
}
},
"node_modules/@trigger.dev/yalt/node_modules/zod": {
"version": "3.22.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
"integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/@trivago/prettier-plugin-sort-imports": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.3.0.tgz",
@ -12007,6 +11981,18 @@
"node": ">=6.5"
}
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
@ -14311,6 +14297,21 @@
}
}
},
"node_modules/co-body": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/co-body/-/co-body-6.2.0.tgz",
"integrity": "sha512-Kbpv2Yd1NdL1V/V4cwLVxraHDV6K8ayohr2rmH0J87Er8+zJjcTa6dAn9QMPC9CRgU8+aNajKbSf1TzDB1yKPA==",
"dependencies": {
"@hapi/bourne": "^3.0.0",
"inflation": "^2.0.0",
"qs": "^6.5.2",
"raw-body": "^2.3.3",
"type-is": "^1.6.16"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/code-block-writer": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz",
@ -14612,6 +14613,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/consola": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz",
"integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==",
"engines": {
"node": "^14.18.0 || >=16.10.0"
}
},
"node_modules/console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
@ -14627,6 +14636,17 @@
"simple-wcswidth": "^1.0.1"
}
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"dependencies": {
"safe-buffer": "5.2.1"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
@ -14712,9 +14732,9 @@
}
},
"node_modules/cookie-es": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.0.0.tgz",
"integrity": "sha512-mWYvfOLrfEc996hlKcdABeIiPHUPC6DM2QYZdGGOvhOTbA3tjm2eBwqlJpoFdjC89NI4Qt6h0Pu06Mp+1Pj5OQ=="
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz",
"integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg=="
},
"node_modules/copy-anything": {
"version": "3.0.5",
@ -14880,6 +14900,14 @@
"node": ">= 8"
}
},
"node_modules/crossws": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.1.tgz",
"integrity": "sha512-HsZgeVYaG+b5zA+9PbIPGq4+J/CJynJuearykPsXx4V/eMhyQ5EDVg3Ak2FBZtVXCiOLu/U7IiwDHTr9MA+IKw==",
"dependencies": {
"uncrypto": "^0.1.3"
}
},
"node_modules/crypto-js": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
@ -15587,6 +15615,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/defu": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="
},
"node_modules/degenerator": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
@ -15693,6 +15726,11 @@
"node": ">=6"
}
},
"node_modules/destr": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz",
"integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ=="
},
"node_modules/detect-indent": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
@ -18123,6 +18161,14 @@
}
}
},
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/from": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
@ -18793,6 +18839,23 @@
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/h3": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/h3/-/h3-1.13.0.tgz",
"integrity": "sha512-vFEAu/yf8UMUcB4s43OaDaigcqpQd14yanmOsn+NcRX3/guSKncyE2rOYhq8RIchgJrPSs/QiIddnTTR1ddiAg==",
"dependencies": {
"cookie-es": "^1.2.2",
"crossws": ">=0.2.0 <0.4.0",
"defu": "^6.1.4",
"destr": "^2.0.3",
"iron-webcrypto": "^1.2.1",
"ohash": "^1.1.4",
"radix3": "^1.1.2",
"ufo": "^1.5.4",
"uncrypto": "^0.1.3",
"unenv": "^1.10.0"
}
},
"node_modules/hard-rejection": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
@ -19844,6 +19907,14 @@
"integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==",
"dev": true
},
"node_modules/inflation": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/inflation/-/inflation-2.1.0.tgz",
"integrity": "sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==",
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/inflection": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/inflection/-/inflection-2.0.1.tgz",
@ -20000,14 +20071,6 @@
"node": ">=6"
}
},
"node_modules/inngest/node_modules/zod": {
"version": "3.22.5",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.5.tgz",
"integrity": "sha512-HqnGsCdVZ2xc0qWPLdO25WnseXThh0kEYKIdV5F/hTHO75hNZFp8thxSeHhiPrHZKrFTo1SOgkAj9po5bexZlw==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/input-otp": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.2.4.tgz",
@ -20219,6 +20282,14 @@
"node": ">= 10"
}
},
"node_modules/iron-webcrypto": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz",
"integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==",
"funding": {
"url": "https://github.com/sponsors/brc-dd"
}
},
"node_modules/is-alphabetical": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
@ -22035,8 +22106,7 @@
"node_modules/lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
"dev": true
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
@ -22863,6 +22933,14 @@
"esbuild": "0.*"
}
},
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/memfs": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz",
@ -22917,6 +22995,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/merge-descriptors": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/merge-refs": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/merge-refs/-/merge-refs-1.2.2.tgz",
@ -22990,6 +23076,14 @@
"uuid": "dist/bin/uuid"
}
},
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/micro": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/micro/-/micro-10.0.1.tgz",
@ -23733,6 +23827,17 @@
"node": ">=8.6"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@ -24139,6 +24244,12 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/mupdf": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/mupdf/-/mupdf-1.0.0.tgz",
"integrity": "sha512-AWT27abYSX5gQmWs7+jDEtmGJpWyZrqdxROpYf5BDAJBA+iYqlNztk2EMlKvuRLBzajj00kf4PtFiergDSKDTg==",
"license": "AGPL-3.0-or-later"
},
"node_modules/mute-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz",
@ -24196,7 +24307,6 @@
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"dev": true,
"engines": {
"node": ">= 0.6"
}
@ -24665,6 +24775,11 @@
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/node-fetch-native": {
"version": "1.6.4",
"resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.4.tgz",
"integrity": "sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ=="
},
"node_modules/node-gyp": {
"version": "9.4.1",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz",
@ -24993,6 +25108,38 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
}
},
"node_modules/node-mocks-http": {
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/node-mocks-http/-/node-mocks-http-1.16.2.tgz",
"integrity": "sha512-2Sh6YItRp1oqewZNlck3LaFp5vbyW2u51HX2p1VLxQ9U/bG90XV8JY9O7Nk+HDd6OOn/oV3nA5Tx5k4Rki0qlg==",
"dependencies": {
"accepts": "^1.3.7",
"content-disposition": "^0.5.3",
"depd": "^1.1.0",
"fresh": "^0.5.2",
"merge-descriptors": "^1.0.1",
"methods": "^1.1.2",
"mime": "^1.3.4",
"parseurl": "^1.3.3",
"range-parser": "^1.2.0",
"type-is": "^1.6.18"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@types/express": "^4.17.21 || ^5.0.0",
"@types/node": "*"
},
"peerDependenciesMeta": {
"@types/express": {
"optional": true
},
"@types/node": {
"optional": true
}
}
},
"node_modules/node-releases": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
@ -25580,6 +25727,11 @@
"integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==",
"dev": true
},
"node_modules/ohash": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.4.tgz",
"integrity": "sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g=="
},
"node_modules/oidc-token-hash": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz",
@ -25681,6 +25833,11 @@
}
}
},
"node_modules/openapi-types": {
"version": "12.1.3",
"resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz",
"integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="
},
"node_modules/openapi3-ts": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-2.0.2.tgz",
@ -26440,6 +26597,14 @@
"url": "https://ko-fi.com/killymxi"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/partysocket": {
"version": "0.0.17",
"resolved": "https://registry.npmjs.org/partysocket/-/partysocket-0.0.17.tgz",
@ -26595,8 +26760,7 @@
"node_modules/pathe": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
"dev": true
"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="
},
"node_modules/pathval": {
"version": "1.1.1",
@ -27908,6 +28072,11 @@
"node": ">=8"
}
},
"node_modules/radix3": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz",
"integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="
},
"node_modules/raf-schd": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
@ -27946,6 +28115,14 @@
"safe-buffer": "^5.1.0"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz",
@ -32782,6 +32959,32 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/trpc-openapi": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/trpc-openapi/-/trpc-openapi-1.2.0.tgz",
"integrity": "sha512-pfYoCd/3KYXWXvUPZBKJw455OOwngKN/6SIcj7Yit19OMLJ+8yVZkEvGEeg5wUSwfsiTdRsKuvqkRPXVSwV7ew==",
"workspaces": [
".",
"examples/with-nextjs",
"examples/with-express",
"examples/with-interop",
"examples/with-serverless",
"examples/with-fastify",
"examples/with-nuxtjs"
],
"dependencies": {
"co-body": "^6.1.0",
"h3": "^1.6.4",
"lodash.clonedeep": "^4.5.0",
"node-mocks-http": "^1.12.2",
"openapi-types": "^12.1.1",
"zod-to-json-schema": "^3.21.1"
},
"peerDependencies": {
"@trpc/server": "^10.0.0",
"zod": "^3.14.4"
}
},
"node_modules/ts-api-utils": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz",
@ -33547,6 +33750,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"dependencies": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/typed-array-buffer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz",
@ -33665,10 +33880,9 @@
}
},
"node_modules/ufo": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.4.0.tgz",
"integrity": "sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==",
"dev": true
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz",
"integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ=="
},
"node_modules/ulid": {
"version": "2.3.0",
@ -33703,6 +33917,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/uncrypto": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz",
"integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="
},
"node_modules/undici": {
"version": "5.28.2",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.2.tgz",
@ -33720,6 +33939,29 @@
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
},
"node_modules/unenv": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/unenv/-/unenv-1.10.0.tgz",
"integrity": "sha512-wY5bskBQFL9n3Eca5XnhH6KbUo/tfvkwm9OpcdCvLaeA7piBNbavbOKJySEwQ1V0RH6HvNlSAFRTpvTqgKRQXQ==",
"dependencies": {
"consola": "^3.2.3",
"defu": "^6.1.4",
"mime": "^3.0.0",
"node-fetch-native": "^1.6.4",
"pathe": "^1.1.2"
}
},
"node_modules/unenv/node_modules/mime": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/unified": {
"version": "10.1.2",
"resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz",
@ -35409,9 +35651,9 @@
}
},
"node_modules/zod": {
"version": "3.23.8",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
"version": "3.24.1",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
@ -35454,6 +35696,14 @@
"@prisma/debug": "5.22.0"
}
},
"node_modules/zod-to-json-schema": {
"version": "3.24.1",
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.1.tgz",
"integrity": "sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==",
"peerDependencies": {
"zod": "^3.24.1"
}
},
"node_modules/zwitch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
@ -35478,7 +35728,7 @@
"superjson": "^1.13.1",
"swagger-ui-react": "^5.11.0",
"ts-pattern": "^5.0.5",
"zod": "^3.23.8"
"zod": "3.24.1"
}
},
"packages/api/node_modules/@ts-rest/next": {
@ -35543,7 +35793,7 @@
"next-auth": "4.24.5",
"react": "^18",
"ts-pattern": "^5.0.5",
"zod": "^3.23.8"
"zod": "3.24.1"
}
},
"packages/email": {
@ -36752,7 +37002,7 @@
"sharp": "0.32.6",
"stripe": "^12.7.0",
"ts-pattern": "^5.0.5",
"zod": "^3.23.8"
"zod": "3.24.1"
},
"devDependencies": {
"@playwright/browser-chromium": "1.43.0",
@ -36890,7 +37140,7 @@
"luxon": "^3.4.0",
"superjson": "^1.13.1",
"ts-pattern": "^5.0.5",
"zod": "^3.23.8"
"zod": "3.24.1"
}
},
"packages/trpc/node_modules/@ts-rest/next": {
@ -36970,7 +37220,7 @@
"tailwind-merge": "^1.12.0",
"tailwindcss-animate": "^1.0.5",
"ts-pattern": "^5.0.5",
"zod": "^3.23.8"
"zod": "3.24.1"
},
"devDependencies": {
"@documenso/tailwind-config": "*",

View File

@ -1,6 +1,6 @@
{
"private": true,
"version": "1.9.0-rc.2",
"version": "1.9.0-rc.5",
"scripts": {
"build": "turbo run build",
"build:web": "turbo run build --filter=@documenso/web",
@ -68,11 +68,14 @@
"@lingui/core": "^4.11.3",
"inngest-cli": "^0.29.1",
"luxon": "^3.5.0",
"mupdf": "^1.0.0",
"next-runtime-env": "^3.2.0",
"react": "^18"
"react": "^18",
"zod": "3.24.1"
},
"overrides": {
"next": "14.2.6"
"next": "14.2.6",
"zod": "3.24.1"
},
"trigger.dev": {
"endpointId": "documenso-app"

View File

@ -25,6 +25,6 @@
"superjson": "^1.13.1",
"swagger-ui-react": "^5.11.0",
"ts-pattern": "^5.0.5",
"zod": "^3.23.8"
"zod": "3.24.1"
}
}
}

View File

@ -23,13 +23,13 @@ import { getFieldsForDocument } from '@documenso/lib/server-only/field/get-field
import { updateField } from '@documenso/lib/server-only/field/update-field';
import { insertFormValuesInPdf } from '@documenso/lib/server-only/pdf/insert-form-values-in-pdf';
import { deleteRecipient } from '@documenso/lib/server-only/recipient/delete-recipient';
import { getRecipientById } from '@documenso/lib/server-only/recipient/get-recipient-by-id';
import { getRecipientByIdV1Api } from '@documenso/lib/server-only/recipient/get-recipient-by-id-v1-api';
import { getRecipientsForDocument } from '@documenso/lib/server-only/recipient/get-recipients-for-document';
import { setRecipientsForDocument } from '@documenso/lib/server-only/recipient/set-recipients-for-document';
import { updateRecipient } from '@documenso/lib/server-only/recipient/update-recipient';
import { createTeamMemberInvites } from '@documenso/lib/server-only/team/create-team-member-invites';
import { deleteTeamMembers } from '@documenso/lib/server-only/team/delete-team-members';
import type { CreateDocumentFromTemplateResponse } from '@documenso/lib/server-only/template/create-document-from-template';
import type { TCreateDocumentFromTemplateResponse } from '@documenso/lib/server-only/template/create-document-from-template';
import { createDocumentFromTemplate } from '@documenso/lib/server-only/template/create-document-from-template';
import { createDocumentFromTemplateLegacy } from '@documenso/lib/server-only/template/create-document-from-template-legacy';
import { deleteTemplate } from '@documenso/lib/server-only/template/delete-template';
@ -345,7 +345,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
});
}
const recipients = await setRecipientsForDocument({
const { recipients } = await setRecipientsForDocument({
userId: user.id,
teamId: team?.id,
documentId: document.id,
@ -560,7 +560,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
const templateId = Number(params.templateId);
let document: CreateDocumentFromTemplateResponse | null = null;
let document: TCreateDocumentFromTemplateResponse | null = null;
try {
document = await createDocumentFromTemplate({
@ -630,7 +630,6 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
token: recipient.token,
role: recipient.role,
signingOrder: recipient.signingOrder,
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
})),
},
@ -786,7 +785,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
}
try {
const newRecipients = await setRecipientsForDocument({
const { recipients: newRecipients } = await setRecipientsForDocument({
documentId: Number(documentId),
userId: user.id,
teamId: team?.id,
@ -1000,7 +999,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
throw new Error('Invalid page number');
}
const recipient = await getRecipientById({
const recipient = await getRecipientByIdV1Api({
id: Number(recipientId),
documentId: Number(documentId),
}).catch(() => null);
@ -1145,7 +1144,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
};
}
const recipient = await getRecipientById({
const recipient = await getRecipientByIdV1Api({
id: Number(recipientId),
documentId: Number(documentId),
}).catch(() => null);
@ -1249,7 +1248,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
};
}
const recipient = await getRecipientById({
const recipient = await getRecipientByIdV1Api({
id: Number(field.recipientId),
documentId: Number(documentId),
}).catch(() => null);

View File

@ -1,5 +1,6 @@
import type { NextApiRequest } from 'next';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { getApiTokenByToken } from '@documenso/lib/server-only/public-api/get-api-token-by-token';
import type { Team, User } from '@documenso/prisma/client';
@ -22,18 +23,33 @@ export const authenticatedMiddleware = <
const [token] = (authorization || '').split('Bearer ').filter((s) => s.length > 0);
if (!token) {
throw new Error('Token was not provided for authenticated middleware');
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'API token was not provided',
});
}
const apiToken = await getApiTokenByToken({ token });
if (apiToken.user.disabled) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'User is disabled',
});
}
return await handler(args, apiToken.user, apiToken.team);
} catch (_err) {
console.log({ _err });
} catch (err) {
console.log({ err: err });
let message = 'Unauthorized';
if (err instanceof AppError) {
message = err.message;
}
return {
status: 401,
body: {
message: 'Unauthorized',
message,
},
} as const;
}

View File

@ -1,6 +1,7 @@
import { expect, test } from '@playwright/test';
import { DocumentStatus, TeamMemberRole } from '@documenso/prisma/client';
import { DocumentStatus, DocumentVisibility, TeamMemberRole } from '@documenso/prisma/client';
import { seedBlankDocument } from '@documenso/prisma/seed/documents';
import { seedDocuments, seedTeamDocuments } from '@documenso/prisma/seed/documents';
import { seedTeam, seedTeamEmail, seedTeamMember } from '@documenso/prisma/seed/teams';
import { seedUser } from '@documenso/prisma/seed/users';
@ -538,7 +539,7 @@ test('[TEAMS]: ensure recipient can see document regardless of visibility', asyn
await apiSignout({ page });
});
test('[TEAMS]: check that members cannot see ADMIN-only documents', async ({ page }) => {
test('[TEAMS]: check that MEMBER role cannot see ADMIN-only documents', async ({ page }) => {
const team = await seedTeam();
// Seed a member user
@ -575,7 +576,46 @@ test('[TEAMS]: check that members cannot see ADMIN-only documents', async ({ pag
await apiSignout({ page });
});
test('[TEAMS]: check that managers cannot see ADMIN-only documents', async ({ page }) => {
test('[TEAMS]: check that MEMBER role cannot see MANAGER_AND_ABOVE-only documents', async ({
page,
}) => {
const team = await seedTeam();
// Seed a member user
const memberUser = await seedTeamMember({
teamId: team.id,
role: TeamMemberRole.MEMBER,
});
// Seed an ADMIN-only document
await seedDocuments([
{
sender: team.owner,
recipients: [],
type: DocumentStatus.COMPLETED,
documentOptions: {
teamId: team.id,
visibility: 'MANAGER_AND_ABOVE',
title: 'Manager and Above Only Document',
},
},
]);
await apiSignin({
page,
email: memberUser.email,
redirectPath: `/t/${team.url}/documents?status=COMPLETED`,
});
// Check that the member user cannot see the ADMIN-only document
await expect(
page.getByRole('link', { name: 'Admin Only Document', exact: true }),
).not.toBeVisible();
await apiSignout({ page });
});
test('[TEAMS]: check that MANAGER role cannot see ADMIN-only documents', async ({ page }) => {
const team = await seedTeam();
// Seed a manager user
@ -612,7 +652,7 @@ test('[TEAMS]: check that managers cannot see ADMIN-only documents', async ({ pa
await apiSignout({ page });
});
test('[TEAMS]: check that admin can see MANAGER_AND_ABOVE documents', async ({ page }) => {
test('[TEAMS]: check that ADMIN role can see MANAGER_AND_ABOVE documents', async ({ page }) => {
const team = await seedTeam();
// Seed an admin user
@ -649,6 +689,187 @@ test('[TEAMS]: check that admin can see MANAGER_AND_ABOVE documents', async ({ p
await apiSignout({ page });
});
test('[TEAMS]: check that ADMIN role can change document visibility', async ({ page }) => {
const team = await seedTeam({
createTeamOptions: {
teamGlobalSettings: {
create: {
documentVisibility: DocumentVisibility.MANAGER_AND_ABOVE,
},
},
},
});
const adminUser = await seedTeamMember({
teamId: team.id,
role: TeamMemberRole.ADMIN,
});
const document = await seedBlankDocument(adminUser, {
createDocumentOptions: {
teamId: team.id,
visibility: team.teamGlobalSettings?.documentVisibility,
},
});
await apiSignin({
page,
email: adminUser.email,
redirectPath: `/t/${team.url}/documents/${document.id}/edit`,
});
await page.getByTestId('documentVisibilitySelectValue').click();
await page.getByLabel('Admins only').click();
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible();
await page.getByRole('button', { name: 'Go Back' }).click();
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
await expect(page.getByTestId('documentVisibilitySelectValue')).toContainText('Admins only');
});
test('[TEAMS]: check that MEMBER role cannot change visibility of EVERYONE documents', async ({
page,
}) => {
const team = await seedTeam({
createTeamOptions: {
teamGlobalSettings: {
create: {
documentVisibility: DocumentVisibility.EVERYONE,
},
},
},
});
const teamMember = await seedTeamMember({
teamId: team.id,
role: TeamMemberRole.MEMBER,
});
const document = await seedBlankDocument(teamMember, {
createDocumentOptions: {
teamId: team.id,
visibility: team.teamGlobalSettings?.documentVisibility,
},
});
await apiSignin({
page,
email: teamMember.email,
redirectPath: `/t/${team.url}/documents/${document.id}/edit`,
});
await expect(page.getByTestId('documentVisibilitySelectValue')).toHaveText('Everyone');
await expect(page.getByTestId('documentVisibilitySelectValue')).toBeDisabled();
});
test('[TEAMS]: check that MEMBER role cannot change visibility of MANAGER_AND_ABOVE documents', async ({
page,
}) => {
const team = await seedTeam({
createTeamOptions: {
teamGlobalSettings: {
create: {
documentVisibility: DocumentVisibility.MANAGER_AND_ABOVE,
},
},
},
});
const teamMember = await seedTeamMember({
teamId: team.id,
role: TeamMemberRole.MEMBER,
});
const document = await seedBlankDocument(teamMember, {
createDocumentOptions: {
teamId: team.id,
visibility: team.teamGlobalSettings?.documentVisibility,
},
});
await apiSignin({
page,
email: teamMember.email,
redirectPath: `/t/${team.url}/documents/${document.id}/edit`,
});
await expect(page.getByTestId('documentVisibilitySelectValue')).toHaveText('Managers and above');
await expect(page.getByTestId('documentVisibilitySelectValue')).toBeDisabled();
});
test('[TEAMS]: check that MEMBER role cannot change visibility of ADMIN documents', async ({
page,
}) => {
const team = await seedTeam({
createTeamOptions: {
teamGlobalSettings: {
create: {
documentVisibility: DocumentVisibility.ADMIN,
},
},
},
});
const teamMember = await seedTeamMember({
teamId: team.id,
role: TeamMemberRole.MEMBER,
});
const document = await seedBlankDocument(teamMember, {
createDocumentOptions: {
teamId: team.id,
visibility: team.teamGlobalSettings?.documentVisibility,
},
});
await apiSignin({
page,
email: teamMember.email,
redirectPath: `/t/${team.url}/documents/${document.id}/edit`,
});
await expect(page.getByTestId('documentVisibilitySelectValue')).toHaveText('Admins only');
await expect(page.getByTestId('documentVisibilitySelectValue')).toBeDisabled();
});
test('[TEAMS]: check that MANAGER role cannot change visibility of ADMIN documents', async ({
page,
}) => {
const team = await seedTeam({
createTeamOptions: {
teamGlobalSettings: {
create: {
documentVisibility: DocumentVisibility.ADMIN,
},
},
},
});
const teamManager = await seedTeamMember({
teamId: team.id,
role: TeamMemberRole.MANAGER,
});
const document = await seedBlankDocument(teamManager, {
createDocumentOptions: {
teamId: team.id,
visibility: team.teamGlobalSettings?.documentVisibility,
},
});
await apiSignin({
page,
email: teamManager.email,
redirectPath: `/t/${team.url}/documents/${document.id}/edit`,
});
await expect(page.getByTestId('documentVisibilitySelectValue')).toHaveText('Admins only');
await expect(page.getByTestId('documentVisibilitySelectValue')).toBeDisabled();
});
test('[TEAMS]: users cannot see documents from other teams', async ({ page }) => {
// Seed two teams with documents
const { team: teamA, teamMember2: teamAMember } = await seedTeamDocuments();

View File

@ -21,6 +21,6 @@
"next-auth": "4.24.5",
"react": "^18",
"ts-pattern": "^5.0.5",
"zod": "^3.23.8"
"zod": "3.24.1"
}
}
}

View File

@ -1,8 +1,10 @@
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { subscriptionsContainActiveEnterprisePlan } from '@documenso/lib/utils/billing';
import { subscriptionsContainsActivePlan } from '@documenso/lib/utils/billing';
import { prisma } from '@documenso/prisma';
import type { Subscription } from '@documenso/prisma/client';
import { getEnterprisePlanPriceIds } from '../stripe/get-enterprise-plan-prices';
export type IsUserEnterpriseOptions = {
userId: number;
teamId?: number;
@ -52,5 +54,11 @@ export const isUserEnterprise = async ({
.then((user) => user.Subscription);
}
return subscriptionsContainActiveEnterprisePlan(subscriptions);
if (subscriptions.length === 0) {
return false;
}
const enterprisePlanPriceIds = await getEnterprisePlanPriceIds();
return subscriptionsContainsActivePlan(subscriptions, enterprisePlanPriceIds, true);
};

View File

@ -0,0 +1,56 @@
import { Trans } from '@lingui/macro';
import { Column, Img, Section, Text } from '../components';
import { TemplateDocumentImage } from './template-document-image';
export interface TemplateDocumentRecipientSignedProps {
documentName: string;
recipientName: string;
recipientEmail: string;
assetBaseUrl: string;
}
export const TemplateDocumentRecipientSigned = ({
documentName,
recipientName,
recipientEmail,
assetBaseUrl,
}: TemplateDocumentRecipientSignedProps) => {
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
const recipientReference = recipientName || recipientEmail;
return (
<>
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
<Section>
<Section className="mb-4">
<Column align="center">
<Text className="text-base font-semibold text-[#7AC455]">
<Img
src={getAssetUrl('/static/completed.png')}
className="-mt-0.5 mr-2 inline h-7 w-7 align-middle"
/>
<Trans>Completed</Trans>
</Text>
</Column>
</Section>
<Text className="text-primary mb-0 text-center text-lg font-semibold">
<Trans>
{recipientReference} has signed "{documentName}"
</Trans>
</Text>
<Text className="mx-auto mb-6 mt-1 max-w-[80%] text-center text-base text-slate-400">
<Trans>{recipientReference} has completed signing the document.</Trans>
</Text>
</Section>
</>
);
};
export default TemplateDocumentRecipientSigned;

View File

@ -0,0 +1,70 @@
import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { Body, Container, Head, Html, Img, Preview, Section } from '../components';
import { useBranding } from '../providers/branding';
import { TemplateDocumentRecipientSigned } from '../template-components/template-document-recipient-signed';
import { TemplateFooter } from '../template-components/template-footer';
export interface DocumentRecipientSignedEmailTemplateProps {
documentName?: string;
recipientName?: string;
recipientEmail?: string;
assetBaseUrl?: string;
}
export const DocumentRecipientSignedEmailTemplate = ({
documentName = 'Open Source Pledge.pdf',
recipientName = 'John Doe',
recipientEmail = 'lucas@documenso.com',
assetBaseUrl = 'http://localhost:3002',
}: DocumentRecipientSignedEmailTemplateProps) => {
const { _ } = useLingui();
const branding = useBranding();
const recipientReference = recipientName || recipientEmail;
const previewText = msg`${recipientReference} has signed ${documentName}`;
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
<Preview>{_(previewText)}</Preview>
<Body className="mx-auto my-auto font-sans">
<Section className="bg-white">
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-2 backdrop-blur-sm">
<Section className="p-2">
{branding.brandingEnabled && branding.brandingLogo ? (
<Img src={branding.brandingLogo} alt="Branding Logo" className="mb-4 h-6" />
) : (
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
)}
<TemplateDocumentRecipientSigned
documentName={documentName}
recipientName={recipientName}
recipientEmail={recipientEmail}
assetBaseUrl={assetBaseUrl}
/>
</Section>
</Container>
<Container className="mx-auto max-w-xl">
<TemplateFooter />
</Container>
</Section>
</Body>
</Html>
);
};
export default DocumentRecipientSignedEmailTemplate;

View File

@ -12,7 +12,7 @@ export function useCopyShareLink({ onSuccess, onError }: UseCopyShareLinkOptions
const [, copyToClipboard] = useCopyToClipboard();
const { mutateAsync: createOrGetShareLink, isLoading: isCreatingShareLink } =
trpc.shareLink.createOrGetShareLink.useMutation();
trpc.document.createOrGetShareLink.useMutation();
/**
* Copy a newly created, or pre-existing share link to the user's clipboard.

View File

@ -1,5 +1,6 @@
import { JobClient } from './client/client';
import { SEND_CONFIRMATION_EMAIL_JOB_DEFINITION } from './definitions/emails/send-confirmation-email';
import { SEND_RECIPIENT_SIGNED_EMAIL_JOB_DEFINITION } from './definitions/emails/send-recipient-signed-email';
import { SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION } from './definitions/emails/send-rejection-emails';
import { SEND_SIGNING_EMAIL_JOB_DEFINITION } from './definitions/emails/send-signing-email';
import { SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION } from './definitions/emails/send-team-deleted-email';
@ -19,6 +20,7 @@ export const jobsClient = new JobClient([
SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION,
SEAL_DOCUMENT_JOB_DEFINITION,
SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION,
SEND_RECIPIENT_SIGNED_EMAIL_JOB_DEFINITION,
] as const);
export const jobs = jobsClient;

View File

@ -0,0 +1,9 @@
import { sendConfirmationToken } from '../../../server-only/user/send-confirmation-token';
import type { TSendConfirmationEmailJobDefinition } from './send-confirmation-email';
export const run = async ({ payload }: { payload: TSendConfirmationEmailJobDefinition }) => {
await sendConfirmationToken({
email: payload.email,
force: payload.force,
});
};

View File

@ -1,6 +1,5 @@
import { z } from 'zod';
import { sendConfirmationToken } from '../../../server-only/user/send-confirmation-token';
import type { JobDefinition } from '../../client/_internal/job';
const SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_ID = 'send.signup.confirmation.email';
@ -10,6 +9,10 @@ const SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
force: z.boolean().optional(),
});
export type TSendConfirmationEmailJobDefinition = z.infer<
typeof SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_SCHEMA
>;
export const SEND_CONFIRMATION_EMAIL_JOB_DEFINITION = {
id: SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_ID,
name: 'Send Confirmation Email',
@ -19,12 +22,11 @@ export const SEND_CONFIRMATION_EMAIL_JOB_DEFINITION = {
schema: SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_SCHEMA,
},
handler: async ({ payload }) => {
await sendConfirmationToken({
email: payload.email,
force: payload.force,
});
const handler = await import('./send-confirmation-email.handler');
await handler.run({ payload });
},
} as const satisfies JobDefinition<
typeof SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_ID,
z.infer<typeof SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_SCHEMA>
TSendConfirmationEmailJobDefinition
>;

View File

@ -0,0 +1,130 @@
import { createElement } from 'react';
import { msg } from '@lingui/macro';
import { z } from 'zod';
import { mailer } from '@documenso/email/mailer';
import { DocumentRecipientSignedEmailTemplate } from '@documenso/email/templates/document-recipient-signed';
import { prisma } from '@documenso/prisma';
import { getI18nInstance } from '../../../client-only/providers/i18n.server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
import { extractDerivedDocumentEmailSettings } from '../../../types/document-email';
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
import { type JobDefinition } from '../../client/_internal/job';
const SEND_RECIPIENT_SIGNED_EMAIL_JOB_DEFINITION_ID = 'send.recipient.signed.email';
const SEND_RECIPIENT_SIGNED_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
documentId: z.number(),
recipientId: z.number(),
});
export const SEND_RECIPIENT_SIGNED_EMAIL_JOB_DEFINITION = {
id: SEND_RECIPIENT_SIGNED_EMAIL_JOB_DEFINITION_ID,
name: 'Send Recipient Signed Email',
version: '1.0.0',
trigger: {
name: SEND_RECIPIENT_SIGNED_EMAIL_JOB_DEFINITION_ID,
schema: SEND_RECIPIENT_SIGNED_EMAIL_JOB_DEFINITION_SCHEMA,
},
handler: async ({ payload, io }) => {
const { documentId, recipientId } = payload;
const document = await prisma.document.findFirst({
where: {
id: documentId,
Recipient: {
some: {
id: recipientId,
},
},
},
include: {
Recipient: {
where: {
id: recipientId,
},
},
User: true,
documentMeta: true,
team: {
include: {
teamGlobalSettings: true,
},
},
},
});
if (!document) {
throw new Error('Document not found');
}
if (document.Recipient.length === 0) {
throw new Error('Document has no recipients');
}
const isRecipientSignedEmailEnabled = extractDerivedDocumentEmailSettings(
document.documentMeta,
).recipientSigned;
if (!isRecipientSignedEmailEnabled) {
return;
}
const [recipient] = document.Recipient;
const { email: recipientEmail, name: recipientName } = recipient;
const { User: owner } = document;
const recipientReference = recipientName || recipientEmail;
// Don't send notification if the owner is the one who signed
if (owner.email === recipientEmail) {
return;
}
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000';
const i18n = await getI18nInstance(document.documentMeta?.language);
const template = createElement(DocumentRecipientSignedEmailTemplate, {
documentName: document.title,
recipientName,
recipientEmail,
assetBaseUrl,
});
await io.runTask('send-recipient-signed-email', async () => {
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(template, { lang: document.documentMeta?.language, branding }),
renderEmailWithI18N(template, {
lang: document.documentMeta?.language,
branding,
plainText: true,
}),
]);
await mailer.sendMail({
to: {
name: owner.name ?? '',
address: owner.email,
},
from: {
name: FROM_NAME,
address: FROM_ADDRESS,
},
subject: i18n._(msg`${recipientReference} has signed "${document.title}"`),
html,
text,
});
});
},
} as const satisfies JobDefinition<
typeof SEND_RECIPIENT_SIGNED_EMAIL_JOB_DEFINITION_ID,
z.infer<typeof SEND_RECIPIENT_SIGNED_EMAIL_JOB_DEFINITION_SCHEMA>
>;

View File

@ -0,0 +1,156 @@
import { createElement } from 'react';
import { msg } from '@lingui/macro';
import { mailer } from '@documenso/email/mailer';
import DocumentRejectedEmail from '@documenso/email/templates/document-rejected';
import DocumentRejectionConfirmedEmail from '@documenso/email/templates/document-rejection-confirmed';
import { prisma } from '@documenso/prisma';
import { SendStatus, SigningStatus } from '@documenso/prisma/client';
import { getI18nInstance } from '../../../client-only/providers/i18n.server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
import { extractDerivedDocumentEmailSettings } from '../../../types/document-email';
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
import { formatDocumentsPath } from '../../../utils/teams';
import type { JobRunIO } from '../../client/_internal/job';
import type { TSendSigningRejectionEmailsJobDefinition } from './send-rejection-emails';
export const run = async ({
payload,
io,
}: {
payload: TSendSigningRejectionEmailsJobDefinition;
io: JobRunIO;
}) => {
const { documentId, recipientId } = payload;
const [document, recipient] = await Promise.all([
prisma.document.findFirstOrThrow({
where: {
id: documentId,
},
include: {
User: true,
documentMeta: true,
team: {
select: {
teamEmail: true,
name: true,
url: true,
teamGlobalSettings: true,
},
},
},
}),
prisma.recipient.findFirstOrThrow({
where: {
id: recipientId,
signingStatus: SigningStatus.REJECTED,
},
}),
]);
const { documentMeta, team, User: documentOwner } = document;
const isEmailEnabled = extractDerivedDocumentEmailSettings(
document.documentMeta,
).recipientSigningRequest;
if (!isEmailEnabled) {
return;
}
const i18n = await getI18nInstance(documentMeta?.language);
// Send confirmation email to the recipient who rejected
await io.runTask('send-rejection-confirmation-email', async () => {
const recipientTemplate = createElement(DocumentRejectionConfirmedEmail, {
recipientName: recipient.name,
documentName: document.title,
documentOwnerName: document.User.name || document.User.email,
reason: recipient.rejectionReason || '',
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(),
});
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(recipientTemplate, { lang: documentMeta?.language, branding }),
renderEmailWithI18N(recipientTemplate, {
lang: documentMeta?.language,
branding,
plainText: true,
}),
]);
await mailer.sendMail({
to: {
name: recipient.name,
address: recipient.email,
},
from: {
name: FROM_NAME,
address: FROM_ADDRESS,
},
subject: i18n._(msg`Document "${document.title}" - Rejection Confirmed`),
html,
text,
});
});
// Send notification email to document owner
await io.runTask('send-owner-notification-email', async () => {
const ownerTemplate = createElement(DocumentRejectedEmail, {
recipientName: recipient.name,
documentName: document.title,
documentUrl: `${NEXT_PUBLIC_WEBAPP_URL()}${formatDocumentsPath(document.team?.url)}/${
document.id
}`,
rejectionReason: recipient.rejectionReason || '',
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(),
});
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(ownerTemplate, { lang: documentMeta?.language, branding }),
renderEmailWithI18N(ownerTemplate, {
lang: documentMeta?.language,
branding,
plainText: true,
}),
]);
await mailer.sendMail({
to: {
name: documentOwner.name || '',
address: documentOwner.email,
},
from: {
name: FROM_NAME,
address: FROM_ADDRESS,
},
subject: i18n._(msg`Document "${document.title}" - Rejected by ${recipient.name}`),
html,
text,
});
});
await io.runTask('update-recipient', async () => {
await prisma.recipient.update({
where: {
id: recipient.id,
},
data: {
sendStatus: SendStatus.SENT,
},
});
});
};

View File

@ -1,21 +1,5 @@
import { createElement } from 'react';
import { msg } from '@lingui/macro';
import { z } from 'zod';
import { mailer } from '@documenso/email/mailer';
import DocumentRejectedEmail from '@documenso/email/templates/document-rejected';
import DocumentRejectionConfirmedEmail from '@documenso/email/templates/document-rejection-confirmed';
import { prisma } from '@documenso/prisma';
import { SendStatus, SigningStatus } from '@documenso/prisma/client';
import { getI18nInstance } from '../../../client-only/providers/i18n.server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
import { extractDerivedDocumentEmailSettings } from '../../../types/document-email';
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
import { formatDocumentsPath } from '../../../utils/teams';
import { type JobDefinition } from '../../client/_internal/job';
const SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_ID = 'send.signing.rejected.emails';
@ -25,6 +9,10 @@ const SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_SCHEMA = z.object({
recipientId: z.number(),
});
export type TSendSigningRejectionEmailsJobDefinition = z.infer<
typeof SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_SCHEMA
>;
export const SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION = {
id: SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_ID,
name: 'Send Rejection Emails',
@ -34,136 +22,11 @@ export const SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION = {
schema: SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_SCHEMA,
},
handler: async ({ payload, io }) => {
const { documentId, recipientId } = payload;
const handler = await import('./send-rejection-emails.handler');
const [document, recipient] = await Promise.all([
prisma.document.findFirstOrThrow({
where: {
id: documentId,
},
include: {
User: true,
documentMeta: true,
team: {
select: {
teamEmail: true,
name: true,
url: true,
teamGlobalSettings: true,
},
},
},
}),
prisma.recipient.findFirstOrThrow({
where: {
id: recipientId,
signingStatus: SigningStatus.REJECTED,
},
}),
]);
const { documentMeta, team, User: documentOwner } = document;
const isEmailEnabled = extractDerivedDocumentEmailSettings(
document.documentMeta,
).recipientSigningRequest;
if (!isEmailEnabled) {
return;
}
const i18n = await getI18nInstance(documentMeta?.language);
// Send confirmation email to the recipient who rejected
await io.runTask('send-rejection-confirmation-email', async () => {
const recipientTemplate = createElement(DocumentRejectionConfirmedEmail, {
recipientName: recipient.name,
documentName: document.title,
documentOwnerName: document.User.name || document.User.email,
reason: recipient.rejectionReason || '',
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(),
});
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(recipientTemplate, { lang: documentMeta?.language, branding }),
renderEmailWithI18N(recipientTemplate, {
lang: documentMeta?.language,
branding,
plainText: true,
}),
]);
await mailer.sendMail({
to: {
name: recipient.name,
address: recipient.email,
},
from: {
name: FROM_NAME,
address: FROM_ADDRESS,
},
subject: i18n._(msg`Document "${document.title}" - Rejection Confirmed`),
html,
text,
});
});
// Send notification email to document owner
await io.runTask('send-owner-notification-email', async () => {
const ownerTemplate = createElement(DocumentRejectedEmail, {
recipientName: recipient.name,
documentName: document.title,
documentUrl: `${NEXT_PUBLIC_WEBAPP_URL()}${formatDocumentsPath(document.team?.url)}/${
document.id
}`,
rejectionReason: recipient.rejectionReason || '',
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(),
});
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(ownerTemplate, { lang: documentMeta?.language, branding }),
renderEmailWithI18N(ownerTemplate, {
lang: documentMeta?.language,
branding,
plainText: true,
}),
]);
await mailer.sendMail({
to: {
name: documentOwner.name || '',
address: documentOwner.email,
},
from: {
name: FROM_NAME,
address: FROM_ADDRESS,
},
subject: i18n._(msg`Document "${document.title}" - Rejected by ${recipient.name}`),
html,
text,
});
});
await io.runTask('update-recipient', async () => {
await prisma.recipient.update({
where: {
id: recipient.id,
},
data: {
sendStatus: SendStatus.SENT,
},
});
});
await handler.run({ payload, io });
},
} as const satisfies JobDefinition<
typeof SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_ID,
z.infer<typeof SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_SCHEMA>
TSendSigningRejectionEmailsJobDefinition
>;

View File

@ -0,0 +1,215 @@
import { createElement } from 'react';
import { msg } from '@lingui/macro';
import { mailer } from '@documenso/email/mailer';
import DocumentInviteEmailTemplate from '@documenso/email/templates/document-invite';
import { prisma } from '@documenso/prisma';
import {
DocumentSource,
DocumentStatus,
RecipientRole,
SendStatus,
} from '@documenso/prisma/client';
import { getI18nInstance } from '../../../client-only/providers/i18n.server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
import {
RECIPIENT_ROLES_DESCRIPTION,
RECIPIENT_ROLE_TO_EMAIL_TYPE,
} from '../../../constants/recipient-roles';
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs';
import { extractDerivedDocumentEmailSettings } from '../../../types/document-email';
import { createDocumentAuditLogData } from '../../../utils/document-audit-logs';
import { renderCustomEmailTemplate } from '../../../utils/render-custom-email-template';
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
import type { JobRunIO } from '../../client/_internal/job';
import type { TSendSigningEmailJobDefinition } from './send-signing-email';
export const run = async ({
payload,
io,
}: {
payload: TSendSigningEmailJobDefinition;
io: JobRunIO;
}) => {
const { userId, documentId, recipientId, requestMetadata } = payload;
const [user, document, recipient] = await Promise.all([
prisma.user.findFirstOrThrow({
where: {
id: userId,
},
}),
prisma.document.findFirstOrThrow({
where: {
id: documentId,
status: DocumentStatus.PENDING,
},
include: {
documentMeta: true,
team: {
select: {
teamEmail: true,
name: true,
teamGlobalSettings: true,
},
},
},
}),
prisma.recipient.findFirstOrThrow({
where: {
id: recipientId,
},
}),
]);
const { documentMeta, team } = document;
if (recipient.role === RecipientRole.CC) {
return;
}
const isRecipientSigningRequestEmailEnabled = extractDerivedDocumentEmailSettings(
document.documentMeta,
).recipientSigningRequest;
if (!isRecipientSigningRequestEmailEnabled) {
return;
}
const customEmail = document?.documentMeta;
const isDirectTemplate = document.source === DocumentSource.TEMPLATE_DIRECT_LINK;
const isTeamDocument = document.teamId !== null;
const recipientEmailType = RECIPIENT_ROLE_TO_EMAIL_TYPE[recipient.role];
const { email, name } = recipient;
const selfSigner = email === user.email;
const i18n = await getI18nInstance(documentMeta?.language);
const recipientActionVerb = i18n
._(RECIPIENT_ROLES_DESCRIPTION[recipient.role].actionVerb)
.toLowerCase();
let emailMessage = customEmail?.message || '';
let emailSubject = i18n._(msg`Please ${recipientActionVerb} this document`);
if (selfSigner) {
emailMessage = i18n._(
msg`You have initiated the document ${`"${document.title}"`} that requires you to ${recipientActionVerb} it.`,
);
emailSubject = i18n._(msg`Please ${recipientActionVerb} your document`);
}
if (isDirectTemplate) {
emailMessage = i18n._(
msg`A document was created by your direct template that requires you to ${recipientActionVerb} it.`,
);
emailSubject = i18n._(
msg`Please ${recipientActionVerb} this document created by your direct template`,
);
}
if (isTeamDocument && team) {
emailSubject = i18n._(msg`${team.name} invited you to ${recipientActionVerb} a document`);
emailMessage = customEmail?.message ?? '';
if (!emailMessage) {
emailMessage = i18n._(
team.teamGlobalSettings?.includeSenderDetails
? msg`${user.name} on behalf of "${team.name}" has invited you to ${recipientActionVerb} the document "${document.title}".`
: msg`${team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`,
);
}
}
const customEmailTemplate = {
'signer.name': name,
'signer.email': email,
'document.name': document.title,
};
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000';
const signDocumentLink = `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`;
const template = createElement(DocumentInviteEmailTemplate, {
documentName: document.title,
inviterName: user.name || undefined,
inviterEmail: isTeamDocument ? team?.teamEmail?.email || user.email : user.email,
assetBaseUrl,
signDocumentLink,
customBody: renderCustomEmailTemplate(emailMessage, customEmailTemplate),
role: recipient.role,
selfSigner,
isTeamInvite: isTeamDocument,
teamName: team?.name,
teamEmail: team?.teamEmail?.email,
includeSenderDetails: team?.teamGlobalSettings?.includeSenderDetails,
});
await io.runTask('send-signing-email', async () => {
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(template, { lang: documentMeta?.language, branding }),
renderEmailWithI18N(template, {
lang: documentMeta?.language,
branding,
plainText: true,
}),
]);
await mailer.sendMail({
to: {
name: recipient.name,
address: recipient.email,
},
from: {
name: FROM_NAME,
address: FROM_ADDRESS,
},
subject: renderCustomEmailTemplate(
documentMeta?.subject || emailSubject,
customEmailTemplate,
),
html,
text,
});
});
await io.runTask('update-recipient', async () => {
await prisma.recipient.update({
where: {
id: recipient.id,
},
data: {
sendStatus: SendStatus.SENT,
},
});
});
await io.runTask('store-audit-log', async () => {
await prisma.documentAuditLog.create({
data: createDocumentAuditLogData({
type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
documentId: document.id,
user,
requestMetadata,
data: {
emailType: recipientEmailType,
recipientId: recipient.id,
recipientName: recipient.name,
recipientEmail: recipient.email,
recipientRole: recipient.role,
isResending: false,
},
}),
});
});
};

View File

@ -1,32 +1,6 @@
import { createElement } from 'react';
import { msg } from '@lingui/macro';
import { z } from 'zod';
import { mailer } from '@documenso/email/mailer';
import DocumentInviteEmailTemplate from '@documenso/email/templates/document-invite';
import { prisma } from '@documenso/prisma';
import {
DocumentSource,
DocumentStatus,
RecipientRole,
SendStatus,
} from '@documenso/prisma/client';
import { getI18nInstance } from '../../../client-only/providers/i18n.server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
import {
RECIPIENT_ROLES_DESCRIPTION,
RECIPIENT_ROLE_TO_EMAIL_TYPE,
} from '../../../constants/recipient-roles';
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs';
import { extractDerivedDocumentEmailSettings } from '../../../types/document-email';
import { ZRequestMetadataSchema } from '../../../universal/extract-request-metadata';
import { createDocumentAuditLogData } from '../../../utils/document-audit-logs';
import { renderCustomEmailTemplate } from '../../../utils/render-custom-email-template';
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
import { type JobDefinition } from '../../client/_internal/job';
const SEND_SIGNING_EMAIL_JOB_DEFINITION_ID = 'send.signing.requested.email';
@ -38,6 +12,10 @@ const SEND_SIGNING_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
requestMetadata: ZRequestMetadataSchema.optional(),
});
export type TSendSigningEmailJobDefinition = z.infer<
typeof SEND_SIGNING_EMAIL_JOB_DEFINITION_SCHEMA
>;
export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
id: SEND_SIGNING_EMAIL_JOB_DEFINITION_ID,
name: 'Send Signing Email',
@ -47,185 +25,11 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
schema: SEND_SIGNING_EMAIL_JOB_DEFINITION_SCHEMA,
},
handler: async ({ payload, io }) => {
const { userId, documentId, recipientId, requestMetadata } = payload;
const handler = await import('./send-signing-email.handler');
const [user, document, recipient] = await Promise.all([
prisma.user.findFirstOrThrow({
where: {
id: userId,
},
}),
prisma.document.findFirstOrThrow({
where: {
id: documentId,
status: DocumentStatus.PENDING,
},
include: {
documentMeta: true,
team: {
select: {
teamEmail: true,
name: true,
teamGlobalSettings: true,
},
},
},
}),
prisma.recipient.findFirstOrThrow({
where: {
id: recipientId,
},
}),
]);
const { documentMeta, team } = document;
if (recipient.role === RecipientRole.CC) {
return;
}
const isRecipientSigningRequestEmailEnabled = extractDerivedDocumentEmailSettings(
document.documentMeta,
).recipientSigningRequest;
if (!isRecipientSigningRequestEmailEnabled) {
return;
}
const customEmail = document?.documentMeta;
const isDirectTemplate = document.source === DocumentSource.TEMPLATE_DIRECT_LINK;
const isTeamDocument = document.teamId !== null;
const recipientEmailType = RECIPIENT_ROLE_TO_EMAIL_TYPE[recipient.role];
const { email, name } = recipient;
const selfSigner = email === user.email;
const i18n = await getI18nInstance(documentMeta?.language);
const recipientActionVerb = i18n
._(RECIPIENT_ROLES_DESCRIPTION[recipient.role].actionVerb)
.toLowerCase();
let emailMessage = customEmail?.message || '';
let emailSubject = i18n._(msg`Please ${recipientActionVerb} this document`);
if (selfSigner) {
emailMessage = i18n._(
msg`You have initiated the document ${`"${document.title}"`} that requires you to ${recipientActionVerb} it.`,
);
emailSubject = i18n._(msg`Please ${recipientActionVerb} your document`);
}
if (isDirectTemplate) {
emailMessage = i18n._(
msg`A document was created by your direct template that requires you to ${recipientActionVerb} it.`,
);
emailSubject = i18n._(
msg`Please ${recipientActionVerb} this document created by your direct template`,
);
}
if (isTeamDocument && team) {
emailSubject = i18n._(msg`${team.name} invited you to ${recipientActionVerb} a document`);
emailMessage = customEmail?.message ?? '';
if (!emailMessage) {
emailMessage = i18n._(
team.teamGlobalSettings?.includeSenderDetails
? msg`${user.name} on behalf of "${team.name}" has invited you to ${recipientActionVerb} the document "${document.title}".`
: msg`${team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`,
);
}
}
const customEmailTemplate = {
'signer.name': name,
'signer.email': email,
'document.name': document.title,
};
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000';
const signDocumentLink = `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`;
const template = createElement(DocumentInviteEmailTemplate, {
documentName: document.title,
inviterName: user.name || undefined,
inviterEmail: isTeamDocument ? team?.teamEmail?.email || user.email : user.email,
assetBaseUrl,
signDocumentLink,
customBody: renderCustomEmailTemplate(emailMessage, customEmailTemplate),
role: recipient.role,
selfSigner,
isTeamInvite: isTeamDocument,
teamName: team?.name,
teamEmail: team?.teamEmail?.email,
includeSenderDetails: team?.teamGlobalSettings?.includeSenderDetails,
});
await io.runTask('send-signing-email', async () => {
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(template, { lang: documentMeta?.language, branding }),
renderEmailWithI18N(template, {
lang: documentMeta?.language,
branding,
plainText: true,
}),
]);
await mailer.sendMail({
to: {
name: recipient.name,
address: recipient.email,
},
from: {
name: FROM_NAME,
address: FROM_ADDRESS,
},
subject: renderCustomEmailTemplate(
documentMeta?.subject || emailSubject,
customEmailTemplate,
),
html,
text,
});
});
await io.runTask('update-recipient', async () => {
await prisma.recipient.update({
where: {
id: recipient.id,
},
data: {
sendStatus: SendStatus.SENT,
},
});
});
await io.runTask('store-audit-log', async () => {
await prisma.documentAuditLog.create({
data: createDocumentAuditLogData({
type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
documentId: document.id,
user,
requestMetadata,
data: {
emailType: recipientEmailType,
recipientId: recipient.id,
recipientName: recipient.name,
recipientEmail: recipient.email,
recipientRole: recipient.role,
isResending: false,
},
}),
});
});
await handler.run({ payload, io });
},
} as const satisfies JobDefinition<
typeof SEND_SIGNING_EMAIL_JOB_DEFINITION_ID,
z.infer<typeof SEND_SIGNING_EMAIL_JOB_DEFINITION_SCHEMA>
TSendSigningEmailJobDefinition
>;

View File

@ -0,0 +1,23 @@
import { sendTeamDeleteEmail } from '../../../server-only/team/delete-team';
import type { JobRunIO } from '../../client/_internal/job';
import type { TSendTeamDeletedEmailJobDefinition } from './send-team-deleted-email';
export const run = async ({
payload,
io,
}: {
payload: TSendTeamDeletedEmailJobDefinition;
io: JobRunIO;
}) => {
const { team, members } = payload;
for (const member of members) {
await io.runTask(`send-team-deleted-email--${team.url}_${member.id}`, async () => {
await sendTeamDeleteEmail({
email: member.email,
team,
isOwner: member.id === team.ownerUserId,
});
});
}
};

View File

@ -2,7 +2,6 @@ import { z } from 'zod';
import { DocumentVisibility } from '@documenso/prisma/client';
import { sendTeamDeleteEmail } from '../../../server-only/team/delete-team';
import type { JobDefinition } from '../../client/_internal/job';
const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_ID = 'send.team-deleted.email';
@ -37,6 +36,10 @@ const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
),
});
export type TSendTeamDeletedEmailJobDefinition = z.infer<
typeof SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA
>;
export const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION = {
id: SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_ID,
name: 'Send Team Deleted Email',
@ -46,19 +49,11 @@ export const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION = {
schema: SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA,
},
handler: async ({ payload, io }) => {
const { team, members } = payload;
const handler = await import('./send-team-deleted-email.handler');
for (const member of members) {
await io.runTask(`send-team-deleted-email--${team.url}_${member.id}`, async () => {
await sendTeamDeleteEmail({
email: member.email,
team,
isOwner: member.id === team.ownerUserId,
});
});
}
await handler.run({ payload, io });
},
} as const satisfies JobDefinition<
typeof SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_ID,
z.infer<typeof SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA>
TSendTeamDeletedEmailJobDefinition
>;

View File

@ -0,0 +1,105 @@
import { createElement } from 'react';
import { msg } from '@lingui/macro';
import { mailer } from '@documenso/email/mailer';
import TeamJoinEmailTemplate from '@documenso/email/templates/team-join';
import { prisma } from '@documenso/prisma';
import { TeamMemberRole } from '@documenso/prisma/client';
import { getI18nInstance } from '../../../client-only/providers/i18n.server';
import { WEBAPP_BASE_URL } from '../../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
import type { JobRunIO } from '../../client/_internal/job';
import type { TSendTeamMemberJoinedEmailJobDefinition } from './send-team-member-joined-email';
export const run = async ({
payload,
io,
}: {
payload: TSendTeamMemberJoinedEmailJobDefinition;
io: JobRunIO;
}) => {
const team = await prisma.team.findFirstOrThrow({
where: {
id: payload.teamId,
},
include: {
members: {
where: {
role: {
in: [TeamMemberRole.ADMIN, TeamMemberRole.MANAGER],
},
},
include: {
user: true,
},
},
teamGlobalSettings: true,
},
});
const invitedMember = await prisma.teamMember.findFirstOrThrow({
where: {
id: payload.memberId,
teamId: payload.teamId,
},
include: {
user: true,
},
});
for (const member of team.members) {
if (member.id === invitedMember.id) {
continue;
}
await io.runTask(
`send-team-member-joined-email--${invitedMember.id}_${member.id}`,
async () => {
const emailContent = createElement(TeamJoinEmailTemplate, {
assetBaseUrl: WEBAPP_BASE_URL,
baseUrl: WEBAPP_BASE_URL,
memberName: invitedMember.user.name || '',
memberEmail: invitedMember.user.email,
teamName: team.name,
teamUrl: team.url,
});
const branding = team.teamGlobalSettings
? teamGlobalSettingsToBranding(team.teamGlobalSettings)
: undefined;
const lang = team.teamGlobalSettings?.documentLanguage;
// !: Replace with the actual language of the recipient later
const [html, text] = await Promise.all([
renderEmailWithI18N(emailContent, {
lang,
branding,
}),
renderEmailWithI18N(emailContent, {
lang,
branding,
plainText: true,
}),
]);
const i18n = await getI18nInstance(lang);
await mailer.sendMail({
to: member.user.email,
from: {
name: FROM_NAME,
address: FROM_ADDRESS,
},
subject: i18n._(msg`A new member has joined your team`),
html,
text,
});
},
);
}
};

View File

@ -1,18 +1,5 @@
import { createElement } from 'react';
import { msg } from '@lingui/macro';
import { z } from 'zod';
import { mailer } from '@documenso/email/mailer';
import TeamJoinEmailTemplate from '@documenso/email/templates/team-join';
import { prisma } from '@documenso/prisma';
import { TeamMemberRole } from '@documenso/prisma/client';
import { getI18nInstance } from '../../../client-only/providers/i18n.server';
import { WEBAPP_BASE_URL } from '../../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
import type { JobDefinition } from '../../client/_internal/job';
const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID = 'send.team-member-joined.email';
@ -22,6 +9,10 @@ const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
memberId: z.number(),
});
export type TSendTeamMemberJoinedEmailJobDefinition = z.infer<
typeof SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_SCHEMA
>;
export const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION = {
id: SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID,
name: 'Send Team Member Joined Email',
@ -31,88 +22,11 @@ export const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION = {
schema: SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_SCHEMA,
},
handler: async ({ payload, io }) => {
const team = await prisma.team.findFirstOrThrow({
where: {
id: payload.teamId,
},
include: {
members: {
where: {
role: {
in: [TeamMemberRole.ADMIN, TeamMemberRole.MANAGER],
},
},
include: {
user: true,
},
},
teamGlobalSettings: true,
},
});
const handler = await import('./send-team-member-joined-email.handler');
const invitedMember = await prisma.teamMember.findFirstOrThrow({
where: {
id: payload.memberId,
teamId: payload.teamId,
},
include: {
user: true,
},
});
for (const member of team.members) {
if (member.id === invitedMember.id) {
continue;
}
await io.runTask(
`send-team-member-joined-email--${invitedMember.id}_${member.id}`,
async () => {
const emailContent = createElement(TeamJoinEmailTemplate, {
assetBaseUrl: WEBAPP_BASE_URL,
baseUrl: WEBAPP_BASE_URL,
memberName: invitedMember.user.name || '',
memberEmail: invitedMember.user.email,
teamName: team.name,
teamUrl: team.url,
});
const branding = team.teamGlobalSettings
? teamGlobalSettingsToBranding(team.teamGlobalSettings)
: undefined;
const lang = team.teamGlobalSettings?.documentLanguage;
// !: Replace with the actual language of the recipient later
const [html, text] = await Promise.all([
renderEmailWithI18N(emailContent, {
lang,
branding,
}),
renderEmailWithI18N(emailContent, {
lang,
branding,
plainText: true,
}),
]);
const i18n = await getI18nInstance(lang);
await mailer.sendMail({
to: member.user.email,
from: {
name: FROM_NAME,
address: FROM_ADDRESS,
},
subject: i18n._(msg`A new member has joined your team`),
html,
text,
});
},
);
}
await handler.run({ payload, io });
},
} as const satisfies JobDefinition<
typeof SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID,
z.infer<typeof SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_SCHEMA>
TSendTeamMemberJoinedEmailJobDefinition
>;

View File

@ -0,0 +1,93 @@
import { createElement } from 'react';
import { msg } from '@lingui/macro';
import { mailer } from '@documenso/email/mailer';
import TeamJoinEmailTemplate from '@documenso/email/templates/team-join';
import { prisma } from '@documenso/prisma';
import { TeamMemberRole } from '@documenso/prisma/client';
import { getI18nInstance } from '../../../client-only/providers/i18n.server';
import { WEBAPP_BASE_URL } from '../../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
import type { JobRunIO } from '../../client/_internal/job';
import type { TSendTeamMemberLeftEmailJobDefinition } from './send-team-member-left-email';
export const run = async ({
payload,
io,
}: {
payload: TSendTeamMemberLeftEmailJobDefinition;
io: JobRunIO;
}) => {
const team = await prisma.team.findFirstOrThrow({
where: {
id: payload.teamId,
},
include: {
members: {
where: {
role: {
in: [TeamMemberRole.ADMIN, TeamMemberRole.MANAGER],
},
},
include: {
user: true,
},
},
teamGlobalSettings: true,
},
});
const oldMember = await prisma.user.findFirstOrThrow({
where: {
id: payload.memberUserId,
},
});
for (const member of team.members) {
await io.runTask(`send-team-member-left-email--${oldMember.id}_${member.id}`, async () => {
const emailContent = createElement(TeamJoinEmailTemplate, {
assetBaseUrl: WEBAPP_BASE_URL,
baseUrl: WEBAPP_BASE_URL,
memberName: oldMember.name || '',
memberEmail: oldMember.email,
teamName: team.name,
teamUrl: team.url,
});
const branding = team.teamGlobalSettings
? teamGlobalSettingsToBranding(team.teamGlobalSettings)
: undefined;
const lang = team.teamGlobalSettings?.documentLanguage;
const [html, text] = await Promise.all([
renderEmailWithI18N(emailContent, {
lang,
branding,
}),
renderEmailWithI18N(emailContent, {
lang,
branding,
plainText: true,
}),
]);
const i18n = await getI18nInstance(lang);
await mailer.sendMail({
to: member.user.email,
from: {
name: FROM_NAME,
address: FROM_ADDRESS,
},
subject: i18n._(msg`A team member has left ${team.name}`),
html,
text,
});
});
}
};

View File

@ -1,18 +1,5 @@
import { createElement } from 'react';
import { msg } from '@lingui/macro';
import { z } from 'zod';
import { mailer } from '@documenso/email/mailer';
import TeamJoinEmailTemplate from '@documenso/email/templates/team-join';
import { prisma } from '@documenso/prisma';
import { TeamMemberRole } from '@documenso/prisma/client';
import { getI18nInstance } from '../../../client-only/providers/i18n.server';
import { WEBAPP_BASE_URL } from '../../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
import type { JobDefinition } from '../../client/_internal/job';
const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_ID = 'send.team-member-left.email';
@ -22,6 +9,10 @@ const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
memberUserId: z.number(),
});
export type TSendTeamMemberLeftEmailJobDefinition = z.infer<
typeof SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_SCHEMA
>;
export const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION = {
id: SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_ID,
name: 'Send Team Member Left Email',
@ -31,76 +22,11 @@ export const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION = {
schema: SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_SCHEMA,
},
handler: async ({ payload, io }) => {
const team = await prisma.team.findFirstOrThrow({
where: {
id: payload.teamId,
},
include: {
members: {
where: {
role: {
in: [TeamMemberRole.ADMIN, TeamMemberRole.MANAGER],
},
},
include: {
user: true,
},
},
teamGlobalSettings: true,
},
});
const handler = await import('./send-team-member-left-email.handler');
const oldMember = await prisma.user.findFirstOrThrow({
where: {
id: payload.memberUserId,
},
});
for (const member of team.members) {
await io.runTask(`send-team-member-left-email--${oldMember.id}_${member.id}`, async () => {
const emailContent = createElement(TeamJoinEmailTemplate, {
assetBaseUrl: WEBAPP_BASE_URL,
baseUrl: WEBAPP_BASE_URL,
memberName: oldMember.name || '',
memberEmail: oldMember.email,
teamName: team.name,
teamUrl: team.url,
});
const branding = team.teamGlobalSettings
? teamGlobalSettingsToBranding(team.teamGlobalSettings)
: undefined;
const lang = team.teamGlobalSettings?.documentLanguage;
const [html, text] = await Promise.all([
renderEmailWithI18N(emailContent, {
lang,
branding,
}),
renderEmailWithI18N(emailContent, {
lang,
branding,
plainText: true,
}),
]);
const i18n = await getI18nInstance(lang);
await mailer.sendMail({
to: member.user.email,
from: {
name: FROM_NAME,
address: FROM_ADDRESS,
},
subject: i18n._(msg`A team member has left ${team.name}`),
html,
text,
});
});
}
await handler.run({ payload, io });
},
} as const satisfies JobDefinition<
typeof SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_ID,
z.infer<typeof SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_SCHEMA>
TSendTeamMemberLeftEmailJobDefinition
>;

View File

@ -0,0 +1,256 @@
import { nanoid } from 'nanoid';
import path from 'node:path';
import { PDFDocument } from 'pdf-lib';
import { prisma } from '@documenso/prisma';
import {
DocumentStatus,
RecipientRole,
SigningStatus,
WebhookTriggerEvents,
} from '@documenso/prisma/client';
import { signPdf } from '@documenso/signing';
import { sendCompletedEmail } from '../../../server-only/document/send-completed-email';
import PostHogServerClient from '../../../server-only/feature-flags/get-post-hog-server-client';
import { getCertificatePdf } from '../../../server-only/htmltopdf/get-certificate-pdf';
import { flattenAnnotations } from '../../../server-only/pdf/flatten-annotations';
import { flattenForm } from '../../../server-only/pdf/flatten-form';
import { insertFieldInPDF } from '../../../server-only/pdf/insert-field-in-pdf';
import { normalizeSignatureAppearances } from '../../../server-only/pdf/normalize-signature-appearances';
import { triggerWebhook } from '../../../server-only/webhooks/trigger/trigger-webhook';
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs';
import { ZWebhookDocumentSchema } from '../../../types/webhook-payload';
import { getFile } from '../../../universal/upload/get-file';
import { putPdfFile } from '../../../universal/upload/put-file';
import { fieldsContainUnsignedRequiredField } from '../../../utils/advanced-fields-helpers';
import { createDocumentAuditLogData } from '../../../utils/document-audit-logs';
import type { JobRunIO } from '../../client/_internal/job';
import type { TSealDocumentJobDefinition } from './seal-document';
export const run = async ({
payload,
io,
}: {
payload: TSealDocumentJobDefinition;
io: JobRunIO;
}) => {
const { documentId, sendEmail = true, isResealing = false, requestMetadata } = payload;
const document = await prisma.document.findFirstOrThrow({
where: {
id: documentId,
Recipient: {
every: {
signingStatus: SigningStatus.SIGNED,
},
},
},
include: {
documentMeta: true,
Recipient: true,
team: {
select: {
teamGlobalSettings: {
select: {
includeSigningCertificate: true,
},
},
},
},
},
});
// Seems silly but we need to do this in case the job is re-ran
// after it has already run through the update task further below.
// eslint-disable-next-line @typescript-eslint/require-await
const documentStatus = await io.runTask('get-document-status', async () => {
return document.status;
});
// This is the same case as above.
// eslint-disable-next-line @typescript-eslint/require-await
const documentDataId = await io.runTask('get-document-data-id', async () => {
return document.documentDataId;
});
const documentData = await prisma.documentData.findFirst({
where: {
id: documentDataId,
},
});
if (!documentData) {
throw new Error(`Document ${document.id} has no document data`);
}
const recipients = await prisma.recipient.findMany({
where: {
documentId: document.id,
role: {
not: RecipientRole.CC,
},
},
});
if (recipients.some((recipient) => recipient.signingStatus !== SigningStatus.SIGNED)) {
throw new Error(`Document ${document.id} has unsigned recipients`);
}
const fields = await prisma.field.findMany({
where: {
documentId: document.id,
},
include: {
Signature: true,
},
});
if (fieldsContainUnsignedRequiredField(fields)) {
throw new Error(`Document ${document.id} has unsigned required fields`);
}
if (isResealing) {
// If we're resealing we want to use the initial data for the document
// so we aren't placing fields on top of eachother.
documentData.data = documentData.initialData;
}
const pdfData = await getFile(documentData);
const certificateData =
(document.team?.teamGlobalSettings?.includeSigningCertificate ?? true)
? await getCertificatePdf({
documentId,
language: document.documentMeta?.language,
}).catch(() => null)
: null;
const newDataId = await io.runTask('decorate-and-sign-pdf', async () => {
const pdfDoc = await PDFDocument.load(pdfData);
// Normalize and flatten layers that could cause issues with the signature
normalizeSignatureAppearances(pdfDoc);
flattenForm(pdfDoc);
flattenAnnotations(pdfDoc);
if (certificateData) {
const certificateDoc = await PDFDocument.load(certificateData);
const certificatePages = await pdfDoc.copyPages(
certificateDoc,
certificateDoc.getPageIndices(),
);
certificatePages.forEach((page) => {
pdfDoc.addPage(page);
});
}
for (const field of fields) {
if (field.inserted) {
await insertFieldInPDF(pdfDoc, field);
}
}
// Re-flatten the form to handle our checkbox and radio fields that
// create native arcoFields
flattenForm(pdfDoc);
const pdfBytes = await pdfDoc.save();
const pdfBuffer = await signPdf({ pdf: Buffer.from(pdfBytes) });
const { name } = path.parse(document.title);
const documentData = await putPdfFile({
name: `${name}_signed.pdf`,
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(pdfBuffer),
});
return documentData.id;
});
const postHog = PostHogServerClient();
if (postHog) {
postHog.capture({
distinctId: nanoid(),
event: 'App: Document Sealed',
properties: {
documentId: document.id,
},
});
}
await io.runTask('update-document', async () => {
await prisma.$transaction(async (tx) => {
const newData = await tx.documentData.findFirstOrThrow({
where: {
id: newDataId,
},
});
await tx.document.update({
where: {
id: document.id,
},
data: {
status: DocumentStatus.COMPLETED,
completedAt: new Date(),
},
});
await tx.documentData.update({
where: {
id: documentData.id,
},
data: {
data: newData.data,
},
});
await tx.documentAuditLog.create({
data: createDocumentAuditLogData({
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED,
documentId: document.id,
requestMetadata,
user: null,
data: {
transactionId: nanoid(),
},
}),
});
});
});
await io.runTask('send-completed-email', async () => {
let shouldSendCompletedEmail = sendEmail && !isResealing;
if (isResealing && documentStatus !== DocumentStatus.COMPLETED) {
shouldSendCompletedEmail = sendEmail;
}
if (shouldSendCompletedEmail) {
await sendCompletedEmail({ documentId, requestMetadata });
}
});
const updatedDocument = await prisma.document.findFirstOrThrow({
where: {
id: document.id,
},
include: {
documentData: true,
documentMeta: true,
Recipient: true,
},
});
await triggerWebhook({
event: WebhookTriggerEvents.DOCUMENT_COMPLETED,
data: ZWebhookDocumentSchema.parse(updatedDocument),
userId: updatedDocument.userId,
teamId: updatedDocument.teamId ?? undefined,
});
};

View File

@ -1,31 +1,6 @@
import { nanoid } from 'nanoid';
import path from 'node:path';
import { PDFDocument } from 'pdf-lib';
import { z } from 'zod';
import { prisma } from '@documenso/prisma';
import {
DocumentStatus,
RecipientRole,
SigningStatus,
WebhookTriggerEvents,
} from '@documenso/prisma/client';
import { signPdf } from '@documenso/signing';
import { sendCompletedEmail } from '../../../server-only/document/send-completed-email';
import PostHogServerClient from '../../../server-only/feature-flags/get-post-hog-server-client';
import { getCertificatePdf } from '../../../server-only/htmltopdf/get-certificate-pdf';
import { flattenAnnotations } from '../../../server-only/pdf/flatten-annotations';
import { flattenForm } from '../../../server-only/pdf/flatten-form';
import { insertFieldInPDF } from '../../../server-only/pdf/insert-field-in-pdf';
import { normalizeSignatureAppearances } from '../../../server-only/pdf/normalize-signature-appearances';
import { triggerWebhook } from '../../../server-only/webhooks/trigger/trigger-webhook';
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs';
import { ZWebhookDocumentSchema } from '../../../types/webhook-payload';
import { ZRequestMetadataSchema } from '../../../universal/extract-request-metadata';
import { getFile } from '../../../universal/upload/get-file';
import { putPdfFile } from '../../../universal/upload/put-file';
import { createDocumentAuditLogData } from '../../../utils/document-audit-logs';
import { type JobDefinition } from '../../client/_internal/job';
const SEAL_DOCUMENT_JOB_DEFINITION_ID = 'internal.seal-document';
@ -37,6 +12,8 @@ const SEAL_DOCUMENT_JOB_DEFINITION_SCHEMA = z.object({
requestMetadata: ZRequestMetadataSchema.optional(),
});
export type TSealDocumentJobDefinition = z.infer<typeof SEAL_DOCUMENT_JOB_DEFINITION_SCHEMA>;
export const SEAL_DOCUMENT_JOB_DEFINITION = {
id: SEAL_DOCUMENT_JOB_DEFINITION_ID,
name: 'Seal Document',
@ -46,223 +23,11 @@ export const SEAL_DOCUMENT_JOB_DEFINITION = {
schema: SEAL_DOCUMENT_JOB_DEFINITION_SCHEMA,
},
handler: async ({ payload, io }) => {
const { documentId, sendEmail = true, isResealing = false, requestMetadata } = payload;
const handler = await import('./seal-document.handler');
const document = await prisma.document.findFirstOrThrow({
where: {
id: documentId,
Recipient: {
every: {
signingStatus: SigningStatus.SIGNED,
},
},
},
include: {
documentMeta: true,
Recipient: true,
team: {
select: {
teamGlobalSettings: {
select: {
includeSigningCertificate: true,
},
},
},
},
},
});
// Seems silly but we need to do this in case the job is re-ran
// after it has already run through the update task further below.
// eslint-disable-next-line @typescript-eslint/require-await
const documentStatus = await io.runTask('get-document-status', async () => {
return document.status;
});
// This is the same case as above.
// eslint-disable-next-line @typescript-eslint/require-await
const documentDataId = await io.runTask('get-document-data-id', async () => {
return document.documentDataId;
});
const documentData = await prisma.documentData.findFirst({
where: {
id: documentDataId,
},
});
if (!documentData) {
throw new Error(`Document ${document.id} has no document data`);
}
const recipients = await prisma.recipient.findMany({
where: {
documentId: document.id,
role: {
not: RecipientRole.CC,
},
},
});
if (recipients.some((recipient) => recipient.signingStatus !== SigningStatus.SIGNED)) {
throw new Error(`Document ${document.id} has unsigned recipients`);
}
const fields = await prisma.field.findMany({
where: {
documentId: document.id,
},
include: {
Signature: true,
},
});
if (fields.some((field) => !field.inserted)) {
throw new Error(`Document ${document.id} has unsigned fields`);
}
if (isResealing) {
// If we're resealing we want to use the initial data for the document
// so we aren't placing fields on top of eachother.
documentData.data = documentData.initialData;
}
const pdfData = await getFile(documentData);
const certificateData =
(document.team?.teamGlobalSettings?.includeSigningCertificate ?? true)
? await getCertificatePdf({
documentId,
language: document.documentMeta?.language,
}).catch(() => null)
: null;
const newDataId = await io.runTask('decorate-and-sign-pdf', async () => {
const pdfDoc = await PDFDocument.load(pdfData);
// Normalize and flatten layers that could cause issues with the signature
normalizeSignatureAppearances(pdfDoc);
flattenForm(pdfDoc);
flattenAnnotations(pdfDoc);
if (certificateData) {
const certificateDoc = await PDFDocument.load(certificateData);
const certificatePages = await pdfDoc.copyPages(
certificateDoc,
certificateDoc.getPageIndices(),
);
certificatePages.forEach((page) => {
pdfDoc.addPage(page);
});
}
for (const field of fields) {
await insertFieldInPDF(pdfDoc, field);
}
// Re-flatten the form to handle our checkbox and radio fields that
// create native arcoFields
flattenForm(pdfDoc);
const pdfBytes = await pdfDoc.save();
const pdfBuffer = await signPdf({ pdf: Buffer.from(pdfBytes) });
const { name } = path.parse(document.title);
const documentData = await putPdfFile({
name: `${name}_signed.pdf`,
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(pdfBuffer),
});
return documentData.id;
});
const postHog = PostHogServerClient();
if (postHog) {
postHog.capture({
distinctId: nanoid(),
event: 'App: Document Sealed',
properties: {
documentId: document.id,
},
});
}
await io.runTask('update-document', async () => {
await prisma.$transaction(async (tx) => {
const newData = await tx.documentData.findFirstOrThrow({
where: {
id: newDataId,
},
});
await tx.document.update({
where: {
id: document.id,
},
data: {
status: DocumentStatus.COMPLETED,
completedAt: new Date(),
},
});
await tx.documentData.update({
where: {
id: documentData.id,
},
data: {
data: newData.data,
},
});
await tx.documentAuditLog.create({
data: createDocumentAuditLogData({
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED,
documentId: document.id,
requestMetadata,
user: null,
data: {
transactionId: nanoid(),
},
}),
});
});
});
await io.runTask('send-completed-email', async () => {
let shouldSendCompletedEmail = sendEmail && !isResealing;
if (isResealing && documentStatus !== DocumentStatus.COMPLETED) {
shouldSendCompletedEmail = sendEmail;
}
if (shouldSendCompletedEmail) {
await sendCompletedEmail({ documentId, requestMetadata });
}
});
const updatedDocument = await prisma.document.findFirstOrThrow({
where: {
id: document.id,
},
include: {
documentData: true,
documentMeta: true,
Recipient: true,
},
});
await triggerWebhook({
event: WebhookTriggerEvents.DOCUMENT_COMPLETED,
data: ZWebhookDocumentSchema.parse(updatedDocument),
userId: updatedDocument.userId,
teamId: updatedDocument.teamId ?? undefined,
});
await handler.run({ payload, io });
},
} as const satisfies JobDefinition<
typeof SEAL_DOCUMENT_JOB_DEFINITION_ID,
z.infer<typeof SEAL_DOCUMENT_JOB_DEFINITION_SCHEMA>
TSealDocumentJobDefinition
>;

View File

@ -121,6 +121,10 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
throw new Error(ErrorCode.UNVERIFIED_EMAIL);
}
if (user.disabled) {
throw new Error(ErrorCode.ACCOUNT_DISABLED);
}
return {
id: Number(user.id),
email: user.email,

View File

@ -20,4 +20,5 @@ export const ErrorCode = {
MISSING_ENCRYPTION_KEY: 'MISSING_ENCRYPTION_KEY',
MISSING_BACKUP_CODE: 'MISSING_BACKUP_CODE',
UNVERIFIED_EMAIL: 'UNVERIFIED_EMAIL',
ACCOUNT_DISABLED: 'ACCOUNT_DISABLED',
} as const;

View File

@ -21,6 +21,10 @@ export const getServerComponentSession = cache(async () => {
},
});
if (user.disabled) {
return { user: null, session: null };
}
return { user, session };
});

View File

@ -56,11 +56,11 @@
"sharp": "0.32.6",
"stripe": "^12.7.0",
"ts-pattern": "^5.0.5",
"zod": "^3.23.8"
"zod": "3.24.1"
},
"devDependencies": {
"@playwright/browser-chromium": "1.43.0",
"@types/luxon": "^3.3.1",
"@types/pg": "^8.11.4"
}
}
}

View File

@ -1,5 +1,5 @@
import { prisma } from '@documenso/prisma';
import { Prisma } from '@documenso/prisma/client';
import { DocumentStatus, Prisma } from '@documenso/prisma/client';
export type SigningVolume = {
id: number;
@ -43,34 +43,41 @@ export async function getSigningVolume({
],
});
const orderByClause = getOrderByClause({ sortBy, sortOrder });
const [subscriptions, totalCount] = await Promise.all([
prisma.subscription.findMany({
where: whereClause,
include: {
User: {
include: {
select: {
name: true,
email: true,
Document: {
where: {
status: 'COMPLETED',
status: DocumentStatus.COMPLETED,
deletedAt: null,
teamId: null,
},
},
},
},
team: {
include: {
select: {
name: true,
document: {
where: {
status: 'COMPLETED',
status: DocumentStatus.COMPLETED,
deletedAt: null,
},
},
},
},
},
orderBy: orderByClause,
orderBy:
sortBy === 'name'
? [{ User: { name: sortOrder } }, { team: { name: sortOrder } }, { createdAt: 'desc' }]
: sortBy === 'createdAt'
? [{ createdAt: sortOrder }]
: undefined,
skip: Math.max(page - 1, 0) * perPage,
take: perPage,
}),
@ -82,10 +89,8 @@ export async function getSigningVolume({
const leaderboardWithVolume: SigningVolume[] = subscriptions.map((subscription) => {
const name =
subscription.User?.name || subscription.team?.name || subscription.User?.email || 'Unknown';
const userSignedDocs = subscription.User?.Document?.length || 0;
const teamSignedDocs = subscription.team?.document?.length || 0;
return {
id: subscription.id,
name,
@ -95,54 +100,16 @@ export async function getSigningVolume({
};
});
if (sortBy === 'signingVolume') {
leaderboardWithVolume.sort((a, b) => {
return sortOrder === 'desc'
? b.signingVolume - a.signingVolume
: a.signingVolume - b.signingVolume;
});
}
return {
leaderboard: leaderboardWithVolume,
totalPages: Math.ceil(totalCount / perPage),
};
}
function getOrderByClause(options: {
sortBy: string;
sortOrder: 'asc' | 'desc';
}): Prisma.SubscriptionOrderByWithRelationInput | Prisma.SubscriptionOrderByWithRelationInput[] {
const { sortBy, sortOrder } = options;
if (sortBy === 'name') {
return [
{
User: {
name: sortOrder,
},
},
{
team: {
name: sortOrder,
},
},
];
}
if (sortBy === 'createdAt') {
return {
createdAt: sortOrder,
};
}
// Default: sort by signing volume
return [
{
User: {
Document: {
_count: sortOrder,
},
},
},
{
team: {
document: {
_count: sortOrder,
},
},
},
];
}

View File

@ -1,5 +1,6 @@
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { fieldsContainUnsignedRequiredField } from '@documenso/lib/utils/advanced-fields-helpers';
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
import { prisma } from '@documenso/prisma';
import {
@ -8,8 +9,8 @@ import {
RecipientRole,
SendStatus,
SigningStatus,
WebhookTriggerEvents,
} from '@documenso/prisma/client';
import { WebhookTriggerEvents } from '@documenso/prisma/client';
import { jobs } from '../../jobs/client';
import type { TRecipientActionAuth } from '../../types/document-auth';
@ -85,7 +86,7 @@ export const completeDocumentWithToken = async ({
},
});
if (fields.some((field) => !field.inserted)) {
if (fieldsContainUnsignedRequiredField(fields)) {
throw new Error(`Recipient ${recipient.id} has unsigned fields`);
}
@ -139,6 +140,14 @@ export const completeDocumentWithToken = async ({
});
});
await jobs.triggerJob({
name: 'send.recipient.signed.email',
payload: {
documentId: document.id,
recipientId: recipient.id,
},
});
const pendingRecipients = await prisma.recipient.findMany({
select: {
id: true,

View File

@ -1,6 +1,9 @@
'use server';
import type { z } from 'zod';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { normalizePdf as makeNormalizedPdf } from '@documenso/lib/server-only/pdf/normalize-pdf';
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
@ -8,8 +11,11 @@ import { prisma } from '@documenso/prisma';
import { DocumentSource, DocumentVisibility, WebhookTriggerEvents } from '@documenso/prisma/client';
import type { Team, TeamGlobalSettings } from '@documenso/prisma/client';
import { TeamMemberRole } from '@documenso/prisma/client';
import { DocumentSchema } from '@documenso/prisma/generated/zod';
import { ZWebhookDocumentSchema } from '../../types/webhook-payload';
import { getFile } from '../../universal/upload/get-file';
import { putPdfFile } from '../../universal/upload/put-file';
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
export type CreateDocumentOptions = {
@ -19,18 +25,24 @@ export type CreateDocumentOptions = {
teamId?: number;
documentDataId: string;
formValues?: Record<string, string | number | boolean>;
normalizePdf?: boolean;
requestMetadata?: RequestMetadata;
};
export const ZCreateDocumentResponseSchema = DocumentSchema;
export type TCreateDocumentResponse = z.infer<typeof ZCreateDocumentResponseSchema>;
export const createDocument = async ({
userId,
title,
externalId,
documentDataId,
teamId,
normalizePdf,
formValues,
requestMetadata,
}: CreateDocumentOptions) => {
}: CreateDocumentOptions): Promise<TCreateDocumentResponse> => {
const user = await prisma.user.findFirstOrThrow({
where: {
id: userId,
@ -82,22 +94,44 @@ export const createDocument = async ({
globalVisibility: DocumentVisibility | null | undefined,
userRole: TeamMemberRole,
): DocumentVisibility => {
const defaultVisibility = globalVisibility ?? DocumentVisibility.EVERYONE;
if (globalVisibility) {
return globalVisibility;
}
if (userRole === TeamMemberRole.ADMIN) {
return defaultVisibility;
return DocumentVisibility.ADMIN;
}
if (userRole === TeamMemberRole.MANAGER) {
if (defaultVisibility === DocumentVisibility.ADMIN) {
return DocumentVisibility.MANAGER_AND_ABOVE;
}
return defaultVisibility;
return DocumentVisibility.MANAGER_AND_ABOVE;
}
return DocumentVisibility.EVERYONE;
};
if (normalizePdf) {
const documentData = await prisma.documentData.findFirst({
where: {
id: documentDataId,
},
});
if (documentData) {
const buffer = await getFile(documentData);
const normalizedPdf = await makeNormalizedPdf(Buffer.from(buffer));
const newDocumentData = await putPdfFile({
name: title.endsWith('.pdf') ? title : `${title}.pdf`,
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(normalizedPdf),
});
// eslint-disable-next-line require-atomic-updates
documentDataId = newDocumentData.id;
}
}
return await prisma.$transaction(async (tx) => {
const document = await tx.document.create({
data: {

View File

@ -1,19 +1,27 @@
import { z } from 'zod';
import { prisma } from '@documenso/prisma';
import { DocumentSource, type Prisma } from '@documenso/prisma/client';
import { getDocumentWhereInput } from './get-document-by-id';
export interface DuplicateDocumentByIdOptions {
export interface DuplicateDocumentOptions {
documentId: number;
userId: number;
teamId?: number;
}
export const duplicateDocumentById = async ({
export const ZDuplicateDocumentResponseSchema = z.object({
documentId: z.number(),
});
export type TDuplicateDocumentResponse = z.infer<typeof ZDuplicateDocumentResponseSchema>;
export const duplicateDocument = async ({
documentId,
userId,
teamId,
}: DuplicateDocumentByIdOptions) => {
}: DuplicateDocumentOptions): Promise<TDuplicateDocumentResponse> => {
const documentWhereInput = await getDocumentWhereInput({
documentId,
userId,
@ -78,5 +86,7 @@ export const duplicateDocumentById = async ({
const createdDocument = await prisma.document.create(createDocumentArguments);
return createdDocument.id;
return {
documentId: createdDocument.id,
};
};

View File

@ -1,5 +1,6 @@
import { DateTime } from 'luxon';
import { match } from 'ts-pattern';
import type { z } from 'zod';
import { prisma } from '@documenso/prisma';
import type {
@ -11,10 +12,16 @@ import type {
User,
} from '@documenso/prisma/client';
import { RecipientRole, SigningStatus, TeamMemberRole } from '@documenso/prisma/client';
import {
DocumentSchema,
RecipientSchema,
TeamSchema,
UserSchema,
} from '@documenso/prisma/generated/zod';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
import { DocumentVisibility } from '../../types/document-visibility';
import type { FindResultResponse } from '../../types/search-params';
import { type FindResultResponse, ZFindResultResponse } from '../../types/search-params';
import { maskRecipientTokensForDocument } from '../../utils/mask-recipient-tokens-for-document';
export type PeriodSelectorValue = '' | '7d' | '14d' | '30d';
@ -36,6 +43,23 @@ export type FindDocumentsOptions = {
query?: string;
};
export const ZFindDocumentsResponseSchema = ZFindResultResponse.extend({
data: DocumentSchema.extend({
User: UserSchema.pick({
id: true,
name: true,
email: true,
}),
Recipient: RecipientSchema.array(),
team: TeamSchema.pick({
id: true,
url: true,
}).nullable(),
}).array(), // Todo: openapi remap.
});
export type TFindDocumentsResponse = z.infer<typeof ZFindDocumentsResponseSchema>;
export const findDocuments = async ({
userId,
teamId,
@ -48,7 +72,7 @@ export const findDocuments = async ({
period,
senderIds,
query,
}: FindDocumentsOptions) => {
}: FindDocumentsOptions): Promise<TFindDocumentsResponse> => {
const user = await prisma.user.findFirstOrThrow({
where: {
id: userId,

View File

@ -1,6 +1,15 @@
import { prisma } from '@documenso/prisma';
import type { DocumentWithDetails } from '@documenso/prisma/types/document';
import type { z } from 'zod';
import { prisma } from '@documenso/prisma';
import {
DocumentDataSchema,
DocumentMetaSchema,
DocumentSchema,
FieldSchema,
RecipientSchema,
} from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error';
import { getDocumentWhereInput } from './get-document-by-id';
export type GetDocumentWithDetailsByIdOptions = {
@ -9,18 +18,29 @@ export type GetDocumentWithDetailsByIdOptions = {
teamId?: number;
};
export const ZGetDocumentWithDetailsByIdResponseSchema = DocumentSchema.extend({
documentData: DocumentDataSchema,
documentMeta: DocumentMetaSchema.nullable(),
Recipient: RecipientSchema.array(),
Field: FieldSchema.array(),
});
export type TGetDocumentWithDetailsByIdResponse = z.infer<
typeof ZGetDocumentWithDetailsByIdResponseSchema
>;
export const getDocumentWithDetailsById = async ({
documentId,
userId,
teamId,
}: GetDocumentWithDetailsByIdOptions): Promise<DocumentWithDetails> => {
}: GetDocumentWithDetailsByIdOptions): Promise<TGetDocumentWithDetailsByIdResponse> => {
const documentWhereInput = await getDocumentWhereInput({
documentId,
userId,
teamId,
});
return await prisma.document.findFirstOrThrow({
const document = await prisma.document.findFirst({
where: documentWhereInput,
include: {
documentData: true,
@ -29,4 +49,12 @@ export const getDocumentWithDetailsById = async ({
Field: true,
},
});
if (!document) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Document not found',
});
}
return document;
};

View File

@ -1,7 +1,9 @@
import { TRPCError } from '@trpc/server';
import type { z } from 'zod';
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { prisma } from '@documenso/prisma';
import { DocumentSchema } from '@documenso/prisma/generated/zod';
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
@ -13,12 +15,16 @@ export type MoveDocumentToTeamOptions = {
requestMetadata?: RequestMetadata;
};
export const ZMoveDocumentToTeamResponseSchema = DocumentSchema;
export type TMoveDocumentToTeamResponse = z.infer<typeof ZMoveDocumentToTeamResponseSchema>;
export const moveDocumentToTeam = async ({
documentId,
teamId,
userId,
requestMetadata,
}: MoveDocumentToTeamOptions) => {
}: MoveDocumentToTeamOptions): Promise<TMoveDocumentToTeamResponse> => {
return await prisma.$transaction(async (tx) => {
const user = await tx.user.findUniqueOrThrow({
where: { id: userId },

View File

@ -38,7 +38,7 @@ export const resendDocument = async ({
recipients,
teamId,
requestMetadata,
}: ResendDocumentOptions) => {
}: ResendDocumentOptions): Promise<void> => {
const user = await prisma.user.findFirstOrThrow({
where: {
id: userId,

View File

@ -18,6 +18,7 @@ import { ZWebhookDocumentSchema } from '../../types/webhook-payload';
import type { RequestMetadata } from '../../universal/extract-request-metadata';
import { getFile } from '../../universal/upload/get-file';
import { putPdfFile } from '../../universal/upload/put-file';
import { fieldsContainUnsignedRequiredField } from '../../utils/advanced-fields-helpers';
import { getCertificatePdf } from '../htmltopdf/get-certificate-pdf';
import { flattenAnnotations } from '../pdf/flatten-annotations';
import { flattenForm } from '../pdf/flatten-form';
@ -92,8 +93,8 @@ export const sealDocument = async ({
},
});
if (fields.some((field) => !field.inserted)) {
throw new Error(`Document ${document.id} has unsigned fields`);
if (fieldsContainUnsignedRequiredField(fields)) {
throw new Error(`Document ${document.id} has unsigned required fields`);
}
if (isResealing) {

View File

@ -1,3 +1,5 @@
import type { z } from 'zod';
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
@ -11,6 +13,11 @@ import {
SigningStatus,
WebhookTriggerEvents,
} from '@documenso/prisma/client';
import {
DocumentMetaSchema,
DocumentSchema,
RecipientSchema,
} from '@documenso/prisma/generated/zod';
import { jobs } from '../../jobs/client';
import { extractDerivedDocumentEmailSettings } from '../../types/document-email';
@ -27,13 +34,20 @@ export type SendDocumentOptions = {
requestMetadata?: RequestMetadata;
};
export const ZSendDocumentResponseSchema = DocumentSchema.extend({
documentMeta: DocumentMetaSchema.nullable(),
Recipient: RecipientSchema.array(),
});
export type TSendDocumentResponse = z.infer<typeof ZSendDocumentResponseSchema>;
export const sendDocument = async ({
documentId,
userId,
teamId,
sendEmail,
requestMetadata,
}: SendDocumentOptions) => {
}: SendDocumentOptions): Promise<TSendDocumentResponse> => {
const user = await prisma.user.findFirstOrThrow({
where: {
id: userId,
@ -211,6 +225,7 @@ export const sendDocument = async ({
id: documentId,
},
include: {
documentMeta: true,
Recipient: true,
},
});

View File

@ -1,6 +1,7 @@
'use server';
import { match } from 'ts-pattern';
import type { z } from 'zod';
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
@ -10,6 +11,7 @@ import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-
import { prisma } from '@documenso/prisma';
import { DocumentVisibility } from '@documenso/prisma/client';
import { DocumentStatus, TeamMemberRole } from '@documenso/prisma/client';
import { DocumentSchema } from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error';
import type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../types/document-auth';
@ -29,13 +31,17 @@ export type UpdateDocumentSettingsOptions = {
requestMetadata?: RequestMetadata;
};
export const ZUpdateDocumentSettingsResponseSchema = DocumentSchema;
export type TUpdateDocumentSettingsResponse = z.infer<typeof ZUpdateDocumentSettingsResponseSchema>;
export const updateDocumentSettings = async ({
userId,
teamId,
documentId,
data,
requestMetadata,
}: UpdateDocumentSettingsOptions) => {
}: UpdateDocumentSettingsOptions): Promise<TUpdateDocumentSettingsResponse> => {
if (!data.title && !data.globalAccessAuth && !data.globalActionAuth) {
throw new AppError(AppErrorCode.INVALID_BODY, {
message: 'Missing data to update',
@ -85,39 +91,43 @@ export const updateDocumentSettings = async ({
if (teamId) {
const currentUserRole = document.team?.members[0]?.role;
const isDocumentOwner = document.userId === userId;
const requestedVisibility = data.visibility;
match(currentUserRole)
.with(TeamMemberRole.ADMIN, () => true)
.with(TeamMemberRole.MANAGER, () => {
const allowedVisibilities: DocumentVisibility[] = [
DocumentVisibility.EVERYONE,
DocumentVisibility.MANAGER_AND_ABOVE,
];
if (!isDocumentOwner) {
match(currentUserRole)
.with(TeamMemberRole.ADMIN, () => true)
.with(TeamMemberRole.MANAGER, () => {
const allowedVisibilities: DocumentVisibility[] = [
DocumentVisibility.EVERYONE,
DocumentVisibility.MANAGER_AND_ABOVE,
];
if (
!allowedVisibilities.includes(document.visibility) ||
(data.visibility && !allowedVisibilities.includes(data.visibility))
) {
if (
!allowedVisibilities.includes(document.visibility) ||
(requestedVisibility && !allowedVisibilities.includes(requestedVisibility))
) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'You do not have permission to update the document visibility',
});
}
})
.with(TeamMemberRole.MEMBER, () => {
if (
document.visibility !== DocumentVisibility.EVERYONE ||
(requestedVisibility && requestedVisibility !== DocumentVisibility.EVERYONE)
) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'You do not have permission to update the document visibility',
});
}
})
.otherwise(() => {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'You do not have permission to update the document visibility',
message: 'You do not have permission to update the document',
});
}
})
.with(TeamMemberRole.MEMBER, () => {
if (
document.visibility !== DocumentVisibility.EVERYONE ||
(data.visibility && data.visibility !== DocumentVisibility.EVERYONE)
) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'You do not have permission to update the document visibility',
});
}
})
.otherwise(() => {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'You do not have permission to update the document',
});
});
}
}
const { documentAuthOption } = extractDocumentAuthMethods({

View File

@ -1,4 +1,9 @@
import type { z } from 'zod';
import { prisma } from '@documenso/prisma';
import { FieldSchema } from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error';
export type GetFieldByIdOptions = {
userId: number;
@ -8,13 +13,17 @@ export type GetFieldByIdOptions = {
templateId?: number;
};
export const ZGetFieldByIdResponseSchema = FieldSchema;
export type TGetFieldByIdResponse = z.infer<typeof ZGetFieldByIdResponseSchema>;
export const getFieldById = async ({
userId,
teamId,
fieldId,
documentId,
templateId,
}: GetFieldByIdOptions) => {
}: GetFieldByIdOptions): Promise<TGetFieldByIdResponse> => {
const field = await prisma.field.findFirst({
where: {
id: fieldId,
@ -45,5 +54,11 @@ export const getFieldById = async ({
},
});
if (!field) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Field not found',
});
}
return field;
};

View File

@ -1,4 +1,5 @@
import { isDeepEqual } from 'remeda';
import { z } from 'zod';
import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
@ -23,6 +24,7 @@ import {
import { prisma } from '@documenso/prisma';
import type { Field } from '@documenso/prisma/client';
import { FieldType } from '@documenso/prisma/client';
import { FieldSchema } from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error';
import { canRecipientFieldsBeModified } from '../../utils/recipients';
@ -34,12 +36,18 @@ export interface SetFieldsForDocumentOptions {
requestMetadata?: RequestMetadata;
}
export const ZSetFieldsForDocumentResponseSchema = z.object({
fields: z.array(FieldSchema),
});
export type TSetFieldsForDocumentResponse = z.infer<typeof ZSetFieldsForDocumentResponseSchema>;
export const setFieldsForDocument = async ({
userId,
documentId,
fields,
requestMetadata,
}: SetFieldsForDocumentOptions): Promise<Field[]> => {
}: SetFieldsForDocumentOptions): Promise<TSetFieldsForDocumentResponse> => {
const document = await prisma.document.findFirst({
where: {
id: documentId,
@ -75,11 +83,15 @@ export const setFieldsForDocument = async ({
});
if (!document) {
throw new Error('Document not found');
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Document not found',
});
}
if (document.completedAt) {
throw new Error('Document already complete');
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message: 'Document already complete',
});
}
const existingFields = await prisma.field.findMany({
@ -335,7 +347,9 @@ export const setFieldsForDocument = async ({
return !isRemoved && !isUpdated;
});
return [...filteredFields, ...persistedFields];
return {
fields: [...filteredFields, ...persistedFields],
};
};
/**

View File

@ -1,3 +1,5 @@
import { z } from 'zod';
import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
@ -14,6 +16,7 @@ import {
} from '@documenso/lib/types/field-meta';
import { prisma } from '@documenso/prisma';
import { FieldType } from '@documenso/prisma/client';
import { FieldSchema } from '@documenso/prisma/generated/zod';
export type SetFieldsForTemplateOptions = {
userId: number;
@ -31,11 +34,17 @@ export type SetFieldsForTemplateOptions = {
}[];
};
export const ZSetFieldsForTemplateResponseSchema = z.object({
fields: z.array(FieldSchema),
});
export type TSetFieldsForTemplateResponse = z.infer<typeof ZSetFieldsForTemplateResponseSchema>;
export const setFieldsForTemplate = async ({
userId,
templateId,
fields,
}: SetFieldsForTemplateOptions) => {
}: SetFieldsForTemplateOptions): Promise<TSetFieldsForTemplateResponse> => {
const template = await prisma.template.findFirst({
where: {
id: templateId,
@ -206,5 +215,7 @@ export const setFieldsForTemplate = async ({
return !isRemoved && !isUpdated;
});
return [...filteredFields, ...persistedFields];
return {
fields: [...filteredFields, ...persistedFields],
};
};

View File

@ -8,6 +8,7 @@ import { validateDropdownField } from '@documenso/lib/advanced-fields-validation
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
import { validateRadioField } from '@documenso/lib/advanced-fields-validation/validate-radio';
import { validateTextField } from '@documenso/lib/advanced-fields-validation/validate-text';
import { fromCheckboxValue } from '@documenso/lib/universal/field-checkbox';
import { prisma } from '@documenso/prisma';
import { DocumentStatus, FieldType, SigningStatus } from '@documenso/prisma/client';
@ -119,7 +120,8 @@ export const signFieldWithToken = async ({
if (field.type === FieldType.CHECKBOX && field.fieldMeta) {
const checkboxFieldParsedMeta = ZCheckboxFieldMeta.parse(field.fieldMeta);
const checkboxFieldValues = value.split(',');
const checkboxFieldValues: string[] = fromCheckboxValue(value);
const errors = validateCheckboxField(checkboxFieldValues, checkboxFieldParsedMeta, true);
if (errors.length > 0) {

View File

@ -16,7 +16,6 @@ import {
export const removeOptionalContentGroups = (document: PDFDocument) => {
const context = document.context;
const catalog = context.lookup(context.trailerInfo.Root);
if (catalog instanceof PDFDict) {
catalog.delete(PDFName.of('OCProperties'));
}

View File

@ -10,6 +10,7 @@ import {
MIN_HANDWRITING_FONT_SIZE,
MIN_STANDARD_FONT_SIZE,
} from '@documenso/lib/constants/pdf';
import { fromCheckboxValue } from '@documenso/lib/universal/field-checkbox';
import { FieldType } from '@documenso/prisma/client';
import { isSignatureFieldType } from '@documenso/prisma/guards/is-signature-field';
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
@ -194,7 +195,7 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
value: item.value.length > 0 ? item.value : `empty-value-${item.id}`,
}));
const selected = field.customText.split(',');
const selected: string[] = fromCheckboxValue(field.customText);
for (const [index, item] of (values ?? []).entries()) {
const offsetY = index * 16;

View File

@ -0,0 +1,18 @@
import { PDFDocument } from 'pdf-lib';
import { flattenAnnotations } from './flatten-annotations';
import { flattenForm, removeOptionalContentGroups } from './flatten-form';
export const normalizePdf = async (pdf: Buffer) => {
const pdfDoc = await PDFDocument.load(pdf).catch(() => null);
if (!pdfDoc) {
return pdf;
}
removeOptionalContentGroups(pdfDoc);
flattenForm(pdfDoc);
flattenAnnotations(pdfDoc);
return Buffer.from(await pdfDoc.save());
};

View File

@ -0,0 +1,21 @@
import { prisma } from '@documenso/prisma';
export type GetRecipientByIdOptions = {
id: number;
documentId: number;
};
export const getRecipientByIdV1Api = async ({ documentId, id }: GetRecipientByIdOptions) => {
const recipient = await prisma.recipient.findFirst({
where: {
documentId,
id,
},
});
if (!recipient) {
throw new Error('Recipient not found');
}
return recipient;
};

View File

@ -1,20 +1,54 @@
import { prisma } from '@documenso/prisma';
import { AppError, AppErrorCode } from '../../errors/app-error';
export type GetRecipientByIdOptions = {
id: number;
documentId: number;
recipientId: number;
userId: number;
teamId?: number;
};
export const getRecipientById = async ({ documentId, id }: GetRecipientByIdOptions) => {
/**
* Get a recipient by ID. This will also return the recipient signing token so
* be careful when using this.
*/
export const getRecipientById = async ({
recipientId,
userId,
teamId,
}: GetRecipientByIdOptions) => {
const recipient = await prisma.recipient.findFirst({
where: {
documentId,
id,
id: recipientId,
Document: {
OR: [
teamId === undefined
? {
userId,
teamId: null,
}
: {
teamId,
team: {
members: {
some: {
userId,
},
},
},
},
],
},
},
include: {
Field: true,
},
});
if (!recipient) {
throw new Error('Recipient not found');
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Recipient not found',
});
}
return recipient;

View File

@ -45,7 +45,7 @@ export const setRecipientsForDocument = async ({
documentId,
recipients,
requestMetadata,
}: SetRecipientsForDocumentOptions): Promise<Recipient[]> => {
}: SetRecipientsForDocumentOptions) => {
const document = await prisma.document.findFirst({
where: {
id: documentId,
@ -344,7 +344,9 @@ export const setRecipientsForDocument = async ({
return !isRemoved && !isUpdated;
});
return [...filteredRecipients, ...persistedRecipients];
return {
recipients: [...filteredRecipients, ...persistedRecipients],
};
};
/**

View File

@ -220,5 +220,7 @@ export const setRecipientsForTemplate = async ({
return !isRemoved && !isUpdated;
});
return [...filteredRecipients, ...persistedRecipients];
return {
recipients: [...filteredRecipients, ...persistedRecipients],
};
};

View File

@ -1,3 +1,5 @@
import type { z } from 'zod';
import { nanoid } from '@documenso/lib/universal/id';
import { prisma } from '@documenso/prisma';
import type { DocumentDistributionMethod } from '@documenso/prisma/client';
@ -11,6 +13,11 @@ import {
SigningStatus,
WebhookTriggerEvents,
} from '@documenso/prisma/client';
import {
DocumentDataSchema,
DocumentSchema,
RecipientSchema,
} from '@documenso/prisma/generated/zod';
import type { SupportedLanguageCodes } from '../../constants/i18n';
import { AppError, AppErrorCode } from '../../errors/app-error';
@ -36,10 +43,6 @@ type FinalRecipient = Pick<
fields: Field[];
};
export type CreateDocumentFromTemplateResponse = Awaited<
ReturnType<typeof createDocumentFromTemplate>
>;
export type CreateDocumentFromTemplateOptions = {
templateId: number;
externalId?: string | null;
@ -72,6 +75,15 @@ export type CreateDocumentFromTemplateOptions = {
requestMetadata?: RequestMetadata;
};
export const ZCreateDocumentFromTemplateResponseSchema = DocumentSchema.extend({
documentData: DocumentDataSchema,
Recipient: RecipientSchema.array(),
});
export type TCreateDocumentFromTemplateResponse = z.infer<
typeof ZCreateDocumentFromTemplateResponseSchema
>;
export const createDocumentFromTemplate = async ({
templateId,
externalId,
@ -80,7 +92,7 @@ export const createDocumentFromTemplate = async ({
recipients,
override,
requestMetadata,
}: CreateDocumentFromTemplateOptions) => {
}: CreateDocumentFromTemplateOptions): Promise<TCreateDocumentFromTemplateResponse> => {
const user = await prisma.user.findFirstOrThrow({
where: {
id: userId,

View File

@ -7,7 +7,7 @@ import {
DIRECT_TEMPLATE_RECIPIENT_NAME,
} from '@documenso/lib/constants/direct-templates';
import { prisma } from '@documenso/prisma';
import type { Recipient, TemplateDirectLink } from '@documenso/prisma/client';
import type { Recipient } from '@documenso/prisma/client';
import { AppError, AppErrorCode } from '../../errors/app-error';
@ -21,7 +21,7 @@ export const createTemplateDirectLink = async ({
templateId,
userId,
directRecipientId,
}: CreateTemplateDirectLinkOptions): Promise<TemplateDirectLink> => {
}: CreateTemplateDirectLinkOptions) => {
const template = await prisma.template.findFirst({
where: {
id: templateId,

View File

@ -1,7 +1,7 @@
import { prisma } from '@documenso/prisma';
import type { Prisma, Template } from '@documenso/prisma/client';
import type { FindResultResponse } from '../../types/search-params';
import { type FindResultResponse } from '../../types/search-params';
export type FindTemplatesOptions = {
userId: number;
@ -11,9 +11,6 @@ export type FindTemplatesOptions = {
perPage?: number;
};
export type FindTemplatesResponse = Awaited<ReturnType<typeof findTemplates>>;
export type FindTemplateRow = FindTemplatesResponse['data'][number];
export const findTemplates = async ({
userId,
teamId,

View File

@ -1,16 +1,5 @@
import type { z } from 'zod';
import { prisma } from '@documenso/prisma';
import type { Prisma } from '@documenso/prisma/client';
import {
DocumentDataSchema,
FieldSchema,
RecipientSchema,
TemplateDirectLinkSchema,
TemplateMetaSchema,
TemplateSchema,
UserSchema,
} from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error';
@ -20,26 +9,7 @@ export type GetTemplateByIdOptions = {
teamId?: number;
};
export const ZGetTemplateByIdResponseSchema = TemplateSchema.extend({
directLink: TemplateDirectLinkSchema.nullable(),
templateDocumentData: DocumentDataSchema,
templateMeta: TemplateMetaSchema.nullable(),
Recipient: RecipientSchema.array(),
Field: FieldSchema.array(),
User: UserSchema.pick({
id: true,
name: true,
email: true,
}),
});
export type TGetTemplateByIdResponse = z.infer<typeof ZGetTemplateByIdResponseSchema>;
export const getTemplateById = async ({
id,
userId,
teamId,
}: GetTemplateByIdOptions): Promise<TGetTemplateByIdResponse> => {
export const getTemplateById = async ({ id, userId, teamId }: GetTemplateByIdOptions) => {
const whereFilter: Prisma.TemplateWhereInput = {
id,
OR:

View File

@ -1,7 +1,7 @@
import { TRPCError } from '@trpc/server';
import { prisma } from '@documenso/prisma';
import { AppError, AppErrorCode } from '../../errors/app-error';
export type MoveTemplateToTeamOptions = {
templateId: number;
teamId: number;
@ -23,8 +23,7 @@ export const moveTemplateToTeam = async ({
});
if (!template) {
throw new TRPCError({
code: 'NOT_FOUND',
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Template not found or already associated with a team.',
});
}
@ -41,9 +40,8 @@ export const moveTemplateToTeam = async ({
});
if (!team) {
throw new TRPCError({
code: 'FORBIDDEN',
message: 'You are not a member of this team.',
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'Team does not exist or you are not a member of this team.',
});
}

View File

@ -0,0 +1,69 @@
import { AppError } from '@documenso/lib/errors/app-error';
import { prisma } from '@documenso/prisma';
export type DisableUserOptions = {
id: number;
};
export const disableUser = async ({ id }: DisableUserOptions) => {
const user = await prisma.user.findFirst({
where: {
id,
},
include: {
ApiToken: true,
Webhooks: true,
passkeys: true,
VerificationToken: true,
PasswordResetToken: true,
},
});
if (!user) {
throw new AppError('There was an error disabling the user');
}
try {
await prisma.$transaction(async (tx) => {
await tx.user.update({
where: { id },
data: { disabled: true },
});
await tx.apiToken.updateMany({
where: { userId: id },
data: {
expires: new Date(),
},
});
await tx.webhook.updateMany({
where: { userId: id },
data: {
enabled: false,
},
});
await tx.verificationToken.updateMany({
where: { userId: id },
data: {
expires: new Date(),
},
});
await tx.passwordResetToken.updateMany({
where: { userId: id },
data: {
expiry: new Date(),
},
});
await tx.passkey.deleteMany({
where: { userId: id },
});
});
} catch (error) {
console.error('Error disabling user', error);
throw error;
}
};

View File

@ -0,0 +1,27 @@
import { AppError } from '@documenso/lib/errors/app-error';
import { prisma } from '@documenso/prisma';
export type EnableUserOptions = {
id: number;
};
export const enableUser = async ({ id }: EnableUserOptions) => {
const user = await prisma.user.findFirst({
where: {
id,
},
});
if (!user) {
throw new AppError('There was an error enabling the user');
}
await prisma.user.update({
where: {
id,
},
data: {
disabled: false,
},
});
};

View File

@ -30,11 +30,11 @@ msgstr "„{documentName}“ wurde unterschrieben"
msgid "“{documentName}” was signed by all signers"
msgstr "„{documentName}“ wurde von allen Unterzeichnern signiert"
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:137
#: packages/lib/jobs/definitions/emails/send-signing-email.handler.ts:125
msgid "{0} has invited you to {recipientActionVerb} the document \"{1}\"."
msgstr "{0} hat Sie eingeladen, das Dokument \"{1}\" {recipientActionVerb}."
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:130
#: packages/lib/jobs/definitions/emails/send-signing-email.handler.ts:118
msgid "{0} invited you to {recipientActionVerb} a document"
msgstr "{0} hat dich eingeladen, ein Dokument {recipientActionVerb}"
@ -50,7 +50,7 @@ msgstr "{0} hat das Team {teamName} bei Documenso verlassen"
msgid "{0} of {1} row(s) selected."
msgstr "{0} von {1} Zeile(n) ausgewählt."
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:136
#: packages/lib/jobs/definitions/emails/send-signing-email.handler.ts:124
#: packages/lib/server-only/document/resend-document.tsx:137
msgid "{0} on behalf of \"{1}\" has invited you to {recipientActionVerb} the document \"{2}\"."
msgstr ""
@ -195,6 +195,22 @@ msgstr "{recipientName} {action} ein Dokument, indem Sie einen Ihrer direkten Li
msgid "{recipientName} has rejected the document '{documentName}'"
msgstr "{recipientName} hat das Dokument '{documentName}' abgelehnt"
#: packages/email/template-components/template-document-recipient-signed.tsx:49
msgid "{recipientReference} has completed signing the document."
msgstr ""
#: packages/lib/jobs/definitions/emails/send-recipient-signed-email.ts:121
msgid "{recipientReference} has signed \"{0}\""
msgstr ""
#: packages/email/template-components/template-document-recipient-signed.tsx:43
msgid "{recipientReference} has signed \"{documentName}\""
msgstr ""
#: packages/email/templates/document-recipient-signed.tsx:27
msgid "{recipientReference} has signed {documentName}"
msgstr ""
#: packages/email/template-components/template-document-rejected.tsx:25
msgid "{signerName} has rejected the document \"{documentName}\"."
msgstr "{signerName} hat das Dokument \"{documentName}\" abgelehnt."
@ -289,7 +305,7 @@ msgstr "<0>Konto erforderlich</0> - Der Empfänger muss angemeldet sein, um das
msgid "<0>Require passkey</0> - The recipient must have an account and passkey configured via their settings"
msgstr "<0>Passkey erforderlich</0> - Der Empfänger muss ein Konto haben und den Passkey über seine Einstellungen konfiguriert haben"
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:122
#: packages/lib/jobs/definitions/emails/send-signing-email.handler.ts:110
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}."
@ -305,7 +321,7 @@ msgstr "Ein Feld wurde entfernt"
msgid "A field was updated"
msgstr "Ein Feld wurde aktualisiert"
#: packages/lib/jobs/definitions/emails/send-team-member-joined-email.ts:107
#: packages/lib/jobs/definitions/emails/send-team-member-joined-email.handler.ts:98
msgid "A new member has joined your team"
msgstr "Ein neues Mitglied ist deinem Team beigetreten"
@ -329,7 +345,7 @@ msgstr "Eine Anfrage zur Verwendung Ihrer E-Mail wurde von {0} auf Documenso ini
msgid "A team member has joined a team on Documenso"
msgstr "Ein Teammitglied ist einem Team bei Documenso beigetreten"
#: packages/lib/jobs/definitions/emails/send-team-member-left-email.ts:96
#: packages/lib/jobs/definitions/emails/send-team-member-left-email.handler.ts:87
msgid "A team member has left {0}"
msgstr "Ein Teammitglied hat {0} verlassen"
@ -364,12 +380,12 @@ msgstr "Übertragungsanfrage des Teams auf Documenso annehmen"
msgid "Add a document"
msgstr "Dokument hinzufügen"
#: packages/ui/primitives/document-flow/add-settings.tsx:378
#: packages/ui/primitives/document-flow/add-settings.tsx:390
#: packages/ui/primitives/template-flow/add-template-settings.tsx:468
msgid "Add a URL to redirect the user to once the document is signed"
msgstr "Fügen Sie eine URL hinzu, um den Benutzer nach der Unterzeichnung des Dokuments weiterzuleiten"
#: packages/ui/primitives/document-flow/add-settings.tsx:290
#: packages/ui/primitives/document-flow/add-settings.tsx:302
msgid "Add an external ID to the document. This can be used to identify the document in external systems."
msgstr "Fügen Sie dem Dokument eine externe ID hinzu. Diese kann verwendet werden, um das Dokument in externen Systemen zu identifizieren."
@ -414,13 +430,13 @@ msgstr "Text zum Feld hinzufügen"
msgid "Admin"
msgstr "Admin"
#: packages/ui/primitives/document-flow/add-settings.tsx:272
#: packages/ui/primitives/document-flow/add-settings.tsx:284
#: packages/ui/primitives/template-flow/add-template-settings.tsx:367
msgid "Advanced Options"
msgstr "Erweiterte Optionen"
#: packages/ui/primitives/document-flow/add-fields.tsx:576
#: packages/ui/primitives/template-flow/add-template-fields.tsx:414
#: packages/ui/primitives/document-flow/add-fields.tsx:577
#: packages/ui/primitives/template-flow/add-template-fields.tsx:415
msgid "Advanced settings"
msgstr "Erweiterte Einstellungen"
@ -472,11 +488,11 @@ msgstr "Genehmigung"
msgid "Before you get started, please confirm your email address by clicking the button below:"
msgstr "Bitte bestätige vor dem Start deine E-Mail-Adresse, indem du auf den Button unten klickst:"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:524
#: packages/ui/primitives/signature-pad/signature-pad.tsx:531
msgid "Black"
msgstr "Schwarz"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:538
#: packages/ui/primitives/signature-pad/signature-pad.tsx:545
msgid "Blue"
msgstr "Blau"
@ -492,7 +508,7 @@ msgstr "Durch die Annahme dieser Anfrage gewähren Sie <0>{teamName}</0> Zugriff
msgid "By accepting this request, you will take responsibility for any billing items associated with this team."
msgstr "Indem du diese Anfrage annimmst, übernimmst du die Verantwortung für alle Abrechnungspunkte, die mit diesem Team verbunden sind."
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:356
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:357
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:58
msgid "Cancel"
msgstr "Abbrechen"
@ -534,7 +550,7 @@ msgstr "Checkbox-Werte"
msgid "Clear filters"
msgstr "Filter löschen"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:558
#: packages/ui/primitives/signature-pad/signature-pad.tsx:565
msgid "Clear Signature"
msgstr "Unterschrift löschen"
@ -547,6 +563,7 @@ msgid "Close"
msgstr "Schließen"
#: packages/email/template-components/template-document-completed.tsx:35
#: packages/email/template-components/template-document-recipient-signed.tsx:37
#: packages/email/template-components/template-document-self-signed.tsx:36
#: packages/lib/constants/document.ts:10
msgid "Completed"
@ -561,8 +578,8 @@ msgstr "Abgeschlossenes Dokument"
msgid "Configure Direct Recipient"
msgstr "Direkten Empfänger konfigurieren"
#: packages/ui/primitives/document-flow/add-fields.tsx:577
#: packages/ui/primitives/template-flow/add-template-fields.tsx:415
#: packages/ui/primitives/document-flow/add-fields.tsx:578
#: packages/ui/primitives/template-flow/add-template-fields.tsx:416
msgid "Configure the {0} field"
msgstr "Konfigurieren Sie das Feld {0}"
@ -619,13 +636,13 @@ msgstr "Konto erstellen"
msgid "Custom Text"
msgstr "Benutzerdefinierter Text"
#: packages/ui/primitives/document-flow/add-fields.tsx:934
#: packages/ui/primitives/document-flow/add-fields.tsx:938
#: packages/ui/primitives/document-flow/types.ts:53
#: packages/ui/primitives/template-flow/add-template-fields.tsx:729
#: packages/ui/primitives/template-flow/add-template-fields.tsx:733
msgid "Date"
msgstr "Datum"
#: packages/ui/primitives/document-flow/add-settings.tsx:313
#: packages/ui/primitives/document-flow/add-settings.tsx:325
#: packages/ui/primitives/template-flow/add-template-settings.tsx:408
msgid "Date Format"
msgstr "Datumsformat"
@ -642,16 +659,16 @@ msgstr "Hast du keinen Passwortwechsel angefordert? Wir helfen dir, dein Konto a
msgid "Direct link receiver"
msgstr "Empfänger des direkten Links"
#: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:149
#: packages/lib/jobs/definitions/emails/send-rejection-emails.handler.ts:140
msgid "Document \"{0}\" - Rejected by {1}"
msgstr "Dokument \"{0}\" - Abgelehnt von {1}"
#: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:109
#: packages/lib/jobs/definitions/emails/send-rejection-emails.handler.ts:100
msgid "Document \"{0}\" - Rejection Confirmed"
msgstr "Dokument \"{0}\" - Ablehnung Bestätigt"
#: packages/ui/components/document/document-global-auth-access-select.tsx:62
#: packages/ui/primitives/document-flow/add-settings.tsx:216
#: packages/ui/primitives/document-flow/add-settings.tsx:227
#: packages/ui/primitives/template-flow/add-template-settings.tsx:202
msgid "Document access"
msgstr "Dokumentenzugriff"
@ -670,7 +687,8 @@ msgstr "Dokument storniert"
msgid "Document completed"
msgstr "Dokument abgeschlossen"
#: packages/ui/components/document/document-email-checkboxes.tsx:168
#: packages/ui/components/document/document-email-checkboxes.tsx:208
#: packages/ui/components/document/document-email-checkboxes.tsx:286
msgid "Document completed email"
msgstr "E-Mail zum Abschluss des Dokuments"
@ -679,7 +697,7 @@ msgid "Document created"
msgstr "Dokument erstellt"
#: packages/email/templates/document-created-from-direct-template.tsx:32
#: packages/lib/server-only/template/create-document-from-direct-template.ts:574
#: packages/lib/server-only/template/create-document-from-direct-template.ts:585
msgid "Document created from direct template"
msgstr "Dokument erstellt aus direkter Vorlage"
@ -691,7 +709,7 @@ msgstr "Dokumenterstellung"
msgid "Document deleted"
msgstr "Dokument gelöscht"
#: packages/ui/components/document/document-email-checkboxes.tsx:207
#: packages/ui/components/document/document-email-checkboxes.tsx:247
msgid "Document deleted email"
msgstr "E-Mail zum Löschen des Dokuments"
@ -716,7 +734,7 @@ msgstr "Dokument ins Team verschoben"
msgid "Document opened"
msgstr "Dokument geöffnet"
#: packages/ui/components/document/document-email-checkboxes.tsx:128
#: packages/ui/components/document/document-email-checkboxes.tsx:168
msgid "Document pending email"
msgstr "E-Mail über ausstehende Dokumente"
@ -757,8 +775,8 @@ msgstr "Entwurf"
msgid "Drag & drop your PDF here."
msgstr "Ziehen Sie Ihr PDF hierher."
#: packages/ui/primitives/document-flow/add-fields.tsx:1065
#: packages/ui/primitives/template-flow/add-template-fields.tsx:860
#: packages/ui/primitives/document-flow/add-fields.tsx:1069
#: packages/ui/primitives/template-flow/add-template-fields.tsx:864
msgid "Dropdown"
msgstr "Dropdown"
@ -767,12 +785,12 @@ msgid "Dropdown options"
msgstr "Dropdown-Optionen"
#: packages/lib/constants/document.ts:28
#: packages/ui/primitives/document-flow/add-fields.tsx:882
#: packages/ui/primitives/document-flow/add-fields.tsx:886
#: packages/ui/primitives/document-flow/add-signature.tsx:273
#: packages/ui/primitives/document-flow/add-signers.tsx:512
#: packages/ui/primitives/document-flow/add-signers.tsx:519
#: packages/ui/primitives/document-flow/types.ts:54
#: packages/ui/primitives/template-flow/add-template-fields.tsx:677
#: packages/ui/primitives/template-flow/add-template-fields.tsx:681
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:471
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:478
msgid "Email"
@ -794,7 +812,7 @@ msgstr "E-Mail erneut gesendet"
msgid "Email sent"
msgstr "E-Mail gesendet"
#: packages/ui/primitives/document-flow/add-fields.tsx:1130
#: packages/ui/primitives/document-flow/add-fields.tsx:1134
msgid "Empty field"
msgstr "Leeres Feld"
@ -807,8 +825,8 @@ msgstr "Direktlink-Signierung aktivieren"
msgid "Enable signing order"
msgstr "Aktiviere die Signaturreihenfolge"
#: packages/ui/primitives/document-flow/add-fields.tsx:802
#: packages/ui/primitives/template-flow/add-template-fields.tsx:597
#: packages/ui/primitives/document-flow/add-fields.tsx:806
#: packages/ui/primitives/template-flow/add-template-fields.tsx:601
msgid "Enable Typed Signatures"
msgstr "Aktivieren Sie getippte Unterschriften"
@ -816,17 +834,17 @@ msgstr "Aktivieren Sie getippte Unterschriften"
msgid "Enter password"
msgstr "Passwort eingeben"
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:257
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:258
#: packages/ui/primitives/pdf-viewer.tsx:166
msgid "Error"
msgstr "Fehler"
#: packages/ui/primitives/document-flow/add-settings.tsx:283
#: packages/ui/primitives/document-flow/add-settings.tsx:295
#: packages/ui/primitives/template-flow/add-template-settings.tsx:378
msgid "External ID"
msgstr "Externe ID"
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:258
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:259
msgid "Failed to save settings."
msgstr "Einstellungen konnten nicht gespeichert werden."
@ -896,7 +914,7 @@ msgstr "Globale Empfängerauthentifizierung"
msgid "Go Back"
msgstr "Zurück"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:545
#: packages/ui/primitives/signature-pad/signature-pad.tsx:552
msgid "Green"
msgstr "Grün"
@ -947,7 +965,7 @@ msgstr "Tritt {teamName} auf Documenso bei"
msgid "Label"
msgstr "Beschriftung"
#: packages/ui/primitives/document-flow/add-settings.tsx:176
#: packages/ui/primitives/document-flow/add-settings.tsx:187
#: packages/ui/primitives/template-flow/add-template-settings.tsx:162
msgid "Language"
msgstr "Sprache"
@ -983,12 +1001,12 @@ msgstr "Nachricht <0>(Optional)</0>"
msgid "Min"
msgstr "Min"
#: packages/ui/primitives/document-flow/add-fields.tsx:908
#: packages/ui/primitives/document-flow/add-fields.tsx:912
#: packages/ui/primitives/document-flow/add-signature.tsx:299
#: packages/ui/primitives/document-flow/add-signers.tsx:550
#: packages/ui/primitives/document-flow/add-signers.tsx:556
#: packages/ui/primitives/document-flow/types.ts:55
#: packages/ui/primitives/template-flow/add-template-fields.tsx:703
#: packages/ui/primitives/template-flow/add-template-fields.tsx:707
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:506
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:512
msgid "Name"
@ -1006,8 +1024,8 @@ msgstr "Muss unterzeichnen"
msgid "Needs to view"
msgstr "Muss sehen"
#: packages/ui/primitives/document-flow/add-fields.tsx:693
#: packages/ui/primitives/template-flow/add-template-fields.tsx:516
#: packages/ui/primitives/document-flow/add-fields.tsx:697
#: packages/ui/primitives/template-flow/add-template-fields.tsx:520
msgid "No recipient matching this description was found."
msgstr "Kein passender Empfänger mit dieser Beschreibung gefunden."
@ -1015,8 +1033,8 @@ msgstr "Kein passender Empfänger mit dieser Beschreibung gefunden."
msgid "No recipients"
msgstr "Keine Empfänger"
#: packages/ui/primitives/document-flow/add-fields.tsx:708
#: packages/ui/primitives/template-flow/add-template-fields.tsx:531
#: packages/ui/primitives/document-flow/add-fields.tsx:712
#: packages/ui/primitives/template-flow/add-template-fields.tsx:535
msgid "No recipients with this role"
msgstr "Keine Empfänger mit dieser Rolle"
@ -1044,9 +1062,9 @@ msgstr "Kein Wert gefunden."
msgid "None"
msgstr "Keine"
#: packages/ui/primitives/document-flow/add-fields.tsx:986
#: packages/ui/primitives/document-flow/add-fields.tsx:990
#: packages/ui/primitives/document-flow/types.ts:56
#: packages/ui/primitives/template-flow/add-template-fields.tsx:781
#: packages/ui/primitives/template-flow/add-template-fields.tsx:785
msgid "Number"
msgstr "Nummer"
@ -1112,15 +1130,15 @@ msgstr "Bitte {0} dein Dokument<0/>\"{documentName}\""
msgid "Please {action} your document {documentName}"
msgstr "Bitte {action} dein Dokument {documentName}"
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:111
#: packages/lib/jobs/definitions/emails/send-signing-email.handler.ts:99
msgid "Please {recipientActionVerb} this document"
msgstr "Bitte {recipientActionVerb} dieses Dokument"
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:125
#: packages/lib/jobs/definitions/emails/send-signing-email.handler.ts:113
msgid "Please {recipientActionVerb} this document created by your direct template"
msgstr "Bitte {recipientActionVerb} dieses Dokument, das von deiner direkten Vorlage erstellt wurde"
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:117
#: packages/lib/jobs/definitions/emails/send-signing-email.handler.ts:105
msgid "Please {recipientActionVerb} your document"
msgstr "Bitte {recipientActionVerb} dein Dokument"
@ -1167,24 +1185,28 @@ msgid "Recipient"
msgstr "Empfänger"
#: 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:269
#: packages/ui/primitives/template-flow/add-template-settings.tsx:291
msgid "Recipient action authentication"
msgstr "Empfängeraktion Authentifizierung"
#: packages/ui/components/document/document-email-checkboxes.tsx:89
#: packages/ui/components/document/document-email-checkboxes.tsx:129
msgid "Recipient removed email"
msgstr "E-Mail des entfernten Empfängers"
#: packages/ui/components/document/document-email-checkboxes.tsx:50
#: packages/ui/components/document/document-email-checkboxes.tsx:51
msgid "Recipient signed email"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx:90
msgid "Recipient signing request email"
msgstr "E-Mail zur Unterzeichnungsanfrage des Empfängers"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:531
#: packages/ui/primitives/signature-pad/signature-pad.tsx:538
msgid "Red"
msgstr "Rot"
#: packages/ui/primitives/document-flow/add-settings.tsx:371
#: packages/ui/primitives/document-flow/add-settings.tsx:383
#: packages/ui/primitives/template-flow/add-template-settings.tsx:461
msgid "Redirect URL"
msgstr "Weiterleitungs-URL"
@ -1217,7 +1239,7 @@ msgstr "Erinnerung: Bitte {recipientActionVerb} dieses Dokument"
msgid "Reminder: Please {recipientActionVerb} your document"
msgstr "Erinnerung: Bitte {recipientActionVerb} dein Dokument"
#: packages/ui/primitives/document-flow/add-fields.tsx:1117
#: packages/ui/primitives/document-flow/add-fields.tsx:1121
msgid "Remove"
msgstr "Entfernen"
@ -1245,11 +1267,11 @@ msgstr "Seien Sie versichert, Ihr Dokument ist streng vertraulich und wird niema
msgid "Rows per page"
msgstr "Zeilen pro Seite"
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:355
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:356
msgid "Save"
msgstr "Speichern"
#: packages/ui/primitives/template-flow/add-template-fields.tsx:893
#: packages/ui/primitives/template-flow/add-template-fields.tsx:897
msgid "Save Template"
msgstr "Vorlage speichern"
@ -1285,15 +1307,19 @@ msgstr "Senden"
msgid "Send Document"
msgstr "Dokument senden"
#: packages/ui/components/document/document-email-checkboxes.tsx:158
#: packages/ui/components/document/document-email-checkboxes.tsx:198
msgid "Send document completed email"
msgstr "E-Mail über den Abschluss des Dokuments senden"
#: packages/ui/components/document/document-email-checkboxes.tsx:197
#: packages/ui/components/document/document-email-checkboxes.tsx:276
msgid "Send document completed email to the owner"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx:237
msgid "Send document deleted email"
msgstr "E-Mail über das Löschen des Dokuments senden"
#: packages/ui/components/document/document-email-checkboxes.tsx:118
#: packages/ui/components/document/document-email-checkboxes.tsx:158
msgid "Send document pending email"
msgstr "E-Mail über ausstehende Dokumente senden"
@ -1301,11 +1327,15 @@ msgstr "E-Mail über ausstehende Dokumente senden"
msgid "Send documents on behalf of the team using the email address"
msgstr "Dokumente im Namen des Teams über die E-Mail-Adresse senden"
#: packages/ui/components/document/document-email-checkboxes.tsx:79
#: packages/ui/components/document/document-email-checkboxes.tsx:119
msgid "Send recipient removed email"
msgstr "E-Mail über entfernten Empfänger senden"
#: packages/ui/components/document/document-email-checkboxes.tsx:40
#: packages/ui/components/document/document-email-checkboxes.tsx:41
msgid "Send recipient signed email"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx:80
msgid "Send recipient signing request email"
msgstr "E-Mail über Unterzeichnungsanfrage des Empfängers senden"
@ -1338,11 +1368,11 @@ msgstr "Dokument signieren"
msgid "Sign In"
msgstr "Anmelden"
#: packages/ui/primitives/document-flow/add-fields.tsx:830
#: packages/ui/primitives/document-flow/add-fields.tsx:834
#: packages/ui/primitives/document-flow/add-signature.tsx:324
#: packages/ui/primitives/document-flow/field-icon.tsx:52
#: packages/ui/primitives/document-flow/types.ts:49
#: packages/ui/primitives/template-flow/add-template-fields.tsx:625
#: packages/ui/primitives/template-flow/add-template-fields.tsx:629
msgid "Signature"
msgstr "Unterschrift"
@ -1366,8 +1396,8 @@ msgstr "Unterzeichner müssen eindeutige E-Mails haben"
msgid "Signing"
msgstr "Unterzeichnung"
#: packages/lib/server-only/document/send-completed-email.ts:114
#: packages/lib/server-only/document/send-completed-email.ts:194
#: packages/lib/server-only/document/send-completed-email.ts:119
#: packages/lib/server-only/document/send-completed-email.ts:199
msgid "Signing Complete!"
msgstr "Unterzeichnung abgeschlossen!"
@ -1421,9 +1451,9 @@ msgstr "Team-E-Mail für {teamName} auf Documenso entfernt"
msgid "Template title"
msgstr "Vorlagentitel"
#: packages/ui/primitives/document-flow/add-fields.tsx:960
#: packages/ui/primitives/document-flow/add-fields.tsx:964
#: packages/ui/primitives/document-flow/types.ts:52
#: packages/ui/primitives/template-flow/add-template-fields.tsx:755
#: packages/ui/primitives/template-flow/add-template-fields.tsx:759
msgid "Text"
msgstr "Text"
@ -1515,7 +1545,7 @@ msgstr "Dies kann überschrieben werden, indem die Authentifizierungsanforderung
msgid "This document can not be recovered, if you would like to dispute the reason for future documents please contact support."
msgstr "Dieses Dokument kann nicht wiederhergestellt werden. Wenn du den Grund für zukünftige Dokumente anfechten möchtest, kontaktiere bitte den Support."
#: packages/ui/primitives/document-flow/add-fields.tsx:764
#: packages/ui/primitives/document-flow/add-fields.tsx:768
msgid "This document has already been sent to this recipient. You can no longer edit this recipient."
msgstr "Dieses Dokument wurde bereits an diesen Empfänger gesendet. Sie können diesen Empfänger nicht mehr bearbeiten."
@ -1531,15 +1561,19 @@ msgstr "Dieses Dokument wurde mit <0>Documenso.</0> gesendet"
msgid "This email confirms that you have rejected the document <0>\"{documentName}\"</0> sent by {documentOwnerName}."
msgstr "Diese E-Mail bestätigt, dass Sie das Dokument <0>\"{documentName}\"</0> abgelehnt haben, das von {documentOwnerName} gesendet wurde."
#: packages/ui/components/document/document-email-checkboxes.tsx:94
#: packages/ui/components/document/document-email-checkboxes.tsx:56
msgid "This email is sent to the document owner when a recipient has signed the document."
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx:134
msgid "This email is sent to the recipient if they are removed from a pending document."
msgstr "Diese E-Mail wird an den Empfänger gesendet, wenn er von einem ausstehenden Dokument entfernt wird."
#: packages/ui/components/document/document-email-checkboxes.tsx:55
#: packages/ui/components/document/document-email-checkboxes.tsx:95
msgid "This email is sent to the recipient requesting them to sign the document."
msgstr "Diese E-Mail wird an den Empfänger gesendet und fordert ihn auf, das Dokument zu unterschreiben."
#: packages/ui/components/document/document-email-checkboxes.tsx:133
#: packages/ui/components/document/document-email-checkboxes.tsx:173
msgid "This email will be sent to the recipient who has just signed the document, if there are still other recipients who have not signed yet."
msgstr "Diese E-Mail wird an den Empfänger gesendet, der das Dokument gerade unterschrieben hat, wenn es noch andere Empfänger gibt, die noch nicht unterschrieben haben."
@ -1551,7 +1585,7 @@ msgstr "Dieses Feld kann nicht geändert oder gelöscht werden. Wenn Sie den dir
msgid "This is how the document will reach the recipients once the document is ready for signing."
msgstr "So wird das Dokument die Empfänger erreichen, sobald es zum Unterschreiben bereit ist."
#: packages/ui/primitives/document-flow/add-fields.tsx:1097
#: packages/ui/primitives/document-flow/add-fields.tsx:1101
msgid "This recipient can no longer be modified as they have signed a field, or completed the document."
msgstr "Dieser Empfänger kann nicht mehr bearbeitet werden, da er ein Feld unterschrieben oder das Dokument abgeschlossen hat."
@ -1559,29 +1593,33 @@ msgstr "Dieser Empfänger kann nicht mehr bearbeitet werden, da er ein Feld unte
msgid "This signer has already signed the document."
msgstr "Dieser Unterzeichner hat das Dokument bereits unterschrieben."
#: packages/ui/components/document/document-email-checkboxes.tsx:212
#: packages/ui/components/document/document-email-checkboxes.tsx:252
msgid "This will be sent to all recipients if a pending document has been deleted."
msgstr "Dies wird an alle Empfänger gesendet, wenn ein ausstehendes Dokument gelöscht wurde."
#: packages/ui/components/document/document-email-checkboxes.tsx:173
#: packages/ui/components/document/document-email-checkboxes.tsx:213
msgid "This will be sent to all recipients once the document has been fully completed."
msgstr "Dies wird an alle Empfänger gesendet, sobald das Dokument vollständig abgeschlossen ist."
#: packages/ui/components/document/document-email-checkboxes.tsx:291
msgid "This will be sent to the document owner once the document has been fully completed."
msgstr ""
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:48
msgid "This will override any global settings."
msgstr "Dies überschreibt alle globalen Einstellungen."
#: packages/ui/primitives/document-flow/add-settings.tsx:347
#: packages/ui/primitives/document-flow/add-settings.tsx:359
#: packages/ui/primitives/template-flow/add-template-settings.tsx:438
msgid "Time Zone"
msgstr "Zeitzone"
#: packages/ui/primitives/document-flow/add-settings.tsx:155
#: packages/ui/primitives/document-flow/add-settings.tsx:166
msgid "Title"
msgstr "Titel"
#: packages/ui/primitives/document-flow/add-fields.tsx:1080
#: packages/ui/primitives/template-flow/add-template-fields.tsx:873
#: packages/ui/primitives/document-flow/add-fields.tsx:1084
#: packages/ui/primitives/template-flow/add-template-fields.tsx:877
msgid "To proceed further, please set at least one value for the {0} field."
msgstr "Um fortzufahren, legen Sie bitte mindestens einen Wert für das Feld {0} fest."
@ -1597,7 +1635,7 @@ msgstr "Aktualisieren Sie die Rolle und fügen Sie Felder nach Bedarf für den d
msgid "Upgrade"
msgstr "Upgrade"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:509
#: packages/ui/primitives/signature-pad/signature-pad.tsx:516
msgid "Upload Signature"
msgstr ""
@ -1726,7 +1764,7 @@ msgstr "Du wurdest eingeladen, {0} auf Documenso beizutreten"
msgid "You have been invited to join the following team"
msgstr "Du wurdest eingeladen, dem folgenden Team beizutreten"
#: packages/lib/server-only/recipient/set-recipients-for-document.ts:327
#: packages/lib/server-only/recipient/set-recipients-for-document.ts:337
msgid "You have been removed from a document"
msgstr "Du wurdest von einem Dokument entfernt"
@ -1734,7 +1772,7 @@ msgstr "Du wurdest von einem Dokument entfernt"
msgid "You have been requested to take ownership of team {0} on Documenso"
msgstr "Du wurdest gebeten, das Team {0} auf Documenso zu übernehmen"
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:115
#: packages/lib/jobs/definitions/emails/send-signing-email.handler.ts:103
#: packages/lib/server-only/document/resend-document.tsx:125
msgid "You have initiated the document {0} that requires you to {recipientActionVerb} it."
msgstr "Du hast das Dokument {0} initiiert, das erfordert, dass du {recipientActionVerb}."

View File

@ -92,11 +92,7 @@ msgstr "{0} Empfänger(in)"
msgid "{charactersRemaining, plural, one {1 character remaining} other {{charactersRemaining} characters remaining}}"
msgstr "{charactersRemaining, plural, one {1 Zeichen verbleibend} other {{charactersRemaining} Zeichen verbleibend}}"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:55
msgid "{formattedTeamMemberQuanity} • Monthly • Renews: {formattedDate}"
msgstr "{formattedTeamMemberQuanity} • Monatlich • Erneuert: {formattedDate}"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:48
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:59
msgid "{numberOfSeats, plural, one {# member} other {# members}}"
msgstr "{numberOfSeats, plural, one {# Mitglied} other {# Mitglieder}}"
@ -254,21 +250,21 @@ msgid "Acknowledgment"
msgstr "Bestätigung"
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-dropdown.tsx:108
#: apps/web/src/app/(dashboard)/documents/[id]/logs/document-logs-data-table.tsx:100
#: apps/web/src/app/(dashboard)/documents/[id]/logs/document-logs-data-table.tsx:98
#: apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx:123
#: apps/web/src/app/(dashboard)/settings/public-profile/public-templates-data-table.tsx:164
#: apps/web/src/app/(dashboard)/settings/security/activity/user-security-activity-data-table.tsx:118
#: apps/web/src/app/(dashboard)/settings/security/activity/user-security-activity-data-table.tsx:116
#: apps/web/src/app/(internal)/%5F%5Fhtmltopdf/audit-log/data-table.tsx:46
msgid "Action"
msgstr "Aktion"
#: apps/web/src/app/(dashboard)/documents/data-table.tsx:85
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:181
#: apps/web/src/app/(dashboard)/documents/data-table.tsx:79
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:177
#: apps/web/src/app/(dashboard)/templates/data-table-templates.tsx:140
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:133
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:142
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:118
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:127
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:131
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:140
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:116
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:125
msgid "Actions"
msgstr "Aktionen"
@ -491,7 +487,7 @@ msgstr "Ein Fehler ist aufgetreten, während die Teammitglieder geladen wurden.
msgid "An error occurred while moving the document."
msgstr "Ein Fehler ist aufgetreten, während das Dokument verschoben wurde."
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:57
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:65
msgid "An error occurred while moving the template."
msgstr "Ein Fehler ist aufgetreten, während die Vorlage verschoben wurde."
@ -499,7 +495,7 @@ msgstr "Ein Fehler ist aufgetreten, während die Vorlage verschoben wurde."
msgid "An error occurred while removing the field."
msgstr "Ein Fehler ist beim Entfernen des Feldes aufgetreten."
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:152
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:154
#: apps/web/src/app/(signing)/sign/[token]/date-field.tsx:126
#: apps/web/src/app/(signing)/sign/[token]/dropdown-field.tsx:137
#: apps/web/src/app/(signing)/sign/[token]/email-field.tsx:110
@ -522,7 +518,7 @@ msgstr "Ein Fehler ist aufgetreten, während das Dokument gesendet wurde."
msgid "An error occurred while sending your confirmation email"
msgstr "Beim Senden Ihrer Bestätigungs-E-Mail ist ein Fehler aufgetreten"
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:123
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:125
#: apps/web/src/app/(signing)/sign/[token]/date-field.tsx:100
#: apps/web/src/app/(signing)/sign/[token]/dropdown-field.tsx:106
#: apps/web/src/app/(signing)/sign/[token]/email-field.tsx:84
@ -544,7 +540,7 @@ msgstr "Ein Fehler ist aufgetreten, während versucht wurde, eine Checkout-Sitzu
msgid "An error occurred while updating the document settings."
msgstr "Ein Fehler ist aufgetreten, während die Dokumenteinstellungen aktualisiert wurden."
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:213
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:215
msgid "An error occurred while updating the signature."
msgstr "Ein Fehler ist aufgetreten, während die Unterschrift aktualisiert wurde."
@ -578,11 +574,11 @@ msgstr "Ein Fehler ist aufgetreten, während dein Dokument hochgeladen wurde."
#: apps/web/src/components/forms/profile.tsx:87
#: apps/web/src/components/forms/public-profile-claim-dialog.tsx:113
#: apps/web/src/components/forms/public-profile-form.tsx:104
#: apps/web/src/components/forms/signin.tsx:248
#: apps/web/src/components/forms/signin.tsx:256
#: apps/web/src/components/forms/signin.tsx:270
#: apps/web/src/components/forms/signin.tsx:285
#: apps/web/src/components/forms/signin.tsx:301
#: apps/web/src/components/forms/signin.tsx:249
#: apps/web/src/components/forms/signin.tsx:257
#: apps/web/src/components/forms/signin.tsx:271
#: apps/web/src/components/forms/signin.tsx:286
#: apps/web/src/components/forms/signin.tsx:302
#: apps/web/src/components/forms/signup.tsx:124
#: apps/web/src/components/forms/signup.tsx:138
#: apps/web/src/components/forms/token.tsx:143
@ -597,11 +593,11 @@ msgstr "Es ist ein unbekannter Fehler aufgetreten"
msgid "Any payment methods attached to this team will remain attached to this team. Please contact us if you need to update this information."
msgstr "Alle Zahlungsmethoden, die mit diesem Team verbunden sind, bleiben diesem Team zugeordnet. Bitte kontaktiere uns, wenn du diese Informationen aktualisieren möchtest."
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:225
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:221
msgid "Any Source"
msgstr "Jede Quelle"
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:205
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:201
msgid "Any Status"
msgstr "Jeder Status"
@ -704,7 +700,7 @@ msgid "Background Color"
msgstr "Hintergrundfarbe"
#: apps/web/src/components/forms/2fa/disable-authenticator-app-dialog.tsx:167
#: apps/web/src/components/forms/signin.tsx:485
#: apps/web/src/components/forms/signin.tsx:486
msgid "Backup Code"
msgstr "Backup-Code"
@ -721,7 +717,7 @@ msgid "Basic details"
msgstr "Basisdetails"
#: apps/web/src/app/(dashboard)/settings/billing/page.tsx:74
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:61
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:71
#: apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx:117
#: apps/web/src/components/(dashboard)/settings/layout/mobile-nav.tsx:120
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:123
@ -737,7 +733,7 @@ msgstr "Markenpräferenzen"
msgid "Branding preferences updated"
msgstr "Markenpräferenzen aktualisiert"
#: apps/web/src/app/(dashboard)/settings/security/activity/user-security-activity-data-table.tsx:99
#: apps/web/src/app/(dashboard)/settings/security/activity/user-security-activity-data-table.tsx:97
#: apps/web/src/app/(internal)/%5F%5Fhtmltopdf/audit-log/data-table.tsx:48
msgid "Browser"
msgstr "Browser"
@ -780,7 +776,7 @@ msgstr "Durch die Verwendung der elektronischen Unterschriftsfunktion stimmen Si
#: apps/web/src/app/(dashboard)/settings/teams/team-email-usage.tsx:109
#: apps/web/src/app/(dashboard)/templates/delete-template-dialog.tsx:81
#: apps/web/src/app/(dashboard)/templates/duplicate-template-dialog.tsx:78
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:119
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:131
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:472
#: apps/web/src/app/(signing)/sign/[token]/auto-sign.tsx:220
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-2fa.tsx:178
@ -864,9 +860,9 @@ msgstr "Benutzername jetzt beanspruchen"
msgid "Click here to get started"
msgstr "Klicken Sie hier, um zu beginnen"
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-recent-activity.tsx:78
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-recent-activity.tsx:76
#: apps/web/src/app/(dashboard)/settings/public-profile/public-templates-data-table.tsx:118
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:68
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:66
#: apps/web/src/components/document/document-history-sheet.tsx:133
msgid "Click here to retry"
msgstr "Klicken Sie hier, um es erneut zu versuchen"
@ -919,7 +915,7 @@ msgstr "Unterzeichnung abschließen"
msgid "Complete Viewing"
msgstr "Betrachten abschließen"
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:208
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:204
#: apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx:77
#: apps/web/src/components/formatter/document-status.tsx:28
msgid "Completed"
@ -1149,9 +1145,9 @@ msgstr "Erstellen Sie Ihr Konto und beginnen Sie mit dem modernen Dokumentensign
#: apps/web/src/app/(dashboard)/admin/documents/document-results.tsx:62
#: apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx:96
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-information.tsx:35
#: apps/web/src/app/(dashboard)/documents/data-table.tsx:54
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table.tsx:65
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:109
#: apps/web/src/app/(dashboard)/documents/data-table.tsx:48
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table.tsx:63
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:105
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-information.tsx:34
#: apps/web/src/app/(dashboard)/templates/data-table-templates.tsx:56
#: apps/web/src/components/templates/manage-public-template-dialog.tsx:274
@ -1168,7 +1164,7 @@ msgid "Created by"
msgstr "Erstellt von"
#: apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx:48
#: apps/web/src/components/(teams)/tables/pending-user-teams-data-table.tsx:78
#: apps/web/src/components/(teams)/tables/pending-user-teams-data-table.tsx:76
msgid "Created on"
msgstr "Erstellt am"
@ -1187,10 +1183,6 @@ msgstr "Aktuelles Passwort"
msgid "Current password is incorrect."
msgstr ""
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:69
msgid "Current plan: {0}"
msgstr "Aktueller Plan: {0}"
#: apps/web/src/app/(dashboard)/settings/billing/billing-plans.tsx:28
msgid "Daily"
msgstr "Täglich"
@ -1199,7 +1191,7 @@ msgstr "Täglich"
msgid "Dark Mode"
msgstr "Dunkelmodus"
#: apps/web/src/app/(dashboard)/settings/security/activity/user-security-activity-data-table.tsx:70
#: apps/web/src/app/(dashboard)/settings/security/activity/user-security-activity-data-table.tsx:68
#: apps/web/src/app/(signing)/sign/[token]/date-field.tsx:148
msgid "Date"
msgstr "Datum"
@ -1319,7 +1311,7 @@ msgstr "Konto wird gelöscht..."
msgid "Details"
msgstr "Einzelheiten"
#: apps/web/src/app/(dashboard)/settings/security/activity/user-security-activity-data-table.tsx:75
#: apps/web/src/app/(dashboard)/settings/security/activity/user-security-activity-data-table.tsx:73
#: apps/web/src/app/(internal)/%5F%5Fhtmltopdf/certificate/page.tsx:242
msgid "Device"
msgstr "Gerät"
@ -1334,8 +1326,8 @@ msgstr "Direkter Link"
msgid "Direct link"
msgstr "Direkter Link"
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:160
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:231
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:156
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:227
msgid "Direct Link"
msgstr "Direkter Link"
@ -1439,11 +1431,11 @@ msgstr "Dokument abgeschlossen!"
msgid "Document created"
msgstr "Dokument erstellt"
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:129
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:127
msgid "Document created by <0>{0}</0>"
msgstr "Dokument erstellt von <0>{0}</0>"
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:134
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:132
msgid "Document created using a <0>direct link</0>"
msgstr "Dokument erstellt mit einem <0>direkten Link</0>"
@ -1598,7 +1590,7 @@ msgstr "Auditprotokolle herunterladen"
msgid "Download Certificate"
msgstr "Zertifikat herunterladen"
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:214
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:210
#: apps/web/src/components/formatter/document-status.tsx:34
msgid "Draft"
msgstr "Entwurf"
@ -1668,7 +1660,7 @@ msgstr "Offenlegung der elektronischen Unterschrift"
#: apps/web/src/components/(teams)/dialogs/update-team-email-dialog.tsx:153
#: apps/web/src/components/forms/forgot-password.tsx:81
#: apps/web/src/components/forms/profile.tsx:122
#: apps/web/src/components/forms/signin.tsx:338
#: apps/web/src/components/forms/signin.tsx:339
#: apps/web/src/components/forms/signup.tsx:176
msgid "Email"
msgstr "E-Mail"
@ -1778,12 +1770,12 @@ msgstr "Geben Sie hier Ihren Text ein"
#: apps/web/src/app/(dashboard)/templates/[id]/edit/edit-template.tsx:217
#: apps/web/src/app/(dashboard)/templates/[id]/edit/edit-template.tsx:256
#: apps/web/src/app/(dashboard)/templates/duplicate-template-dialog.tsx:51
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:56
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:68
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:175
#: apps/web/src/app/(signing)/sign/[token]/auto-sign.tsx:152
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:122
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:151
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:212
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:124
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:153
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:214
#: apps/web/src/app/(signing)/sign/[token]/date-field.tsx:99
#: apps/web/src/app/(signing)/sign/[token]/date-field.tsx:125
#: apps/web/src/app/(signing)/sign/[token]/dropdown-field.tsx:105
@ -1859,7 +1851,7 @@ msgid "For any questions regarding this disclosure, electronic signatures, or an
msgstr "Für Fragen zu dieser Offenlegung, elektronischen Unterschriften oder einem verwandten Verfahren kontaktieren Sie uns bitte unter: <0>{SUPPORT_EMAIL}</0>"
#: apps/web/src/app/(unauthenticated)/forgot-password/page.tsx:21
#: apps/web/src/components/forms/signin.tsx:370
#: apps/web/src/components/forms/signin.tsx:371
msgid "Forgot your password?"
msgstr "Haben Sie Ihr Passwort vergessen?"
@ -2039,11 +2031,11 @@ msgstr "Einladung akzeptiert!"
msgid "Invitation declined"
msgstr "Einladung abgelehnt"
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:80
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:78
msgid "Invitation has been deleted"
msgstr "Einladung wurde gelöscht"
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:63
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:61
msgid "Invitation has been resent"
msgstr "Einladung wurde erneut gesendet"
@ -2063,7 +2055,7 @@ msgstr "Mitglieder einladen"
msgid "Invite team members"
msgstr "Teammitglieder einladen"
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:128
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:126
msgid "Invited At"
msgstr "Eingeladen am"
@ -2133,7 +2125,7 @@ msgstr "Zuletzt aktualisiert"
msgid "Last updated at"
msgstr "Zuletzt aktualisiert am"
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table.tsx:71
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table.tsx:69
msgid "Last used"
msgstr "Zuletzt verwendet"
@ -2142,7 +2134,7 @@ msgid "Leaderboard"
msgstr ""
#: apps/web/src/components/(teams)/dialogs/leave-team-dialog.tsx:111
#: apps/web/src/components/(teams)/tables/current-user-teams-data-table.tsx:117
#: apps/web/src/components/(teams)/tables/current-user-teams-data-table.tsx:115
msgid "Leave"
msgstr "Verlassen"
@ -2175,7 +2167,7 @@ msgstr "Links generiert"
msgid "Listening to {0}"
msgstr "Anhören von {0}"
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-recent-activity.tsx:100
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-recent-activity.tsx:98
msgid "Load older activity"
msgstr "Ältere Aktivitäten laden"
@ -2190,11 +2182,11 @@ msgid "Loading Document..."
msgstr "Dokument wird geladen..."
#: apps/web/src/app/(dashboard)/documents/move-document-dialog.tsx:92
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:91
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:103
msgid "Loading teams..."
msgstr "Teams werden geladen..."
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-recent-activity.tsx:100
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-recent-activity.tsx:98
msgid "Loading..."
msgstr "Wird geladen..."
@ -2204,7 +2196,7 @@ msgstr "Wird geladen..."
msgid "Login"
msgstr "Anmelden"
#: apps/web/src/components/(teams)/tables/current-user-teams-data-table.tsx:101
#: apps/web/src/components/(teams)/tables/current-user-teams-data-table.tsx:99
msgid "Manage"
msgstr "Verwalten"
@ -2252,7 +2244,7 @@ msgstr "Abonnement verwalten"
msgid "Manage subscriptions"
msgstr "Abonnements verwalten"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:81
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:87
msgid "Manage team subscription."
msgstr "Teamabonnement verwalten."
@ -2292,8 +2284,8 @@ msgstr "MAU (erstellt Dokument)"
msgid "MAU (had document completed)"
msgstr "MAU (hat Dokument abgeschlossen)"
#: apps/web/src/components/(teams)/tables/current-user-teams-data-table.tsx:90
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:113
#: apps/web/src/components/(teams)/tables/current-user-teams-data-table.tsx:88
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:111
msgid "Member Since"
msgstr "Mitglied seit"
@ -2309,6 +2301,7 @@ msgid "Modify recipients"
msgstr "Empfänger ändern"
#: apps/web/src/app/(dashboard)/settings/billing/billing-plans.tsx:30
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:54
msgid "Monthly"
msgstr "Monatlich"
@ -2321,7 +2314,7 @@ msgid "Monthly Active Users: Users that had at least one of their documents comp
msgstr "Monatlich aktive Benutzer: Benutzer, die mindestens eines ihrer Dokumente abgeschlossen haben"
#: apps/web/src/app/(dashboard)/documents/move-document-dialog.tsx:123
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:122
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:134
msgid "Move"
msgstr "Verschieben"
@ -2329,7 +2322,7 @@ msgstr "Verschieben"
msgid "Move Document to Team"
msgstr "Dokument in Team verschieben"
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:77
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:89
msgid "Move Template to Team"
msgstr "Vorlage in Team verschieben"
@ -2339,7 +2332,7 @@ msgid "Move to Team"
msgstr "In Team verschieben"
#: apps/web/src/app/(dashboard)/documents/move-document-dialog.tsx:123
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:122
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:134
msgid "Moving..."
msgstr "Verschieben..."
@ -2352,7 +2345,7 @@ msgstr "Meine Vorlagen"
#: apps/web/src/app/(dashboard)/admin/users/[id]/page.tsx:99
#: apps/web/src/app/(dashboard)/admin/users/data-table-users.tsx:66
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table-actions.tsx:144
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table.tsx:61
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table.tsx:59
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:287
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:294
#: apps/web/src/app/(signing)/sign/[token]/complete/claim-account.tsx:118
@ -2367,7 +2360,7 @@ msgstr "Name"
msgid "Need to sign documents?"
msgstr "Müssen Dokumente signieren?"
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table.tsx:76
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table.tsx:74
msgid "Never"
msgstr "Niemals"
@ -2402,7 +2395,7 @@ msgstr "Keine aktiven Entwürfe"
msgid "No further action is required from you at this time."
msgstr "Es sind derzeit keine weiteren Maßnahmen Ihrerseits erforderlich."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:42
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:43
msgid "No payment required"
msgstr "Keine Zahlung erforderlich"
@ -2410,11 +2403,11 @@ msgstr "Keine Zahlung erforderlich"
msgid "No public profile templates found"
msgstr "Keine Vorlagen für das öffentliche Profil gefunden"
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-recent-activity.tsx:108
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-recent-activity.tsx:106
msgid "No recent activity"
msgstr "Keine aktuellen Aktivitäten"
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:103
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:101
msgid "No recent documents"
msgstr "Keine aktuellen Dokumente"
@ -2449,7 +2442,7 @@ msgstr "Kein Wert gefunden."
msgid "No worries, it happens! Enter your email and we'll email you a special link to reset your password."
msgstr "Keine Sorge, das passiert! Geben Sie Ihre E-Mail ein, und wir senden Ihnen einen speziellen Link zum Zurücksetzen Ihres Passworts."
#: apps/web/src/components/forms/signin.tsx:160
#: apps/web/src/components/forms/signin.tsx:161
msgid "Not supported"
msgstr "Nicht unterstützt"
@ -2535,7 +2528,7 @@ msgstr "Geöffnet"
msgid "Or"
msgstr "Oder"
#: apps/web/src/components/forms/signin.tsx:390
#: apps/web/src/components/forms/signin.tsx:391
msgid "Or continue with"
msgstr "Oder fahren Sie fort mit"
@ -2546,8 +2539,8 @@ msgstr "Andernfalls wird das Dokument als Entwurf erstellt."
#: apps/web/src/app/(dashboard)/admin/documents/document-results.tsx:86
#: apps/web/src/app/(internal)/%5F%5Fhtmltopdf/audit-log/page.tsx:103
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:81
#: apps/web/src/components/(teams)/tables/current-user-teams-data-table.tsx:86
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:109
#: apps/web/src/components/(teams)/tables/current-user-teams-data-table.tsx:84
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:107
msgid "Owner"
msgstr "Besitzer"
@ -2555,7 +2548,7 @@ msgstr "Besitzer"
msgid "Paid"
msgstr "Bezahlt"
#: apps/web/src/components/forms/signin.tsx:435
#: apps/web/src/components/forms/signin.tsx:436
msgid "Passkey"
msgstr "Passkey"
@ -2592,14 +2585,14 @@ msgstr "Passkeys"
msgid "Passkeys allow you to sign in and authenticate using biometrics, password managers, etc."
msgstr "Passkeys ermöglichen das Anmelden und die Authentifizierung mit biometrischen Daten, Passwortmanagern usw."
#: apps/web/src/components/forms/signin.tsx:161
#: apps/web/src/components/forms/signin.tsx:162
msgid "Passkeys are not supported on this browser"
msgstr "Passkeys werden von diesem Browser nicht unterstützt"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:70
#: apps/web/src/components/forms/password.tsx:128
#: apps/web/src/components/forms/reset-password.tsx:115
#: apps/web/src/components/forms/signin.tsx:356
#: apps/web/src/components/forms/signin.tsx:357
#: apps/web/src/components/forms/signup.tsx:192
#: apps/web/src/components/forms/v2/signup.tsx:347
msgid "Password"
@ -2629,7 +2622,7 @@ msgid "Payment overdue"
msgstr "Zahlung überfällig"
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-recipients.tsx:131
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:211
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:207
#: apps/web/src/components/(teams)/tables/teams-member-page-data-table.tsx:82
#: apps/web/src/components/(teams)/tables/user-settings-teams-page-data-table.tsx:77
#: apps/web/src/components/document/document-read-only-fields.tsx:89
@ -2746,7 +2739,7 @@ msgstr "Bitte überprüfen Sie das Dokument vor der Unterzeichnung."
msgid "Please try again and make sure you enter the correct email address."
msgstr "Bitte versuchen Sie es erneut und stellen Sie sicher, dass Sie die korrekte E-Mail-Adresse eingeben."
#: apps/web/src/components/forms/signin.tsx:203
#: apps/web/src/components/forms/signin.tsx:204
msgid "Please try again later or login using your normal details"
msgstr "Bitte versuchen Sie es später erneut oder melden Sie sich mit Ihren normalen Daten an"
@ -2858,17 +2851,17 @@ msgstr "Der Grund muss weniger als 500 Zeichen lang sein"
msgid "Reauthentication is required to sign this field"
msgstr "Eine erneute Authentifizierung ist erforderlich, um dieses Feld zu unterschreiben"
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-recent-activity.tsx:57
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-recent-activity.tsx:55
#: apps/web/src/app/(dashboard)/settings/security/page.tsx:130
msgid "Recent activity"
msgstr "Aktuelle Aktivitäten"
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:47
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:45
msgid "Recent documents"
msgstr "Neueste Dokumente"
#: apps/web/src/app/(dashboard)/documents/data-table.tsx:69
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:120
#: apps/web/src/app/(dashboard)/documents/data-table.tsx:63
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:116
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:280
msgid "Recipient"
msgstr "Empfänger"
@ -2929,8 +2922,8 @@ msgstr "Haben Sie Ihr Passwort vergessen? <0>Einloggen</0>"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/team-email-dropdown.tsx:89
#: apps/web/src/components/(teams)/dialogs/remove-team-email-dialog.tsx:159
#: apps/web/src/components/(teams)/tables/pending-user-teams-data-table-actions.tsx:54
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:166
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:167
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:164
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:165
#: apps/web/src/components/forms/avatar-image.tsx:166
msgid "Remove"
msgstr "Entfernen"
@ -2939,10 +2932,14 @@ msgstr "Entfernen"
msgid "Remove team email"
msgstr "Team-E-Mail entfernen"
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:164
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:162
msgid "Remove team member"
msgstr "Teammitglied entfernen"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:63
msgid "Renews: {formattedDate}"
msgstr ""
#: apps/web/src/components/forms/password.tsx:144
#: apps/web/src/components/forms/reset-password.tsx:131
msgid "Repeat Password"
@ -2957,7 +2954,7 @@ msgid "Reseal document"
msgstr "Dokument wieder versiegeln"
#: apps/web/src/app/(dashboard)/documents/_action-items/resend-document.tsx:118
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:154
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:152
msgid "Resend"
msgstr "Erneut senden"
@ -3035,9 +3032,9 @@ msgstr "Zugriff widerrufen"
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:283
#: apps/web/src/components/(teams)/dialogs/invite-team-member-dialog.tsx:318
#: apps/web/src/components/(teams)/dialogs/update-team-member-dialog.tsx:163
#: apps/web/src/components/(teams)/tables/current-user-teams-data-table.tsx:82
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:123
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:105
#: apps/web/src/components/(teams)/tables/current-user-teams-data-table.tsx:80
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:121
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:103
msgid "Role"
msgstr "Rolle"
@ -3094,7 +3091,7 @@ msgid "Select"
msgstr "Auswählen"
#: apps/web/src/app/(dashboard)/documents/move-document-dialog.tsx:87
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:86
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:98
msgid "Select a team"
msgstr "Wählen Sie ein Team aus"
@ -3102,7 +3099,7 @@ msgstr "Wählen Sie ein Team aus"
msgid "Select a team to move this document to. This action cannot be undone."
msgstr "Wählen Sie ein Team aus, um dieses Dokument dorthin zu verschieben. Diese Aktion kann nicht rückgängig gemacht werden."
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:80
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:92
msgid "Select a team to move this template to. This action cannot be undone."
msgstr "Wählen Sie ein Team aus, um diese Vorlage dorthin zu verschieben. Diese Aktion kann nicht rückgängig gemacht werden."
@ -3134,7 +3131,7 @@ msgstr "Im Namen des Teams senden"
msgid "Send reminder"
msgstr "Erinnerung senden"
#: apps/web/src/app/(dashboard)/documents/data-table.tsx:65
#: apps/web/src/app/(dashboard)/documents/data-table.tsx:59
msgid "Sender"
msgstr "Absender"
@ -3233,8 +3230,8 @@ msgid "Sign Here"
msgstr "Hier unterzeichnen"
#: apps/web/src/app/not-found.tsx:29
#: apps/web/src/components/forms/signin.tsx:383
#: apps/web/src/components/forms/signin.tsx:510
#: apps/web/src/components/forms/signin.tsx:384
#: apps/web/src/components/forms/signin.tsx:511
msgid "Sign In"
msgstr "Einloggen"
@ -3319,8 +3316,8 @@ msgstr "Unterzeichnungszertifikat"
msgid "Signing certificate provided by"
msgstr "Unterzeichnungszertifikat bereitgestellt von"
#: apps/web/src/components/forms/signin.tsx:383
#: apps/web/src/components/forms/signin.tsx:510
#: apps/web/src/components/forms/signin.tsx:384
#: apps/web/src/components/forms/signin.tsx:511
msgid "Signing in..."
msgstr "Anmeldung..."
@ -3396,8 +3393,8 @@ msgstr "Website Einstellungen"
#: apps/web/src/components/(teams)/dialogs/remove-team-email-dialog.tsx:64
#: apps/web/src/components/(teams)/dialogs/remove-team-email-dialog.tsx:83
#: apps/web/src/components/(teams)/tables/pending-user-teams-data-table-actions.tsx:33
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:68
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:85
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:66
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:83
#: apps/web/src/components/(teams)/team-billing-portal-button.tsx:29
msgid "Something went wrong"
msgstr "Etwas ist schiefgelaufen"
@ -3439,7 +3436,7 @@ msgstr "Entschuldigung, wir konnten die Prüfprotokolle nicht herunterladen. Bit
msgid "Sorry, we were unable to download the certificate. Please try again later."
msgstr "Entschuldigung, wir konnten das Zertifikat nicht herunterladen. Bitte versuchen Sie es später erneut."
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:138
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:134
msgid "Source"
msgstr "Quelle"
@ -3449,8 +3446,8 @@ msgstr "Statistiken"
#: apps/web/src/app/(dashboard)/admin/documents/document-results.tsx:81
#: apps/web/src/app/(dashboard)/admin/subscriptions/page.tsx:32
#: apps/web/src/app/(dashboard)/documents/data-table.tsx:79
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:130
#: apps/web/src/app/(dashboard)/documents/data-table.tsx:73
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:126
#: apps/web/src/app/(internal)/%5F%5Fhtmltopdf/audit-log/page.tsx:93
#: apps/web/src/components/(teams)/tables/team-billing-invoices-data-table.tsx:73
msgid "Status"
@ -3492,8 +3489,8 @@ msgstr "Abonnements"
#: apps/web/src/components/(teams)/dialogs/update-team-member-dialog.tsx:92
#: apps/web/src/components/(teams)/forms/update-team-form.tsx:67
#: apps/web/src/components/(teams)/tables/pending-user-teams-data-table-actions.tsx:27
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:62
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:79
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:60
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:77
#: apps/web/src/components/forms/public-profile-form.tsx:80
#: apps/web/src/components/templates/manage-public-template-dialog.tsx:133
#: apps/web/src/components/templates/manage-public-template-dialog.tsx:170
@ -3512,8 +3509,8 @@ msgstr "Systemanforderungen"
msgid "System Theme"
msgstr "Systemthema"
#: apps/web/src/components/(teams)/tables/current-user-teams-data-table.tsx:65
#: apps/web/src/components/(teams)/tables/pending-user-teams-data-table.tsx:64
#: apps/web/src/components/(teams)/tables/current-user-teams-data-table.tsx:63
#: apps/web/src/components/(teams)/tables/pending-user-teams-data-table.tsx:62
msgid "Team"
msgstr "Team"
@ -3559,8 +3556,8 @@ msgstr "Teameinladung"
msgid "Team invitations have been sent."
msgstr "Teameinladungen wurden gesendet."
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:109
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:86
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:107
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:84
msgid "Team Member"
msgstr "Teammitglied"
@ -3634,10 +3631,10 @@ msgstr "Teams"
msgid "Teams restricted"
msgstr "Teams beschränkt"
#: apps/web/src/app/(dashboard)/templates/[id]/edit/template-edit-page-view.tsx:63
#: apps/web/src/app/(dashboard)/templates/[id]/edit/template-edit-page-view.tsx:64
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:39
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:148
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:228
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:144
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:224
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view.tsx:148
#: apps/web/src/components/(teams)/dialogs/invite-team-member-dialog.tsx:408
#: apps/web/src/components/templates/manage-public-template-dialog.tsx:271
@ -3664,10 +3661,14 @@ msgstr "Vorlage ist von Deinem öffentlichen Profil entfernt worden."
msgid "Template has been updated."
msgstr "Vorlage wurde aktualisiert."
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:48
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:50
msgid "Template moved"
msgstr "Vorlage verschoben"
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:62
msgid "Template not found or already associated with a team."
msgstr ""
#: apps/web/src/app/(dashboard)/templates/[id]/edit/edit-template.tsx:246
msgid "Template saved"
msgstr "Vorlage gespeichert"
@ -3795,7 +3796,7 @@ msgstr "Die Teamübertragungsanfrage an <0>{0}</0> ist abgelaufen."
msgid "The team you are looking for may have been removed, renamed or may have never existed."
msgstr "Das Team, das Sie suchen, wurde möglicherweise entfernt, umbenannt oder hat möglicherweise nie existiert."
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:49
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:51
msgid "The template has been successfully moved to the selected team."
msgstr "Die Vorlage wurde erfolgreich in das ausgewählte Team verschoben."
@ -3884,11 +3885,11 @@ msgstr "Dieses Dokument wurde von allen Empfängern unterschrieben"
msgid "This document is currently a draft and has not been sent"
msgstr "Dieses Dokument ist momentan ein Entwurf und wurde nicht gesendet"
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:152
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:148
msgid "This document was created by you or a team member using the template above."
msgstr "Dieses Dokument wurde von Ihnen oder einem Teammitglied unter Verwendung der oben genannten Vorlage erstellt."
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:164
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:160
msgid "This document was created using a direct link."
msgstr "Dieses Dokument wurde mit einem direkten Link erstellt."
@ -3908,7 +3909,7 @@ msgstr "Dieser Link ist ungültig oder abgelaufen. Bitte kontaktieren Sie Ihr Te
msgid "This passkey has already been registered."
msgstr "Dieser Zugangsschlüssel wurde bereits registriert."
#: apps/web/src/components/forms/signin.tsx:200
#: apps/web/src/components/forms/signin.tsx:201
msgid "This passkey is not configured for this application. Please login and add one in the user settings."
msgstr "Dieser Passkey ist für diese Anwendung nicht konfiguriert. Bitte melden Sie sich an und fügen Sie einen in den Benutzereinstellungen hinzu."
@ -3916,7 +3917,7 @@ msgstr "Dieser Passkey ist für diese Anwendung nicht konfiguriert. Bitte melden
msgid "This price includes minimum 5 seats."
msgstr "Dieser Preis beinhaltet mindestens 5 Plätze."
#: apps/web/src/components/forms/signin.tsx:202
#: apps/web/src/components/forms/signin.tsx:203
msgid "This session has expired. Please try again."
msgstr "Diese Sitzung ist abgelaufen. Bitte versuchen Sie es erneut."
@ -3949,7 +3950,7 @@ msgstr "Dieser Benutzername ist bereits vergeben"
msgid "This username is already taken"
msgstr "Dieser Benutzername ist bereits vergeben"
#: apps/web/src/app/(dashboard)/documents/[id]/logs/document-logs-data-table.tsx:73
#: apps/web/src/app/(dashboard)/documents/[id]/logs/document-logs-data-table.tsx:71
#: apps/web/src/app/(internal)/%5F%5Fhtmltopdf/audit-log/data-table.tsx:44
msgid "Time"
msgstr "Zeit"
@ -3963,8 +3964,8 @@ msgid "Time Zone"
msgstr "Zeitzone"
#: apps/web/src/app/(dashboard)/admin/documents/document-results.tsx:67
#: apps/web/src/app/(dashboard)/documents/data-table.tsx:60
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:115
#: apps/web/src/app/(dashboard)/documents/data-table.tsx:54
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:111
#: apps/web/src/app/(dashboard)/templates/data-table-templates.tsx:61
msgid "Title"
msgstr "Titel"
@ -4098,7 +4099,7 @@ msgstr "Zwei-Faktor-Authentifizierung"
msgid "Two factor authentication recovery codes are used to access your account in the event that you lose access to your authenticator app."
msgstr "Wiederherstellungscodes für die Zwei-Faktor-Authentifizierung werden verwendet, um auf Ihr Konto zuzugreifen, falls Sie den Zugang zu Ihrer Authentifizierungs-App verlieren."
#: apps/web/src/components/forms/signin.tsx:448
#: apps/web/src/components/forms/signin.tsx:449
msgid "Two-Factor Authentication"
msgstr "Zwei-Faktor-Authentifizierung"
@ -4155,7 +4156,7 @@ msgstr "Direkter Zugriff auf die Vorlage kann nicht erstellt werden. Bitte versu
msgid "Unable to decline this team invitation at this time."
msgstr "Zurzeit kann diese Teameinladung nicht abgelehnt werden."
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:86
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:84
msgid "Unable to delete invitation. Please try again."
msgstr "Einladung kann nicht gelöscht werden. Bitte versuchen Sie es erneut."
@ -4171,12 +4172,12 @@ msgstr "Zwei-Faktor-Authentifizierung kann nicht deaktiviert werden"
msgid "Unable to join this team at this time."
msgstr "Zurzeit kann diesem Team nicht beigetreten werden."
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-recent-activity.tsx:72
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-recent-activity.tsx:70
#: apps/web/src/components/document/document-history-sheet.tsx:127
msgid "Unable to load document history"
msgstr "Kann den Dokumentverlauf nicht laden"
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:62
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:60
msgid "Unable to load documents"
msgstr "Dokumente können nicht geladen werden"
@ -4192,7 +4193,7 @@ msgstr "Derzeit ist es nicht möglich, die E-Mail-Verifizierung zu entfernen. Bi
msgid "Unable to remove team email at this time. Please try again."
msgstr "Das Team-E-Mail kann zurzeit nicht entfernt werden. Bitte versuchen Sie es erneut."
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:69
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:67
msgid "Unable to resend invitation. Please try again."
msgstr "Einladung kann nicht erneut gesendet werden. Bitte versuchen Sie es erneut."
@ -4209,8 +4210,8 @@ msgstr "Passwort kann nicht zurückgesetzt werden"
msgid "Unable to setup two-factor authentication"
msgstr "Zwei-Faktor-Authentifizierung kann nicht eingerichtet werden"
#: apps/web/src/components/forms/signin.tsx:247
#: apps/web/src/components/forms/signin.tsx:255
#: apps/web/src/components/forms/signin.tsx:248
#: apps/web/src/components/forms/signin.tsx:256
msgid "Unable to sign in"
msgstr "Anmeldung nicht möglich"
@ -4227,6 +4228,7 @@ msgstr "Unvollendet"
#: apps/web/src/app/(internal)/%5F%5Fhtmltopdf/certificate/page.tsx:262
#: apps/web/src/app/(internal)/%5F%5Fhtmltopdf/certificate/page.tsx:273
#: apps/web/src/app/(internal)/%5F%5Fhtmltopdf/certificate/page.tsx:284
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:55
msgid "Unknown"
msgstr "Unbekannt"
@ -4267,7 +4269,7 @@ msgstr "Profil aktualisieren"
msgid "Update Recipient"
msgstr "Empfänger aktualisieren"
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:146
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:144
msgid "Update role"
msgstr "Rolle aktualisieren"
@ -4336,12 +4338,12 @@ msgid "Use"
msgstr "Verwenden"
#: apps/web/src/components/forms/2fa/disable-authenticator-app-dialog.tsx:187
#: apps/web/src/components/forms/signin.tsx:505
#: apps/web/src/components/forms/signin.tsx:506
msgid "Use Authenticator"
msgstr "Authenticator verwenden"
#: apps/web/src/components/forms/2fa/disable-authenticator-app-dialog.tsx:185
#: apps/web/src/components/forms/signin.tsx:503
#: apps/web/src/components/forms/signin.tsx:504
msgid "Use Backup Code"
msgstr "Backup-Code verwenden"
@ -4349,7 +4351,7 @@ msgstr "Backup-Code verwenden"
msgid "Use Template"
msgstr "Vorlage verwenden"
#: apps/web/src/app/(dashboard)/documents/[id]/logs/document-logs-data-table.tsx:78
#: apps/web/src/app/(dashboard)/documents/[id]/logs/document-logs-data-table.tsx:76
#: apps/web/src/app/(internal)/%5F%5Fhtmltopdf/audit-log/data-table.tsx:45
msgid "User"
msgstr "Benutzer"
@ -4435,7 +4437,7 @@ msgstr "Alle Dokumente anzeigen, die an Ihr Konto gesendet wurden"
msgid "View all recent security activity related to your account."
msgstr "Sehen Sie sich alle aktuellen Sicherheitsaktivitäten in Ihrem Konto an."
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:157
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:155
msgid "View all related documents"
msgstr "Alle verwandten Dokumente anzeigen"
@ -4459,7 +4461,7 @@ msgstr "Dokumente ansehen, die mit dieser E-Mail verknüpft sind"
msgid "View invites"
msgstr "Einladungen ansehen"
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:95
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:93
msgid "View more"
msgstr "Mehr anzeigen"
@ -4594,9 +4596,9 @@ msgid "We encountered an unknown error while attempting to save your details. Pl
msgstr "Wir sind auf einen unbekannten Fehler gestoßen, während wir versucht haben, Ihre Daten zu speichern. Bitte versuchen Sie es später erneut."
#: apps/web/src/components/forms/profile.tsx:89
#: apps/web/src/components/forms/signin.tsx:272
#: apps/web/src/components/forms/signin.tsx:287
#: apps/web/src/components/forms/signin.tsx:303
#: apps/web/src/components/forms/signin.tsx:273
#: apps/web/src/components/forms/signin.tsx:288
#: apps/web/src/components/forms/signin.tsx:304
msgid "We encountered an unknown error while attempting to sign you In. Please try again later."
msgstr "Wir sind auf einen unbekannten Fehler gestoßen, während wir versucht haben, Sie anzumelden. Bitte versuchen Sie es später erneut."
@ -4797,6 +4799,7 @@ msgid "Write about yourself"
msgstr "Schreiben Sie über sich selbst"
#: apps/web/src/app/(dashboard)/settings/billing/billing-plans.tsx:31
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:53
msgid "Yearly"
msgstr "Jährlich"
@ -4841,6 +4844,10 @@ msgstr "Sie aktualisieren derzeit <0>{teamMemberName}.</0>"
msgid "You are currently updating the <0>{passkeyName}</0> passkey."
msgstr "Sie aktualisieren derzeit den <0>{passkeyName}</0> Passkey."
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:64
msgid "You are not a member of this team."
msgstr ""
#: apps/web/src/app/(teams)/t/[teamUrl]/error.tsx:28
msgid "You are not authorized to view this page."
msgstr "Sie sind nicht berechtigt, diese Seite anzuzeigen."
@ -4965,7 +4972,7 @@ msgstr "Sie haben {teamMemberName} aktualisiert."
msgid "You have verified your email address for <0>{0}</0>."
msgstr "Sie haben Ihre E-Mail-Adresse für <0>{0}</0> bestätigt."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:82
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:88
msgid "You must be an admin of this team to manage billing."
msgstr "Sie müssen Administrator dieses Teams sein, um die Abrechnung zu verwalten."
@ -5119,7 +5126,7 @@ msgstr "Ihr Wiederherstellungscode wurde in die Zwischenablage kopiert."
msgid "Your recovery codes are listed below. Please store them in a safe place."
msgstr "Ihre Wiederherstellungscodes sind unten aufgeführt. Bitte bewahren Sie sie an einem sicheren Ort auf."
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:62
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:72
msgid "Your subscription is currently active."
msgstr "Ihr Abonnement ist derzeit aktiv."

View File

@ -25,11 +25,11 @@ msgstr "“{documentName}” has been signed"
msgid "“{documentName}” was signed by all signers"
msgstr "“{documentName}” was signed by all signers"
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:137
#: packages/lib/jobs/definitions/emails/send-signing-email.handler.ts:125
msgid "{0} has invited you to {recipientActionVerb} the document \"{1}\"."
msgstr "{0} has invited you to {recipientActionVerb} the document \"{1}\"."
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:130
#: packages/lib/jobs/definitions/emails/send-signing-email.handler.ts:118
msgid "{0} invited you to {recipientActionVerb} a document"
msgstr "{0} invited you to {recipientActionVerb} a document"
@ -45,7 +45,7 @@ msgstr "{0} left the team {teamName} on Documenso"
msgid "{0} of {1} row(s) selected."
msgstr "{0} of {1} row(s) selected."
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:136
#: packages/lib/jobs/definitions/emails/send-signing-email.handler.ts:124
#: packages/lib/server-only/document/resend-document.tsx:137
msgid "{0} on behalf of \"{1}\" has invited you to {recipientActionVerb} the document \"{2}\"."
msgstr "{0} on behalf of \"{1}\" has invited you to {recipientActionVerb} the document \"{2}\"."
@ -190,6 +190,22 @@ msgstr "{recipientName} {action} a document by using one of your direct links"
msgid "{recipientName} has rejected the document '{documentName}'"
msgstr "{recipientName} has rejected the document '{documentName}'"
#: packages/email/template-components/template-document-recipient-signed.tsx:49
msgid "{recipientReference} has completed signing the document."
msgstr "{recipientReference} has completed signing the document."
#: packages/lib/jobs/definitions/emails/send-recipient-signed-email.ts:121
msgid "{recipientReference} has signed \"{0}\""
msgstr "{recipientReference} has signed \"{0}\""
#: packages/email/template-components/template-document-recipient-signed.tsx:43
msgid "{recipientReference} has signed \"{documentName}\""
msgstr "{recipientReference} has signed \"{documentName}\""
#: packages/email/templates/document-recipient-signed.tsx:27
msgid "{recipientReference} has signed {documentName}"
msgstr "{recipientReference} has signed {documentName}"
#: packages/email/template-components/template-document-rejected.tsx:25
msgid "{signerName} has rejected the document \"{documentName}\"."
msgstr "{signerName} has rejected the document \"{documentName}\"."
@ -284,7 +300,7 @@ msgstr "<0>Require account</0> - The recipient must be signed in to view the doc
msgid "<0>Require passkey</0> - The recipient must have an account and passkey configured via their settings"
msgstr "<0>Require passkey</0> - The recipient must have an account and passkey configured via their settings"
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:122
#: packages/lib/jobs/definitions/emails/send-signing-email.handler.ts:110
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."
@ -300,7 +316,7 @@ msgstr "A field was removed"
msgid "A field was updated"
msgstr "A field was updated"
#: packages/lib/jobs/definitions/emails/send-team-member-joined-email.ts:107
#: packages/lib/jobs/definitions/emails/send-team-member-joined-email.handler.ts:98
msgid "A new member has joined your team"
msgstr "A new member has joined your team"
@ -324,7 +340,7 @@ msgstr "A request to use your email has been initiated by {0} on Documenso"
msgid "A team member has joined a team on Documenso"
msgstr "A team member has joined a team on Documenso"
#: packages/lib/jobs/definitions/emails/send-team-member-left-email.ts:96
#: packages/lib/jobs/definitions/emails/send-team-member-left-email.handler.ts:87
msgid "A team member has left {0}"
msgstr "A team member has left {0}"
@ -359,12 +375,12 @@ msgstr "Accept team transfer request on Documenso"
msgid "Add a document"
msgstr "Add a document"
#: packages/ui/primitives/document-flow/add-settings.tsx:378
#: packages/ui/primitives/document-flow/add-settings.tsx:390
#: packages/ui/primitives/template-flow/add-template-settings.tsx:468
msgid "Add a URL to redirect the user to once the document is signed"
msgstr "Add a URL to redirect the user to once the document is signed"
#: packages/ui/primitives/document-flow/add-settings.tsx:290
#: packages/ui/primitives/document-flow/add-settings.tsx:302
msgid "Add an external ID to the document. This can be used to identify the document in external systems."
msgstr "Add an external ID to the document. This can be used to identify the document in external systems."
@ -409,13 +425,13 @@ msgstr "Add text to the field"
msgid "Admin"
msgstr "Admin"
#: packages/ui/primitives/document-flow/add-settings.tsx:272
#: packages/ui/primitives/document-flow/add-settings.tsx:284
#: packages/ui/primitives/template-flow/add-template-settings.tsx:367
msgid "Advanced Options"
msgstr "Advanced Options"
#: packages/ui/primitives/document-flow/add-fields.tsx:576
#: packages/ui/primitives/template-flow/add-template-fields.tsx:414
#: packages/ui/primitives/document-flow/add-fields.tsx:577
#: packages/ui/primitives/template-flow/add-template-fields.tsx:415
msgid "Advanced settings"
msgstr "Advanced settings"
@ -467,11 +483,11 @@ msgstr "Approving"
msgid "Before you get started, please confirm your email address by clicking the button below:"
msgstr "Before you get started, please confirm your email address by clicking the button below:"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:524
#: packages/ui/primitives/signature-pad/signature-pad.tsx:531
msgid "Black"
msgstr "Black"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:538
#: packages/ui/primitives/signature-pad/signature-pad.tsx:545
msgid "Blue"
msgstr "Blue"
@ -487,7 +503,7 @@ msgstr "By accepting this request, you will be granting <0>{teamName}</0> access
msgid "By accepting this request, you will take responsibility for any billing items associated with this team."
msgstr "By accepting this request, you will take responsibility for any billing items associated with this team."
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:356
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:357
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:58
msgid "Cancel"
msgstr "Cancel"
@ -529,7 +545,7 @@ msgstr "Checkbox values"
msgid "Clear filters"
msgstr "Clear filters"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:558
#: packages/ui/primitives/signature-pad/signature-pad.tsx:565
msgid "Clear Signature"
msgstr "Clear Signature"
@ -542,6 +558,7 @@ msgid "Close"
msgstr "Close"
#: packages/email/template-components/template-document-completed.tsx:35
#: packages/email/template-components/template-document-recipient-signed.tsx:37
#: packages/email/template-components/template-document-self-signed.tsx:36
#: packages/lib/constants/document.ts:10
msgid "Completed"
@ -556,8 +573,8 @@ msgstr "Completed Document"
msgid "Configure Direct Recipient"
msgstr "Configure Direct Recipient"
#: packages/ui/primitives/document-flow/add-fields.tsx:577
#: packages/ui/primitives/template-flow/add-template-fields.tsx:415
#: packages/ui/primitives/document-flow/add-fields.tsx:578
#: packages/ui/primitives/template-flow/add-template-fields.tsx:416
msgid "Configure the {0} field"
msgstr "Configure the {0} field"
@ -614,13 +631,13 @@ msgstr "Create account"
msgid "Custom Text"
msgstr "Custom Text"
#: packages/ui/primitives/document-flow/add-fields.tsx:934
#: packages/ui/primitives/document-flow/add-fields.tsx:938
#: packages/ui/primitives/document-flow/types.ts:53
#: packages/ui/primitives/template-flow/add-template-fields.tsx:729
#: packages/ui/primitives/template-flow/add-template-fields.tsx:733
msgid "Date"
msgstr "Date"
#: packages/ui/primitives/document-flow/add-settings.tsx:313
#: packages/ui/primitives/document-flow/add-settings.tsx:325
#: packages/ui/primitives/template-flow/add-template-settings.tsx:408
msgid "Date Format"
msgstr "Date Format"
@ -637,16 +654,16 @@ msgstr "Didn't request a password change? We are here to help you secure your ac
msgid "Direct link receiver"
msgstr "Direct link receiver"
#: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:149
#: packages/lib/jobs/definitions/emails/send-rejection-emails.handler.ts:140
msgid "Document \"{0}\" - Rejected by {1}"
msgstr "Document \"{0}\" - Rejected by {1}"
#: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:109
#: packages/lib/jobs/definitions/emails/send-rejection-emails.handler.ts:100
msgid "Document \"{0}\" - Rejection Confirmed"
msgstr "Document \"{0}\" - Rejection Confirmed"
#: packages/ui/components/document/document-global-auth-access-select.tsx:62
#: packages/ui/primitives/document-flow/add-settings.tsx:216
#: packages/ui/primitives/document-flow/add-settings.tsx:227
#: packages/ui/primitives/template-flow/add-template-settings.tsx:202
msgid "Document access"
msgstr "Document access"
@ -665,7 +682,8 @@ msgstr "Document Cancelled"
msgid "Document completed"
msgstr "Document completed"
#: packages/ui/components/document/document-email-checkboxes.tsx:168
#: packages/ui/components/document/document-email-checkboxes.tsx:208
#: packages/ui/components/document/document-email-checkboxes.tsx:286
msgid "Document completed email"
msgstr "Document completed email"
@ -674,7 +692,7 @@ msgid "Document created"
msgstr "Document created"
#: packages/email/templates/document-created-from-direct-template.tsx:32
#: packages/lib/server-only/template/create-document-from-direct-template.ts:574
#: packages/lib/server-only/template/create-document-from-direct-template.ts:585
msgid "Document created from direct template"
msgstr "Document created from direct template"
@ -686,7 +704,7 @@ msgstr "Document Creation"
msgid "Document deleted"
msgstr "Document deleted"
#: packages/ui/components/document/document-email-checkboxes.tsx:207
#: packages/ui/components/document/document-email-checkboxes.tsx:247
msgid "Document deleted email"
msgstr "Document deleted email"
@ -711,7 +729,7 @@ msgstr "Document moved to team"
msgid "Document opened"
msgstr "Document opened"
#: packages/ui/components/document/document-email-checkboxes.tsx:128
#: packages/ui/components/document/document-email-checkboxes.tsx:168
msgid "Document pending email"
msgstr "Document pending email"
@ -752,8 +770,8 @@ msgstr "Draft"
msgid "Drag & drop your PDF here."
msgstr "Drag & drop your PDF here."
#: packages/ui/primitives/document-flow/add-fields.tsx:1065
#: packages/ui/primitives/template-flow/add-template-fields.tsx:860
#: packages/ui/primitives/document-flow/add-fields.tsx:1069
#: packages/ui/primitives/template-flow/add-template-fields.tsx:864
msgid "Dropdown"
msgstr "Dropdown"
@ -762,12 +780,12 @@ msgid "Dropdown options"
msgstr "Dropdown options"
#: packages/lib/constants/document.ts:28
#: packages/ui/primitives/document-flow/add-fields.tsx:882
#: packages/ui/primitives/document-flow/add-fields.tsx:886
#: packages/ui/primitives/document-flow/add-signature.tsx:273
#: packages/ui/primitives/document-flow/add-signers.tsx:512
#: packages/ui/primitives/document-flow/add-signers.tsx:519
#: packages/ui/primitives/document-flow/types.ts:54
#: packages/ui/primitives/template-flow/add-template-fields.tsx:677
#: packages/ui/primitives/template-flow/add-template-fields.tsx:681
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:471
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:478
msgid "Email"
@ -789,7 +807,7 @@ msgstr "Email resent"
msgid "Email sent"
msgstr "Email sent"
#: packages/ui/primitives/document-flow/add-fields.tsx:1130
#: packages/ui/primitives/document-flow/add-fields.tsx:1134
msgid "Empty field"
msgstr "Empty field"
@ -802,8 +820,8 @@ msgstr "Enable Direct Link Signing"
msgid "Enable signing order"
msgstr "Enable signing order"
#: packages/ui/primitives/document-flow/add-fields.tsx:802
#: packages/ui/primitives/template-flow/add-template-fields.tsx:597
#: packages/ui/primitives/document-flow/add-fields.tsx:806
#: packages/ui/primitives/template-flow/add-template-fields.tsx:601
msgid "Enable Typed Signatures"
msgstr "Enable Typed Signatures"
@ -811,17 +829,17 @@ msgstr "Enable Typed Signatures"
msgid "Enter password"
msgstr "Enter password"
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:257
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:258
#: packages/ui/primitives/pdf-viewer.tsx:166
msgid "Error"
msgstr "Error"
#: packages/ui/primitives/document-flow/add-settings.tsx:283
#: packages/ui/primitives/document-flow/add-settings.tsx:295
#: packages/ui/primitives/template-flow/add-template-settings.tsx:378
msgid "External ID"
msgstr "External ID"
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:258
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:259
msgid "Failed to save settings."
msgstr "Failed to save settings."
@ -891,7 +909,7 @@ msgstr "Global recipient action authentication"
msgid "Go Back"
msgstr "Go Back"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:545
#: packages/ui/primitives/signature-pad/signature-pad.tsx:552
msgid "Green"
msgstr "Green"
@ -942,7 +960,7 @@ msgstr "Join {teamName} on Documenso"
msgid "Label"
msgstr "Label"
#: packages/ui/primitives/document-flow/add-settings.tsx:176
#: packages/ui/primitives/document-flow/add-settings.tsx:187
#: packages/ui/primitives/template-flow/add-template-settings.tsx:162
msgid "Language"
msgstr "Language"
@ -978,12 +996,12 @@ msgstr "Message <0>(Optional)</0>"
msgid "Min"
msgstr "Min"
#: packages/ui/primitives/document-flow/add-fields.tsx:908
#: packages/ui/primitives/document-flow/add-fields.tsx:912
#: packages/ui/primitives/document-flow/add-signature.tsx:299
#: packages/ui/primitives/document-flow/add-signers.tsx:550
#: packages/ui/primitives/document-flow/add-signers.tsx:556
#: packages/ui/primitives/document-flow/types.ts:55
#: packages/ui/primitives/template-flow/add-template-fields.tsx:703
#: packages/ui/primitives/template-flow/add-template-fields.tsx:707
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:506
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:512
msgid "Name"
@ -1001,8 +1019,8 @@ msgstr "Needs to sign"
msgid "Needs to view"
msgstr "Needs to view"
#: packages/ui/primitives/document-flow/add-fields.tsx:693
#: packages/ui/primitives/template-flow/add-template-fields.tsx:516
#: packages/ui/primitives/document-flow/add-fields.tsx:697
#: packages/ui/primitives/template-flow/add-template-fields.tsx:520
msgid "No recipient matching this description was found."
msgstr "No recipient matching this description was found."
@ -1010,8 +1028,8 @@ msgstr "No recipient matching this description was found."
msgid "No recipients"
msgstr "No recipients"
#: packages/ui/primitives/document-flow/add-fields.tsx:708
#: packages/ui/primitives/template-flow/add-template-fields.tsx:531
#: packages/ui/primitives/document-flow/add-fields.tsx:712
#: packages/ui/primitives/template-flow/add-template-fields.tsx:535
msgid "No recipients with this role"
msgstr "No recipients with this role"
@ -1039,9 +1057,9 @@ msgstr "No value found."
msgid "None"
msgstr "None"
#: packages/ui/primitives/document-flow/add-fields.tsx:986
#: packages/ui/primitives/document-flow/add-fields.tsx:990
#: packages/ui/primitives/document-flow/types.ts:56
#: packages/ui/primitives/template-flow/add-template-fields.tsx:781
#: packages/ui/primitives/template-flow/add-template-fields.tsx:785
msgid "Number"
msgstr "Number"
@ -1107,15 +1125,15 @@ msgstr "Please {0} your document<0/>\"{documentName}\""
msgid "Please {action} your document {documentName}"
msgstr "Please {action} your document {documentName}"
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:111
#: packages/lib/jobs/definitions/emails/send-signing-email.handler.ts:99
msgid "Please {recipientActionVerb} this document"
msgstr "Please {recipientActionVerb} this document"
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:125
#: packages/lib/jobs/definitions/emails/send-signing-email.handler.ts:113
msgid "Please {recipientActionVerb} this document created by your direct template"
msgstr "Please {recipientActionVerb} this document created by your direct template"
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:117
#: packages/lib/jobs/definitions/emails/send-signing-email.handler.ts:105
msgid "Please {recipientActionVerb} your document"
msgstr "Please {recipientActionVerb} your document"
@ -1162,24 +1180,28 @@ msgid "Recipient"
msgstr "Recipient"
#: 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:269
#: packages/ui/primitives/template-flow/add-template-settings.tsx:291
msgid "Recipient action authentication"
msgstr "Recipient action authentication"
#: packages/ui/components/document/document-email-checkboxes.tsx:89
#: packages/ui/components/document/document-email-checkboxes.tsx:129
msgid "Recipient removed email"
msgstr "Recipient removed email"
#: packages/ui/components/document/document-email-checkboxes.tsx:50
#: packages/ui/components/document/document-email-checkboxes.tsx:51
msgid "Recipient signed email"
msgstr "Recipient signed email"
#: packages/ui/components/document/document-email-checkboxes.tsx:90
msgid "Recipient signing request email"
msgstr "Recipient signing request email"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:531
#: packages/ui/primitives/signature-pad/signature-pad.tsx:538
msgid "Red"
msgstr "Red"
#: packages/ui/primitives/document-flow/add-settings.tsx:371
#: packages/ui/primitives/document-flow/add-settings.tsx:383
#: packages/ui/primitives/template-flow/add-template-settings.tsx:461
msgid "Redirect URL"
msgstr "Redirect URL"
@ -1212,7 +1234,7 @@ msgstr "Reminder: Please {recipientActionVerb} this document"
msgid "Reminder: Please {recipientActionVerb} your document"
msgstr "Reminder: Please {recipientActionVerb} your document"
#: packages/ui/primitives/document-flow/add-fields.tsx:1117
#: packages/ui/primitives/document-flow/add-fields.tsx:1121
msgid "Remove"
msgstr "Remove"
@ -1240,11 +1262,11 @@ msgstr "Rest assured, your document is strictly confidential and will never be s
msgid "Rows per page"
msgstr "Rows per page"
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:355
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:356
msgid "Save"
msgstr "Save"
#: packages/ui/primitives/template-flow/add-template-fields.tsx:893
#: packages/ui/primitives/template-flow/add-template-fields.tsx:897
msgid "Save Template"
msgstr "Save Template"
@ -1280,15 +1302,19 @@ msgstr "Send"
msgid "Send Document"
msgstr "Send Document"
#: packages/ui/components/document/document-email-checkboxes.tsx:158
#: packages/ui/components/document/document-email-checkboxes.tsx:198
msgid "Send document completed email"
msgstr "Send document completed email"
#: packages/ui/components/document/document-email-checkboxes.tsx:197
#: packages/ui/components/document/document-email-checkboxes.tsx:276
msgid "Send document completed email to the owner"
msgstr "Send document completed email to the owner"
#: packages/ui/components/document/document-email-checkboxes.tsx:237
msgid "Send document deleted email"
msgstr "Send document deleted email"
#: packages/ui/components/document/document-email-checkboxes.tsx:118
#: packages/ui/components/document/document-email-checkboxes.tsx:158
msgid "Send document pending email"
msgstr "Send document pending email"
@ -1296,11 +1322,15 @@ msgstr "Send document pending email"
msgid "Send documents on behalf of the team using the email address"
msgstr "Send documents on behalf of the team using the email address"
#: packages/ui/components/document/document-email-checkboxes.tsx:79
#: packages/ui/components/document/document-email-checkboxes.tsx:119
msgid "Send recipient removed email"
msgstr "Send recipient removed email"
#: packages/ui/components/document/document-email-checkboxes.tsx:40
#: packages/ui/components/document/document-email-checkboxes.tsx:41
msgid "Send recipient signed email"
msgstr "Send recipient signed email"
#: packages/ui/components/document/document-email-checkboxes.tsx:80
msgid "Send recipient signing request email"
msgstr "Send recipient signing request email"
@ -1333,11 +1363,11 @@ msgstr "Sign Document"
msgid "Sign In"
msgstr "Sign In"
#: packages/ui/primitives/document-flow/add-fields.tsx:830
#: packages/ui/primitives/document-flow/add-fields.tsx:834
#: packages/ui/primitives/document-flow/add-signature.tsx:324
#: packages/ui/primitives/document-flow/field-icon.tsx:52
#: packages/ui/primitives/document-flow/types.ts:49
#: packages/ui/primitives/template-flow/add-template-fields.tsx:625
#: packages/ui/primitives/template-flow/add-template-fields.tsx:629
msgid "Signature"
msgstr "Signature"
@ -1361,8 +1391,8 @@ msgstr "Signers must have unique emails"
msgid "Signing"
msgstr "Signing"
#: packages/lib/server-only/document/send-completed-email.ts:114
#: packages/lib/server-only/document/send-completed-email.ts:194
#: packages/lib/server-only/document/send-completed-email.ts:119
#: packages/lib/server-only/document/send-completed-email.ts:199
msgid "Signing Complete!"
msgstr "Signing Complete!"
@ -1416,9 +1446,9 @@ msgstr "Team email removed for {teamName} on Documenso"
msgid "Template title"
msgstr "Template title"
#: packages/ui/primitives/document-flow/add-fields.tsx:960
#: packages/ui/primitives/document-flow/add-fields.tsx:964
#: packages/ui/primitives/document-flow/types.ts:52
#: packages/ui/primitives/template-flow/add-template-fields.tsx:755
#: packages/ui/primitives/template-flow/add-template-fields.tsx:759
msgid "Text"
msgstr "Text"
@ -1510,7 +1540,7 @@ msgstr "This can be overriden by setting the authentication requirements directl
msgid "This document can not be recovered, if you would like to dispute the reason for future documents please contact support."
msgstr "This document can not be recovered, if you would like to dispute the reason for future documents please contact support."
#: packages/ui/primitives/document-flow/add-fields.tsx:764
#: packages/ui/primitives/document-flow/add-fields.tsx:768
msgid "This document has already been sent to this recipient. You can no longer edit this recipient."
msgstr "This document has already been sent to this recipient. You can no longer edit this recipient."
@ -1526,15 +1556,19 @@ msgstr "This document was sent using <0>Documenso.</0>"
msgid "This email confirms that you have rejected the document <0>\"{documentName}\"</0> sent by {documentOwnerName}."
msgstr "This email confirms that you have rejected the document <0>\"{documentName}\"</0> sent by {documentOwnerName}."
#: packages/ui/components/document/document-email-checkboxes.tsx:94
#: packages/ui/components/document/document-email-checkboxes.tsx:56
msgid "This email is sent to the document owner when a recipient has signed the document."
msgstr "This email is sent to the document owner when a recipient has signed the document."
#: packages/ui/components/document/document-email-checkboxes.tsx:134
msgid "This email is sent to the recipient if they are removed from a pending document."
msgstr "This email is sent to the recipient if they are removed from a pending document."
#: packages/ui/components/document/document-email-checkboxes.tsx:55
#: packages/ui/components/document/document-email-checkboxes.tsx:95
msgid "This email is sent to the recipient requesting them to sign the document."
msgstr "This email is sent to the recipient requesting them to sign the document."
#: packages/ui/components/document/document-email-checkboxes.tsx:133
#: packages/ui/components/document/document-email-checkboxes.tsx:173
msgid "This email will be sent to the recipient who has just signed the document, if there are still other recipients who have not signed yet."
msgstr "This email will be sent to the recipient who has just signed the document, if there are still other recipients who have not signed yet."
@ -1546,7 +1580,7 @@ msgstr "This field cannot be modified or deleted. When you share this template's
msgid "This is how the document will reach the recipients once the document is ready for signing."
msgstr "This is how the document will reach the recipients once the document is ready for signing."
#: packages/ui/primitives/document-flow/add-fields.tsx:1097
#: packages/ui/primitives/document-flow/add-fields.tsx:1101
msgid "This recipient can no longer be modified as they have signed a field, or completed the document."
msgstr "This recipient can no longer be modified as they have signed a field, or completed the document."
@ -1554,29 +1588,33 @@ msgstr "This recipient can no longer be modified as they have signed a field, or
msgid "This signer has already signed the document."
msgstr "This signer has already signed the document."
#: packages/ui/components/document/document-email-checkboxes.tsx:212
#: packages/ui/components/document/document-email-checkboxes.tsx:252
msgid "This will be sent to all recipients if a pending document has been deleted."
msgstr "This will be sent to all recipients if a pending document has been deleted."
#: packages/ui/components/document/document-email-checkboxes.tsx:173
#: packages/ui/components/document/document-email-checkboxes.tsx:213
msgid "This will be sent to all recipients once the document has been fully completed."
msgstr "This will be sent to all recipients once the document has been fully completed."
#: packages/ui/components/document/document-email-checkboxes.tsx:291
msgid "This will be sent to the document owner once the document has been fully completed."
msgstr "This will be sent to the document owner once the document has been fully completed."
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:48
msgid "This will override any global settings."
msgstr "This will override any global settings."
#: packages/ui/primitives/document-flow/add-settings.tsx:347
#: packages/ui/primitives/document-flow/add-settings.tsx:359
#: packages/ui/primitives/template-flow/add-template-settings.tsx:438
msgid "Time Zone"
msgstr "Time Zone"
#: packages/ui/primitives/document-flow/add-settings.tsx:155
#: packages/ui/primitives/document-flow/add-settings.tsx:166
msgid "Title"
msgstr "Title"
#: packages/ui/primitives/document-flow/add-fields.tsx:1080
#: packages/ui/primitives/template-flow/add-template-fields.tsx:873
#: packages/ui/primitives/document-flow/add-fields.tsx:1084
#: packages/ui/primitives/template-flow/add-template-fields.tsx:877
msgid "To proceed further, please set at least one value for the {0} field."
msgstr "To proceed further, please set at least one value for the {0} field."
@ -1592,7 +1630,7 @@ msgstr "Update the role and add fields as required for the direct recipient. The
msgid "Upgrade"
msgstr "Upgrade"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:509
#: packages/ui/primitives/signature-pad/signature-pad.tsx:516
msgid "Upload Signature"
msgstr "Upload Signature"
@ -1721,7 +1759,7 @@ msgstr "You have been invited to join {0} on Documenso"
msgid "You have been invited to join the following team"
msgstr "You have been invited to join the following team"
#: packages/lib/server-only/recipient/set-recipients-for-document.ts:327
#: packages/lib/server-only/recipient/set-recipients-for-document.ts:337
msgid "You have been removed from a document"
msgstr "You have been removed from a document"
@ -1729,7 +1767,7 @@ msgstr "You have been removed from a document"
msgid "You have been requested to take ownership of team {0} on Documenso"
msgstr "You have been requested to take ownership of team {0} on Documenso"
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:115
#: packages/lib/jobs/definitions/emails/send-signing-email.handler.ts:103
#: packages/lib/server-only/document/resend-document.tsx:125
msgid "You have initiated the document {0} that requires you to {recipientActionVerb} it."
msgstr "You have initiated the document {0} that requires you to {recipientActionVerb} it."

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