Compare commits

..

2 Commits

Author SHA1 Message Date
Mythie 76e4807c67 v1.9.0-rc.2 2024-12-12 14:24:40 +11:00
Mythie efd6531d3e feat: add controls for sending completion emails to document owners
Adds a new `ownerDocumentCompleted` to the email settings that
controls whether a document will be sent to the owner upon completion.

This was previously the only email you couldn't disable and didn't
account for users integrating with just the API and Webhooks.

Also adds a flag to the public `sendDocument` endpoint which will
adjust this setting while sendint the document for users who aren't
using `emailSettings` on the `createDocument` endpoint.
2024-12-12 13:55:46 +11:00
215 changed files with 4302 additions and 6686 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ jobs:
e2e_tests:
name: 'E2E Tests'
timeout-minutes: 60
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+1 -1
View File
@@ -4,7 +4,7 @@ import '../styles.css';
export default function App({ Component, pageProps }) {
return (
<PlausibleProvider>
<Component {...pageProps} />
<Component {...pageProps} />;
</PlausibleProvider>
);
}
@@ -17,25 +17,23 @@ 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 "_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.
- 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.
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 preferences will not affect the
Updating the default document visibility in the team's general settings will not affect the
visibility of existing documents. You will need to update the visibility of each document
individually.
</Callout>
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@documenso/marketing",
"version": "1.9.0-rc.5",
"version": "1.9.0-rc.2",
"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.24.1"
"zod": "^3.23.8"
},
"devDependencies": {
"@lingui/loader": "^4.11.3",
@@ -10,13 +10,16 @@ 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';
@@ -40,6 +43,9 @@ 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`,
@@ -106,35 +112,38 @@ export const SinglePlayerClient = () => {
/**
* Upload, create, sign and send the document.
*/
const onSignSubmit = (data: unknown) => {
const onSignSubmit = async (data: TAddSignatureFormSchema) => {
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',
@@ -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,
+4 -4
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) {
+2 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@documenso/web",
"version": "1.9.0-rc.5",
"version": "1.9.0-rc.2",
"private": true,
"license": "AGPL-3.0",
"scripts": {
@@ -56,11 +56,10 @@
"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.24.1"
"zod": "^3.23.8"
},
"devDependencies": {
"@documenso/tailwind-config": "*",
@@ -134,7 +134,7 @@ export const LeaderboardTable = ({
startTransition(() => {
updateSearchParams({
sortBy: column,
sortOrder: sortBy === column && sortOrder === 'asc' ? 'desc' : 'asc',
sortOrder: sortOrder === 'asc' ? 'desc' : 'asc',
});
});
};
@@ -30,8 +30,8 @@ export type DeleteUserDialogProps = {
};
export const DeleteUserDialog = ({ className, user }: DeleteUserDialogProps) => {
const { _ } = useLingui();
const { toast } = useToast();
const { _ } = useLingui();
const router = useRouter();
@@ -44,6 +44,7 @@ export const DeleteUserDialog = ({ className, user }: DeleteUserDialogProps) =>
try {
await deleteUser({
id: user.id,
email,
});
toast({
@@ -77,7 +78,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>
@@ -1,141 +0,0 @@
'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>
);
};
@@ -1,130 +0,0 @@
'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>
);
};
@@ -23,8 +23,6 @@ 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 });
@@ -37,7 +35,7 @@ export default function UserPage({ params }: { params: { id: number } }) {
const router = useRouter();
const { data: user } = trpc.admin.getUser.useQuery(
const { data: user } = trpc.profile.getUser.useQuery(
{
id: Number(params.id),
},
@@ -155,11 +153,7 @@ export default function UserPage({ params }: { params: { id: number } }) {
<hr className="my-4" />
<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>
{user && <DeleteUserDialog user={user} />}
</div>
);
}
@@ -37,8 +37,10 @@ export const DocumentPageViewRecentActivity = ({
{
documentId,
filterForRecentActivity: true,
orderByColumn: 'createdAt',
orderByDirection: 'asc',
orderBy: {
column: 'createdAt',
direction: 'asc',
},
perPage: 10,
},
{
@@ -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: TGetDocumentWithDetailsByIdResponse;
initialDocument: DocumentWithDetails;
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: ({ fields: newFields }) => {
onSuccess: (newFields) => {
utils.document.getDocumentWithDetailsById.setData(
{
documentId: initialDocument.id,
@@ -132,9 +132,9 @@ export const EditDocumentForm = ({
},
});
const { mutateAsync: addSigners } = trpc.recipient.setDocumentRecipients.useMutation({
const { mutateAsync: addSigners } = trpc.recipient.addSigners.useMutation({
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
onSuccess: ({ recipients: newRecipients }) => {
onSuccess: (newRecipients) => {
utils.document.getDocumentWithDetailsById.setData(
{
documentId: initialDocument.id,
@@ -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 { TFindDocumentsResponse } from '@documenso/lib/server-only/document/find-documents';
import type { Team } from '@documenso/prisma/client';
import type { FindResultResponse } from '@documenso/lib/types/search-params';
import type { Document, Recipient, Team, User } 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,7 +24,13 @@ import { DataTableActionDropdown } from './data-table-action-dropdown';
import { DataTableTitle } from './data-table-title';
export type DocumentsDataTableProps = {
results: TFindDocumentsResponse;
results: FindResultResponse<
Document & {
Recipient: Recipient[];
User: Pick<User, 'id' | 'name' | 'email'>;
team: Pick<Team, 'id' | 'url'> | null;
}
>;
showSenderColumn?: boolean;
team?: Pick<Team, 'id' | 'url'> & { teamEmail?: string };
};
@@ -26,8 +26,10 @@ export const TemplatePageViewRecentActivity = ({
const { data, isLoading, isLoadingError, refetch } = trpc.document.findDocuments.useQuery({
templateId,
teamId,
orderByColumn: 'createdAt',
orderByDirection: 'asc',
orderBy: {
column: 'createdAt',
direction: 'asc',
},
perPage: 5,
});
@@ -4,10 +4,8 @@ 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';
@@ -53,20 +51,10 @@ export const MoveTemplateDialog = ({ templateId, open, onOpenChange }: MoveTempl
});
onOpenChange(false);
},
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.`);
onError: (error) => {
toast({
title: _(msg`Error`),
description: _(errorMessage),
description: error.message || _(msg`An error occurred while moving the template.`),
variant: 'destructive',
duration: 7500,
});
@@ -12,7 +12,6 @@ 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';
@@ -55,7 +54,6 @@ export const CheckboxField = ({
...item,
value: item.value.length > 0 ? item.value : `empty-value-${item.id}`,
}));
const [checkedValues, setCheckedValues] = useState(
values
?.map((item) =>
@@ -99,7 +97,7 @@ export const CheckboxField = ({
const payload: TSignFieldWithTokenMutationSchema = {
token: recipient.token,
fieldId: field.id,
value: toCheckboxValue(checkedValues),
value: checkedValues.join(','),
isBase64: true,
authOptions,
};
@@ -193,7 +191,7 @@ export const CheckboxField = ({
await signFieldWithToken({
token: recipient.token,
fieldId: field.id,
value: toCheckboxValue(checkedValues),
value: updatedValues.join(','),
isBase64: true,
});
}
@@ -230,11 +228,6 @@ 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 && (
@@ -284,7 +277,9 @@ export const CheckboxField = ({
className="h-3 w-3"
checkClassName="text-white"
id={`checkbox-${index}`}
checked={parsedCheckedValues.includes(itemValue)}
checked={field.customText
.split(',')
.some((customValue) => customValue === itemValue)}
disabled={isLoading}
onCheckedChange={() => void handleCheckboxOptionClick(item)}
/>
@@ -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(
@@ -11,7 +11,6 @@ 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';
@@ -58,31 +57,26 @@ 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(fieldsRequiringValidation.filter((field) => !field.inserted));
return sortFieldsByPosition(fields.filter((field) => !field.inserted));
}, [fields]);
const fieldsValidated = () => {
setValidateUninsertedFields(true);
validateFieldsInserted(fieldsRequiringValidation);
validateFieldsInserted(fields);
};
const onFormSubmit = async () => {
setValidateUninsertedFields(true);
const isFieldsValid = validateFieldsInserted(fieldsRequiringValidation);
if (hasSignatureField && !signatureValid) {
return;
}
const isFieldsValid = validateFieldsInserted(fields);
if (!isFieldsValid) {
return;
}
@@ -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);
@@ -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);
@@ -1,8 +1,7 @@
import { useMemo, useState } from 'react';
import { 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';
@@ -37,7 +36,7 @@ export const SignDialog = ({
}: SignDialogProps) => {
const [showDialog, setShowDialog] = useState(false);
const isComplete = useMemo(() => !fieldsContainUnsignedRequiredField(fields), [fields]);
const isComplete = fields.every((field) => field.inserted);
const handleOpenChange = (open: boolean) => {
if (isSubmitting || !isComplete) {
@@ -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);
@@ -2,7 +2,6 @@ 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';
@@ -45,24 +44,15 @@ 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',
);
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 _(msg`${formattedTeamMemberQuanity} • Monthly • Renews: ${formattedDate}`);
};
return (
@@ -76,6 +66,10 @@ 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>
@@ -47,7 +47,10 @@ 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>
@@ -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 { ZCreateTeamEmailVerificationRequestSchema } from '@documenso/trpc/server/team-router/create-team-email-verification-route';
import { ZCreateTeamEmailVerificationMutationSchema } from '@documenso/trpc/server/team-router/schema';
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 = ZCreateTeamEmailVerificationRequestSchema.pick({
const ZCreateTeamEmailFormSchema = ZCreateTeamEmailVerificationMutationSchema.pick({
name: true,
email: true,
});
@@ -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 { ZCreateTeamRequestSchema } from '@documenso/trpc/server/team-router/create-team-route';
import { ZCreateTeamMutationSchema } from '@documenso/trpc/server/team-router/schema';
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 = ZCreateTeamRequestSchema.pick({
const ZCreateTeamFormSchema = ZCreateTeamMutationSchema.pick({
teamName: true,
teamUrl: true,
});
@@ -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 { ZCreateTeamMemberInvitesRequestSchema } from '@documenso/trpc/server/team-router/create-team-member-invites-route';
import { ZCreateTeamMemberInvitesMutationSchema } from '@documenso/trpc/server/team-router/schema';
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: ZCreateTeamMemberInvitesRequestSchema.shape.invitations,
invitations: ZCreateTeamMemberInvitesMutationSchema.shape.invitations,
})
// Display exactly which rows are duplicates.
.superRefine((items, ctx) => {
@@ -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 { ZUpdateTeamRequestSchema } from '@documenso/trpc/server/team-router/update-team-route';
import { ZUpdateTeamMutationSchema } from '@documenso/trpc/server/team-router/schema';
import { Button } from '@documenso/ui/primitives/button';
import {
Form,
@@ -31,7 +31,7 @@ export type UpdateTeamDialogProps = {
teamUrl: string;
};
const ZUpdateTeamFormSchema = ZUpdateTeamRequestSchema.shape.data.pick({
const ZUpdateTeamFormSchema = ZUpdateTeamMutationSchema.shape.data.pick({
name: true,
url: true,
});
@@ -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,
ZUpdatePublicProfileRequestSchema,
} from '@documenso/trpc/server/profile-router/update-public-profile-route';
ZUpdatePublicProfileMutationSchema,
} from '@documenso/trpc/server/profile-router/schema';
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 = ZUpdatePublicProfileRequestSchema.pick({
export const ZPublicProfileFormSchema = ZUpdatePublicProfileMutationSchema.pick({
bio: true,
enabled: true,
url: true,
-1
View File
@@ -53,7 +53,6 @@ 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;
@@ -70,7 +70,6 @@ export const ZSignUpFormV2Schema = z
},
{
message: msg`Password should not be common or based on personal information`.id,
path: ['password'],
},
);
-47
View File
@@ -1,47 +0,0 @@
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'];
@@ -1,9 +0,0 @@
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;
-1
View File
@@ -45,7 +45,6 @@ export default trpcNext.createNextApiHandler({
logger.error(error, {
method: path,
context: {
source: 'trpc',
appError: AppError.toJSON(appError),
},
});
+69 -319
View File
@@ -1,12 +1,12 @@
{
"name": "@documenso/root",
"version": "1.9.0-rc.5",
"version": "1.9.0-rc.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@documenso/root",
"version": "1.9.0-rc.5",
"version": "1.9.0-rc.2",
"workspaces": [
"apps/*",
"packages/*"
@@ -17,10 +17,8 @@
"@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",
"zod": "3.24.1"
"react": "^18"
},
"devDependencies": {
"@commitlint/cli": "^17.7.1",
@@ -81,7 +79,7 @@
},
"apps/marketing": {
"name": "@documenso/marketing",
"version": "1.9.0-rc.5",
"version": "1.9.0-rc.2",
"license": "AGPL-3.0",
"dependencies": {
"@documenso/assets": "*",
@@ -117,7 +115,7 @@
"recharts": "^2.7.2",
"sharp": "0.32.6",
"typescript": "5.2.2",
"zod": "3.24.1"
"zod": "^3.23.8"
},
"devDependencies": {
"@lingui/loader": "^4.11.3",
@@ -494,7 +492,7 @@
},
"apps/web": {
"name": "@documenso/web",
"version": "1.9.0-rc.5",
"version": "1.9.0-rc.2",
"license": "AGPL-3.0",
"dependencies": {
"@documenso/api": "*",
@@ -538,11 +536,10 @@
"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.24.1"
"zod": "^3.23.8"
},
"devDependencies": {
"@documenso/tailwind-config": "*",
@@ -3497,11 +3494,6 @@
"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",
@@ -10725,6 +10717,15 @@
"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",
@@ -10746,6 +10747,14 @@
"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",
@@ -10809,6 +10818,14 @@
"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",
@@ -10825,6 +10842,15 @@
"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",
@@ -11981,18 +12007,6 @@
"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",
@@ -14297,21 +14311,6 @@
}
}
},
"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",
@@ -14613,14 +14612,6 @@
"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",
@@ -14636,17 +14627,6 @@
"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",
@@ -14732,9 +14712,9 @@
}
},
"node_modules/cookie-es": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz",
"integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg=="
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.0.0.tgz",
"integrity": "sha512-mWYvfOLrfEc996hlKcdABeIiPHUPC6DM2QYZdGGOvhOTbA3tjm2eBwqlJpoFdjC89NI4Qt6h0Pu06Mp+1Pj5OQ=="
},
"node_modules/copy-anything": {
"version": "3.0.5",
@@ -14900,14 +14880,6 @@
"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",
@@ -15615,11 +15587,6 @@
"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",
@@ -15726,11 +15693,6 @@
"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",
@@ -18161,14 +18123,6 @@
}
}
},
"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",
@@ -18839,23 +18793,6 @@
"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",
@@ -19907,14 +19844,6 @@
"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",
@@ -20071,6 +20000,14 @@
"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",
@@ -20282,14 +20219,6 @@
"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",
@@ -22106,7 +22035,8 @@
"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=="
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
"dev": true
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
@@ -22933,14 +22863,6 @@
"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",
@@ -22995,14 +22917,6 @@
"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",
@@ -23076,14 +22990,6 @@
"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",
@@ -23827,17 +23733,6 @@
"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",
@@ -24244,12 +24139,6 @@
"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",
@@ -24307,6 +24196,7 @@
"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"
}
@@ -24775,11 +24665,6 @@
"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",
@@ -25108,38 +24993,6 @@
"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",
@@ -25727,11 +25580,6 @@
"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",
@@ -25833,11 +25681,6 @@
}
}
},
"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",
@@ -26597,14 +26440,6 @@
"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",
@@ -26760,7 +26595,8 @@
"node_modules/pathe": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="
"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
"dev": true
},
"node_modules/pathval": {
"version": "1.1.1",
@@ -28072,11 +27908,6 @@
"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",
@@ -28115,14 +27946,6 @@
"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",
@@ -32959,32 +32782,6 @@
"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",
@@ -33750,18 +33547,6 @@
"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",
@@ -33880,9 +33665,10 @@
}
},
"node_modules/ufo": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz",
"integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ=="
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.4.0.tgz",
"integrity": "sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==",
"dev": true
},
"node_modules/ulid": {
"version": "2.3.0",
@@ -33917,11 +33703,6 @@
"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",
@@ -33939,29 +33720,6 @@
"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",
@@ -35651,9 +35409,9 @@
}
},
"node_modules/zod": {
"version": "3.24.1",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
"version": "3.23.8",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
@@ -35696,14 +35454,6 @@
"@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",
@@ -35728,7 +35478,7 @@
"superjson": "^1.13.1",
"swagger-ui-react": "^5.11.0",
"ts-pattern": "^5.0.5",
"zod": "3.24.1"
"zod": "^3.23.8"
}
},
"packages/api/node_modules/@ts-rest/next": {
@@ -35793,7 +35543,7 @@
"next-auth": "4.24.5",
"react": "^18",
"ts-pattern": "^5.0.5",
"zod": "3.24.1"
"zod": "^3.23.8"
}
},
"packages/email": {
@@ -37002,7 +36752,7 @@
"sharp": "0.32.6",
"stripe": "^12.7.0",
"ts-pattern": "^5.0.5",
"zod": "3.24.1"
"zod": "^3.23.8"
},
"devDependencies": {
"@playwright/browser-chromium": "1.43.0",
@@ -37140,7 +36890,7 @@
"luxon": "^3.4.0",
"superjson": "^1.13.1",
"ts-pattern": "^5.0.5",
"zod": "3.24.1"
"zod": "^3.23.8"
}
},
"packages/trpc/node_modules/@ts-rest/next": {
@@ -37220,7 +36970,7 @@
"tailwind-merge": "^1.12.0",
"tailwindcss-animate": "^1.0.5",
"ts-pattern": "^5.0.5",
"zod": "3.24.1"
"zod": "^3.23.8"
},
"devDependencies": {
"@documenso/tailwind-config": "*",
+3 -6
View File
@@ -1,6 +1,6 @@
{
"private": true,
"version": "1.9.0-rc.5",
"version": "1.9.0-rc.2",
"scripts": {
"build": "turbo run build",
"build:web": "turbo run build --filter=@documenso/web",
@@ -68,14 +68,11 @@
"@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",
"zod": "3.24.1"
"react": "^18"
},
"overrides": {
"next": "14.2.6",
"zod": "3.24.1"
"next": "14.2.6"
},
"trigger.dev": {
"endpointId": "documenso-app"
+2 -2
View File
@@ -25,6 +25,6 @@
"superjson": "^1.13.1",
"swagger-ui-react": "^5.11.0",
"ts-pattern": "^5.0.5",
"zod": "3.24.1"
"zod": "^3.23.8"
}
}
}
+9 -8
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 { getRecipientByIdV1Api } from '@documenso/lib/server-only/recipient/get-recipient-by-id-v1-api';
import { getRecipientById } from '@documenso/lib/server-only/recipient/get-recipient-by-id';
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 { TCreateDocumentFromTemplateResponse } from '@documenso/lib/server-only/template/create-document-from-template';
import type { CreateDocumentFromTemplateResponse } 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: TCreateDocumentFromTemplateResponse | null = null;
let document: CreateDocumentFromTemplateResponse | null = null;
try {
document = await createDocumentFromTemplate({
@@ -630,6 +630,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
token: recipient.token,
role: recipient.role,
signingOrder: recipient.signingOrder,
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
})),
},
@@ -785,7 +786,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
}
try {
const { recipients: newRecipients } = await setRecipientsForDocument({
const newRecipients = await setRecipientsForDocument({
documentId: Number(documentId),
userId: user.id,
teamId: team?.id,
@@ -999,7 +1000,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
throw new Error('Invalid page number');
}
const recipient = await getRecipientByIdV1Api({
const recipient = await getRecipientById({
id: Number(recipientId),
documentId: Number(documentId),
}).catch(() => null);
@@ -1144,7 +1145,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
};
}
const recipient = await getRecipientByIdV1Api({
const recipient = await getRecipientById({
id: Number(recipientId),
documentId: Number(documentId),
}).catch(() => null);
@@ -1248,7 +1249,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
};
}
const recipient = await getRecipientByIdV1Api({
const recipient = await getRecipientById({
id: Number(field.recipientId),
documentId: Number(documentId),
}).catch(() => null);
+4 -20
View File
@@ -1,6 +1,5 @@
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';
@@ -23,33 +22,18 @@ export const authenticatedMiddleware = <
const [token] = (authorization || '').split('Bearer ').filter((s) => s.length > 0);
if (!token) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'API token was not provided',
});
throw new Error('Token was not provided for authenticated middleware');
}
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: err });
let message = 'Unauthorized';
if (err instanceof AppError) {
message = err.message;
}
} catch (_err) {
console.log({ _err });
return {
status: 401,
body: {
message,
message: 'Unauthorized',
},
} as const;
}
@@ -1,7 +1,6 @@
import { expect, test } from '@playwright/test';
import { DocumentStatus, DocumentVisibility, TeamMemberRole } from '@documenso/prisma/client';
import { seedBlankDocument } from '@documenso/prisma/seed/documents';
import { DocumentStatus, TeamMemberRole } from '@documenso/prisma/client';
import { seedDocuments, seedTeamDocuments } from '@documenso/prisma/seed/documents';
import { seedTeam, seedTeamEmail, seedTeamMember } from '@documenso/prisma/seed/teams';
import { seedUser } from '@documenso/prisma/seed/users';
@@ -539,7 +538,7 @@ test('[TEAMS]: ensure recipient can see document regardless of visibility', asyn
await apiSignout({ page });
});
test('[TEAMS]: check that MEMBER role cannot see ADMIN-only documents', async ({ page }) => {
test('[TEAMS]: check that members cannot see ADMIN-only documents', async ({ page }) => {
const team = await seedTeam();
// Seed a member user
@@ -576,46 +575,7 @@ test('[TEAMS]: check that MEMBER role cannot see ADMIN-only documents', async ({
await apiSignout({ 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 }) => {
test('[TEAMS]: check that managers cannot see ADMIN-only documents', async ({ page }) => {
const team = await seedTeam();
// Seed a manager user
@@ -652,7 +612,7 @@ test('[TEAMS]: check that MANAGER role cannot see ADMIN-only documents', async (
await apiSignout({ page });
});
test('[TEAMS]: check that ADMIN role can see MANAGER_AND_ABOVE documents', async ({ page }) => {
test('[TEAMS]: check that admin can see MANAGER_AND_ABOVE documents', async ({ page }) => {
const team = await seedTeam();
// Seed an admin user
@@ -689,187 +649,6 @@ test('[TEAMS]: check that ADMIN role can see MANAGER_AND_ABOVE documents', async
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();
+2 -2
View File
@@ -21,6 +21,6 @@
"next-auth": "4.24.5",
"react": "^18",
"ts-pattern": "^5.0.5",
"zod": "3.24.1"
"zod": "^3.23.8"
}
}
}
@@ -1,10 +1,8 @@
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { subscriptionsContainsActivePlan } from '@documenso/lib/utils/billing';
import { subscriptionsContainActiveEnterprisePlan } 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;
@@ -54,11 +52,5 @@ export const isUserEnterprise = async ({
.then((user) => user.Subscription);
}
if (subscriptions.length === 0) {
return false;
}
const enterprisePlanPriceIds = await getEnterprisePlanPriceIds();
return subscriptionsContainsActivePlan(subscriptions, enterprisePlanPriceIds, true);
return subscriptionsContainActiveEnterprisePlan(subscriptions);
};
@@ -1,56 +0,0 @@
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;
@@ -1,70 +0,0 @@
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;
@@ -12,7 +12,7 @@ export function useCopyShareLink({ onSuccess, onError }: UseCopyShareLinkOptions
const [, copyToClipboard] = useCopyToClipboard();
const { mutateAsync: createOrGetShareLink, isLoading: isCreatingShareLink } =
trpc.document.createOrGetShareLink.useMutation();
trpc.shareLink.createOrGetShareLink.useMutation();
/**
* Copy a newly created, or pre-existing share link to the user's clipboard.
-2
View File
@@ -1,6 +1,5 @@
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';
@@ -20,7 +19,6 @@ 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;
@@ -1,9 +0,0 @@
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,
});
};
@@ -1,5 +1,6 @@
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';
@@ -9,10 +10,6 @@ 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',
@@ -22,11 +19,12 @@ export const SEND_CONFIRMATION_EMAIL_JOB_DEFINITION = {
schema: SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_SCHEMA,
},
handler: async ({ payload }) => {
const handler = await import('./send-confirmation-email.handler');
await handler.run({ payload });
await sendConfirmationToken({
email: payload.email,
force: payload.force,
});
},
} as const satisfies JobDefinition<
typeof SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_ID,
TSendConfirmationEmailJobDefinition
z.infer<typeof SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_SCHEMA>
>;
@@ -1,130 +0,0 @@
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>
>;
@@ -1,156 +0,0 @@
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,
},
});
});
};
@@ -1,5 +1,21 @@
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';
@@ -9,10 +25,6 @@ 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',
@@ -22,11 +34,136 @@ export const SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION = {
schema: SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_SCHEMA,
},
handler: async ({ payload, io }) => {
const handler = await import('./send-rejection-emails.handler');
const { documentId, recipientId } = payload;
await handler.run({ payload, io });
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,
},
});
});
},
} as const satisfies JobDefinition<
typeof SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_ID,
TSendSigningRejectionEmailsJobDefinition
z.infer<typeof SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_SCHEMA>
>;
@@ -1,215 +0,0 @@
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,
},
}),
});
});
};
@@ -1,6 +1,32 @@
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';
@@ -12,10 +38,6 @@ 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',
@@ -25,11 +47,185 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
schema: SEND_SIGNING_EMAIL_JOB_DEFINITION_SCHEMA,
},
handler: async ({ payload, io }) => {
const handler = await import('./send-signing-email.handler');
const { userId, documentId, recipientId, requestMetadata } = payload;
await handler.run({ payload, io });
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,
},
}),
});
});
},
} as const satisfies JobDefinition<
typeof SEND_SIGNING_EMAIL_JOB_DEFINITION_ID,
TSendSigningEmailJobDefinition
z.infer<typeof SEND_SIGNING_EMAIL_JOB_DEFINITION_SCHEMA>
>;
@@ -1,23 +0,0 @@
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,
});
});
}
};
@@ -2,6 +2,7 @@ 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';
@@ -36,10 +37,6 @@ 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',
@@ -49,11 +46,19 @@ export const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION = {
schema: SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA,
},
handler: async ({ payload, io }) => {
const handler = await import('./send-team-deleted-email.handler');
const { team, members } = payload;
await handler.run({ payload, io });
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,
});
});
}
},
} as const satisfies JobDefinition<
typeof SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_ID,
TSendTeamDeletedEmailJobDefinition
z.infer<typeof SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA>
>;
@@ -1,105 +0,0 @@
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,
});
},
);
}
};
@@ -1,5 +1,18 @@
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';
@@ -9,10 +22,6 @@ 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',
@@ -22,11 +31,88 @@ export const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION = {
schema: SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_SCHEMA,
},
handler: async ({ payload, io }) => {
const handler = await import('./send-team-member-joined-email.handler');
const team = await prisma.team.findFirstOrThrow({
where: {
id: payload.teamId,
},
include: {
members: {
where: {
role: {
in: [TeamMemberRole.ADMIN, TeamMemberRole.MANAGER],
},
},
include: {
user: true,
},
},
teamGlobalSettings: true,
},
});
await handler.run({ payload, io });
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,
});
},
);
}
},
} as const satisfies JobDefinition<
typeof SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID,
TSendTeamMemberJoinedEmailJobDefinition
z.infer<typeof SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_SCHEMA>
>;
@@ -1,93 +0,0 @@
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,
});
});
}
};
@@ -1,5 +1,18 @@
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';
@@ -9,10 +22,6 @@ 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',
@@ -22,11 +31,76 @@ export const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION = {
schema: SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_SCHEMA,
},
handler: async ({ payload, io }) => {
const handler = await import('./send-team-member-left-email.handler');
const team = await prisma.team.findFirstOrThrow({
where: {
id: payload.teamId,
},
include: {
members: {
where: {
role: {
in: [TeamMemberRole.ADMIN, TeamMemberRole.MANAGER],
},
},
include: {
user: true,
},
},
teamGlobalSettings: true,
},
});
await handler.run({ payload, io });
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,
});
});
}
},
} as const satisfies JobDefinition<
typeof SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_ID,
TSendTeamMemberLeftEmailJobDefinition
z.infer<typeof SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_SCHEMA>
>;
@@ -1,256 +0,0 @@
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,
});
};
@@ -1,6 +1,31 @@
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';
@@ -12,8 +37,6 @@ 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',
@@ -23,11 +46,223 @@ export const SEAL_DOCUMENT_JOB_DEFINITION = {
schema: SEAL_DOCUMENT_JOB_DEFINITION_SCHEMA,
},
handler: async ({ payload, io }) => {
const handler = await import('./seal-document.handler');
const { documentId, sendEmail = true, isResealing = false, requestMetadata } = payload;
await handler.run({ payload, io });
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,
});
},
} as const satisfies JobDefinition<
typeof SEAL_DOCUMENT_JOB_DEFINITION_ID,
TSealDocumentJobDefinition
z.infer<typeof SEAL_DOCUMENT_JOB_DEFINITION_SCHEMA>
>;
-4
View File
@@ -121,10 +121,6 @@ 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,
-1
View File
@@ -20,5 +20,4 @@ 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;
@@ -21,10 +21,6 @@ export const getServerComponentSession = cache(async () => {
},
});
if (user.disabled) {
return { user: null, session: null };
}
return { user, session };
});
+2 -2
View File
@@ -56,11 +56,11 @@
"sharp": "0.32.6",
"stripe": "^12.7.0",
"ts-pattern": "^5.0.5",
"zod": "3.24.1"
"zod": "^3.23.8"
},
"devDependencies": {
"@playwright/browser-chromium": "1.43.0",
"@types/luxon": "^3.3.1",
"@types/pg": "^8.11.4"
}
}
}
@@ -1,5 +1,5 @@
import { prisma } from '@documenso/prisma';
import { DocumentStatus, Prisma } from '@documenso/prisma/client';
import { Prisma } from '@documenso/prisma/client';
export type SigningVolume = {
id: number;
@@ -43,41 +43,34 @@ export async function getSigningVolume({
],
});
const orderByClause = getOrderByClause({ sortBy, sortOrder });
const [subscriptions, totalCount] = await Promise.all([
prisma.subscription.findMany({
where: whereClause,
include: {
User: {
select: {
name: true,
email: true,
include: {
Document: {
where: {
status: DocumentStatus.COMPLETED,
status: 'COMPLETED',
deletedAt: null,
teamId: null,
},
},
},
},
team: {
select: {
name: true,
include: {
document: {
where: {
status: DocumentStatus.COMPLETED,
status: 'COMPLETED',
deletedAt: null,
},
},
},
},
},
orderBy:
sortBy === 'name'
? [{ User: { name: sortOrder } }, { team: { name: sortOrder } }, { createdAt: 'desc' }]
: sortBy === 'createdAt'
? [{ createdAt: sortOrder }]
: undefined,
orderBy: orderByClause,
skip: Math.max(page - 1, 0) * perPage,
take: perPage,
}),
@@ -89,8 +82,10 @@ 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,
@@ -100,16 +95,54 @@ 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,
},
},
},
];
}
@@ -1,6 +1,5 @@
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 {
@@ -9,8 +8,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';
@@ -86,7 +85,7 @@ export const completeDocumentWithToken = async ({
},
});
if (fieldsContainUnsignedRequiredField(fields)) {
if (fields.some((field) => !field.inserted)) {
throw new Error(`Recipient ${recipient.id} has unsigned fields`);
}
@@ -140,14 +139,6 @@ 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,
@@ -1,9 +1,6 @@
'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';
@@ -11,11 +8,8 @@ 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 = {
@@ -25,24 +19,18 @@ 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): Promise<TCreateDocumentResponse> => {
}: CreateDocumentOptions) => {
const user = await prisma.user.findFirstOrThrow({
where: {
id: userId,
@@ -94,44 +82,22 @@ export const createDocument = async ({
globalVisibility: DocumentVisibility | null | undefined,
userRole: TeamMemberRole,
): DocumentVisibility => {
if (globalVisibility) {
return globalVisibility;
}
const defaultVisibility = globalVisibility ?? DocumentVisibility.EVERYONE;
if (userRole === TeamMemberRole.ADMIN) {
return DocumentVisibility.ADMIN;
return defaultVisibility;
}
if (userRole === TeamMemberRole.MANAGER) {
return DocumentVisibility.MANAGER_AND_ABOVE;
if (defaultVisibility === DocumentVisibility.ADMIN) {
return DocumentVisibility.MANAGER_AND_ABOVE;
}
return defaultVisibility;
}
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: {
@@ -1,27 +1,19 @@
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 DuplicateDocumentOptions {
export interface DuplicateDocumentByIdOptions {
documentId: number;
userId: number;
teamId?: number;
}
export const ZDuplicateDocumentResponseSchema = z.object({
documentId: z.number(),
});
export type TDuplicateDocumentResponse = z.infer<typeof ZDuplicateDocumentResponseSchema>;
export const duplicateDocument = async ({
export const duplicateDocumentById = async ({
documentId,
userId,
teamId,
}: DuplicateDocumentOptions): Promise<TDuplicateDocumentResponse> => {
}: DuplicateDocumentByIdOptions) => {
const documentWhereInput = await getDocumentWhereInput({
documentId,
userId,
@@ -86,7 +78,5 @@ export const duplicateDocument = async ({
const createdDocument = await prisma.document.create(createDocumentArguments);
return {
documentId: createdDocument.id,
};
return createdDocument.id;
};
@@ -1,6 +1,5 @@
import { DateTime } from 'luxon';
import { match } from 'ts-pattern';
import type { z } from 'zod';
import { prisma } from '@documenso/prisma';
import type {
@@ -12,16 +11,10 @@ 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, ZFindResultResponse } from '../../types/search-params';
import type { FindResultResponse } from '../../types/search-params';
import { maskRecipientTokensForDocument } from '../../utils/mask-recipient-tokens-for-document';
export type PeriodSelectorValue = '' | '7d' | '14d' | '30d';
@@ -43,23 +36,6 @@ 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,
@@ -72,7 +48,7 @@ export const findDocuments = async ({
period,
senderIds,
query,
}: FindDocumentsOptions): Promise<TFindDocumentsResponse> => {
}: FindDocumentsOptions) => {
const user = await prisma.user.findFirstOrThrow({
where: {
id: userId,
@@ -1,15 +1,6 @@
import type { z } from 'zod';
import { prisma } from '@documenso/prisma';
import {
DocumentDataSchema,
DocumentMetaSchema,
DocumentSchema,
FieldSchema,
RecipientSchema,
} from '@documenso/prisma/generated/zod';
import type { DocumentWithDetails } from '@documenso/prisma/types/document';
import { AppError, AppErrorCode } from '../../errors/app-error';
import { getDocumentWhereInput } from './get-document-by-id';
export type GetDocumentWithDetailsByIdOptions = {
@@ -18,29 +9,18 @@ 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<TGetDocumentWithDetailsByIdResponse> => {
}: GetDocumentWithDetailsByIdOptions): Promise<DocumentWithDetails> => {
const documentWhereInput = await getDocumentWhereInput({
documentId,
userId,
teamId,
});
const document = await prisma.document.findFirst({
return await prisma.document.findFirstOrThrow({
where: documentWhereInput,
include: {
documentData: true,
@@ -49,12 +29,4 @@ export const getDocumentWithDetailsById = async ({
Field: true,
},
});
if (!document) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Document not found',
});
}
return document;
};
@@ -1,9 +1,7 @@
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';
@@ -15,16 +13,12 @@ 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): Promise<TMoveDocumentToTeamResponse> => {
}: MoveDocumentToTeamOptions) => {
return await prisma.$transaction(async (tx) => {
const user = await tx.user.findUniqueOrThrow({
where: { id: userId },
@@ -38,7 +38,7 @@ export const resendDocument = async ({
recipients,
teamId,
requestMetadata,
}: ResendDocumentOptions): Promise<void> => {
}: ResendDocumentOptions) => {
const user = await prisma.user.findFirstOrThrow({
where: {
id: userId,
@@ -6,19 +6,14 @@ import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-po
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
import { prisma } from '@documenso/prisma';
import {
DocumentStatus,
RecipientRole,
SigningStatus,
WebhookTriggerEvents,
} from '@documenso/prisma/client';
import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/client';
import { WebhookTriggerEvents } from '@documenso/prisma/client';
import { signPdf } from '@documenso/signing';
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';
@@ -93,8 +88,8 @@ export const sealDocument = async ({
},
});
if (fieldsContainUnsignedRequiredField(fields)) {
throw new Error(`Document ${document.id} has unsigned required fields`);
if (fields.some((field) => !field.inserted)) {
throw new Error(`Document ${document.id} has unsigned fields`);
}
if (isResealing) {
@@ -1,5 +1,3 @@
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,13 +9,8 @@ import {
RecipientRole,
SendStatus,
SigningStatus,
WebhookTriggerEvents,
} from '@documenso/prisma/client';
import {
DocumentMetaSchema,
DocumentSchema,
RecipientSchema,
} from '@documenso/prisma/generated/zod';
import { WebhookTriggerEvents } from '@documenso/prisma/client';
import { jobs } from '../../jobs/client';
import { extractDerivedDocumentEmailSettings } from '../../types/document-email';
@@ -34,20 +27,13 @@ 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): Promise<TSendDocumentResponse> => {
}: SendDocumentOptions) => {
const user = await prisma.user.findFirstOrThrow({
where: {
id: userId,
@@ -225,7 +211,6 @@ export const sendDocument = async ({
id: documentId,
},
include: {
documentMeta: true,
Recipient: true,
},
});
@@ -1,7 +1,6 @@
'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';
@@ -11,7 +10,6 @@ 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';
@@ -31,17 +29,13 @@ 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): Promise<TUpdateDocumentSettingsResponse> => {
}: UpdateDocumentSettingsOptions) => {
if (!data.title && !data.globalAccessAuth && !data.globalActionAuth) {
throw new AppError(AppErrorCode.INVALID_BODY, {
message: 'Missing data to update',
@@ -91,43 +85,39 @@ export const updateDocumentSettings = async ({
if (teamId) {
const currentUserRole = document.team?.members[0]?.role;
const isDocumentOwner = document.userId === userId;
const requestedVisibility = data.visibility;
if (!isDocumentOwner) {
match(currentUserRole)
.with(TeamMemberRole.ADMIN, () => true)
.with(TeamMemberRole.MANAGER, () => {
const allowedVisibilities: DocumentVisibility[] = [
DocumentVisibility.EVERYONE,
DocumentVisibility.MANAGER_AND_ABOVE,
];
match(currentUserRole)
.with(TeamMemberRole.ADMIN, () => true)
.with(TeamMemberRole.MANAGER, () => {
const allowedVisibilities: DocumentVisibility[] = [
DocumentVisibility.EVERYONE,
DocumentVisibility.MANAGER_AND_ABOVE,
];
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(() => {
if (
!allowedVisibilities.includes(document.visibility) ||
(data.visibility && !allowedVisibilities.includes(data.visibility))
) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'You do not have permission to update the document',
message: 'You do not have permission to update the document visibility',
});
}
})
.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({
@@ -1,9 +1,4 @@
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;
@@ -13,17 +8,13 @@ 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): Promise<TGetFieldByIdResponse> => {
}: GetFieldByIdOptions) => {
const field = await prisma.field.findFirst({
where: {
id: fieldId,
@@ -54,11 +45,5 @@ export const getFieldById = async ({
},
});
if (!field) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Field not found',
});
}
return field;
};
@@ -1,5 +1,4 @@
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';
@@ -24,7 +23,6 @@ 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';
@@ -36,18 +34,12 @@ 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<TSetFieldsForDocumentResponse> => {
}: SetFieldsForDocumentOptions): Promise<Field[]> => {
const document = await prisma.document.findFirst({
where: {
id: documentId,
@@ -83,15 +75,11 @@ export const setFieldsForDocument = async ({
});
if (!document) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Document not found',
});
throw new Error('Document not found');
}
if (document.completedAt) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message: 'Document already complete',
});
throw new Error('Document already complete');
}
const existingFields = await prisma.field.findMany({
@@ -347,9 +335,7 @@ export const setFieldsForDocument = async ({
return !isRemoved && !isUpdated;
});
return {
fields: [...filteredFields, ...persistedFields],
};
return [...filteredFields, ...persistedFields];
};
/**
@@ -1,5 +1,3 @@
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';
@@ -16,7 +14,6 @@ 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;
@@ -34,17 +31,11 @@ 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): Promise<TSetFieldsForTemplateResponse> => {
}: SetFieldsForTemplateOptions) => {
const template = await prisma.template.findFirst({
where: {
id: templateId,
@@ -215,7 +206,5 @@ export const setFieldsForTemplate = async ({
return !isRemoved && !isUpdated;
});
return {
fields: [...filteredFields, ...persistedFields],
};
return [...filteredFields, ...persistedFields];
};
@@ -8,7 +8,6 @@ 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';
@@ -120,8 +119,7 @@ export const signFieldWithToken = async ({
if (field.type === FieldType.CHECKBOX && field.fieldMeta) {
const checkboxFieldParsedMeta = ZCheckboxFieldMeta.parse(field.fieldMeta);
const checkboxFieldValues: string[] = fromCheckboxValue(value);
const checkboxFieldValues = value.split(',');
const errors = validateCheckboxField(checkboxFieldValues, checkboxFieldParsedMeta, true);
if (errors.length > 0) {
+1 -13
View File
@@ -1,11 +1,9 @@
import type { PDFField, PDFWidgetAnnotation } from 'pdf-lib';
import { PDFCheckBox, PDFRadioGroup, PDFRef } from 'pdf-lib';
import {
PDFCheckBox,
PDFDict,
type PDFDocument,
PDFName,
PDFRadioGroup,
PDFRef,
drawObject,
popGraphicsState,
pushGraphicsState,
@@ -13,17 +11,7 @@ import {
translate,
} from 'pdf-lib';
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'));
}
};
export const flattenForm = (document: PDFDocument) => {
removeOptionalContentGroups(document);
const form = document.getForm();
form.updateFieldAppearances();
@@ -10,7 +10,6 @@ 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';
@@ -195,7 +194,7 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
value: item.value.length > 0 ? item.value : `empty-value-${item.id}`,
}));
const selected: string[] = fromCheckboxValue(field.customText);
const selected = field.customText.split(',');
for (const [index, item] of (values ?? []).entries()) {
const offsetY = index * 16;
@@ -1,18 +0,0 @@
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());
};
@@ -1,21 +0,0 @@
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;
};
@@ -1,54 +1,20 @@
import { prisma } from '@documenso/prisma';
import { AppError, AppErrorCode } from '../../errors/app-error';
export type GetRecipientByIdOptions = {
recipientId: number;
userId: number;
teamId?: number;
id: number;
documentId: number;
};
/**
* 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) => {
export const getRecipientById = async ({ documentId, id }: GetRecipientByIdOptions) => {
const recipient = await prisma.recipient.findFirst({
where: {
id: recipientId,
Document: {
OR: [
teamId === undefined
? {
userId,
teamId: null,
}
: {
teamId,
team: {
members: {
some: {
userId,
},
},
},
},
],
},
},
include: {
Field: true,
documentId,
id,
},
});
if (!recipient) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Recipient not found',
});
throw new Error('Recipient not found');
}
return recipient;
@@ -45,7 +45,7 @@ export const setRecipientsForDocument = async ({
documentId,
recipients,
requestMetadata,
}: SetRecipientsForDocumentOptions) => {
}: SetRecipientsForDocumentOptions): Promise<Recipient[]> => {
const document = await prisma.document.findFirst({
where: {
id: documentId,
@@ -344,9 +344,7 @@ export const setRecipientsForDocument = async ({
return !isRemoved && !isUpdated;
});
return {
recipients: [...filteredRecipients, ...persistedRecipients],
};
return [...filteredRecipients, ...persistedRecipients];
};
/**
@@ -220,7 +220,5 @@ export const setRecipientsForTemplate = async ({
return !isRemoved && !isUpdated;
});
return {
recipients: [...filteredRecipients, ...persistedRecipients],
};
return [...filteredRecipients, ...persistedRecipients];
};
@@ -1,5 +1,3 @@
import type { z } from 'zod';
import { nanoid } from '@documenso/lib/universal/id';
import { prisma } from '@documenso/prisma';
import type { DocumentDistributionMethod } from '@documenso/prisma/client';
@@ -13,11 +11,6 @@ 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';
@@ -43,6 +36,10 @@ type FinalRecipient = Pick<
fields: Field[];
};
export type CreateDocumentFromTemplateResponse = Awaited<
ReturnType<typeof createDocumentFromTemplate>
>;
export type CreateDocumentFromTemplateOptions = {
templateId: number;
externalId?: string | null;
@@ -75,15 +72,6 @@ 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,
@@ -92,7 +80,7 @@ export const createDocumentFromTemplate = async ({
recipients,
override,
requestMetadata,
}: CreateDocumentFromTemplateOptions): Promise<TCreateDocumentFromTemplateResponse> => {
}: CreateDocumentFromTemplateOptions) => {
const user = await prisma.user.findFirstOrThrow({
where: {
id: userId,
@@ -7,7 +7,7 @@ import {
DIRECT_TEMPLATE_RECIPIENT_NAME,
} from '@documenso/lib/constants/direct-templates';
import { prisma } from '@documenso/prisma';
import type { Recipient } from '@documenso/prisma/client';
import type { Recipient, TemplateDirectLink } from '@documenso/prisma/client';
import { AppError, AppErrorCode } from '../../errors/app-error';
@@ -21,7 +21,7 @@ export const createTemplateDirectLink = async ({
templateId,
userId,
directRecipientId,
}: CreateTemplateDirectLinkOptions) => {
}: CreateTemplateDirectLinkOptions): Promise<TemplateDirectLink> => {
const template = await prisma.template.findFirst({
where: {
id: templateId,
@@ -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,6 +11,9 @@ export type FindTemplatesOptions = {
perPage?: number;
};
export type FindTemplatesResponse = Awaited<ReturnType<typeof findTemplates>>;
export type FindTemplateRow = FindTemplatesResponse['data'][number];
export const findTemplates = async ({
userId,
teamId,
@@ -1,5 +1,16 @@
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';
@@ -9,7 +20,26 @@ export type GetTemplateByIdOptions = {
teamId?: number;
};
export const getTemplateById = async ({ id, userId, teamId }: GetTemplateByIdOptions) => {
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> => {
const whereFilter: Prisma.TemplateWhereInput = {
id,
OR:
@@ -1,6 +1,6 @@
import { prisma } from '@documenso/prisma';
import { TRPCError } from '@trpc/server';
import { AppError, AppErrorCode } from '../../errors/app-error';
import { prisma } from '@documenso/prisma';
export type MoveTemplateToTeamOptions = {
templateId: number;
@@ -23,7 +23,8 @@ export const moveTemplateToTeam = async ({
});
if (!template) {
throw new AppError(AppErrorCode.NOT_FOUND, {
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Template not found or already associated with a team.',
});
}
@@ -40,8 +41,9 @@ export const moveTemplateToTeam = async ({
});
if (!team) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'Team does not exist or you are not a member of this team.',
throw new TRPCError({
code: 'FORBIDDEN',
message: 'You are not a member of this team.',
});
}
@@ -1,69 +0,0 @@
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;
}
};
@@ -1,27 +0,0 @@
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,
},
});
};
+83 -121
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.handler.ts:125
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:137
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.handler.ts:118
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:130
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.handler.ts:124
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:136
#: 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,22 +195,6 @@ 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."
@@ -305,7 +289,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.handler.ts:110
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:122
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}."
@@ -321,7 +305,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.handler.ts:98
#: packages/lib/jobs/definitions/emails/send-team-member-joined-email.ts:107
msgid "A new member has joined your team"
msgstr "Ein neues Mitglied ist deinem Team beigetreten"
@@ -345,7 +329,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.handler.ts:87
#: packages/lib/jobs/definitions/emails/send-team-member-left-email.ts:96
msgid "A team member has left {0}"
msgstr "Ein Teammitglied hat {0} verlassen"
@@ -380,12 +364,12 @@ msgstr "Übertragungsanfrage des Teams auf Documenso annehmen"
msgid "Add a document"
msgstr "Dokument hinzufügen"
#: packages/ui/primitives/document-flow/add-settings.tsx:390
#: packages/ui/primitives/document-flow/add-settings.tsx:378
#: 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:302
#: packages/ui/primitives/document-flow/add-settings.tsx:290
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."
@@ -430,13 +414,13 @@ msgstr "Text zum Feld hinzufügen"
msgid "Admin"
msgstr "Admin"
#: packages/ui/primitives/document-flow/add-settings.tsx:284
#: packages/ui/primitives/document-flow/add-settings.tsx:272
#: packages/ui/primitives/template-flow/add-template-settings.tsx:367
msgid "Advanced Options"
msgstr "Erweiterte Optionen"
#: 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:576
#: packages/ui/primitives/template-flow/add-template-fields.tsx:414
msgid "Advanced settings"
msgstr "Erweiterte Einstellungen"
@@ -488,11 +472,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:531
#: packages/ui/primitives/signature-pad/signature-pad.tsx:524
msgid "Black"
msgstr "Schwarz"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:545
#: packages/ui/primitives/signature-pad/signature-pad.tsx:538
msgid "Blue"
msgstr "Blau"
@@ -508,7 +492,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:357
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:356
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:58
msgid "Cancel"
msgstr "Abbrechen"
@@ -550,7 +534,7 @@ msgstr "Checkbox-Werte"
msgid "Clear filters"
msgstr "Filter löschen"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:565
#: packages/ui/primitives/signature-pad/signature-pad.tsx:558
msgid "Clear Signature"
msgstr "Unterschrift löschen"
@@ -563,7 +547,6 @@ 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"
@@ -578,8 +561,8 @@ msgstr "Abgeschlossenes Dokument"
msgid "Configure Direct Recipient"
msgstr "Direkten Empfänger konfigurieren"
#: packages/ui/primitives/document-flow/add-fields.tsx:578
#: packages/ui/primitives/template-flow/add-template-fields.tsx:416
#: packages/ui/primitives/document-flow/add-fields.tsx:577
#: packages/ui/primitives/template-flow/add-template-fields.tsx:415
msgid "Configure the {0} field"
msgstr "Konfigurieren Sie das Feld {0}"
@@ -636,13 +619,13 @@ msgstr "Konto erstellen"
msgid "Custom Text"
msgstr "Benutzerdefinierter Text"
#: packages/ui/primitives/document-flow/add-fields.tsx:938
#: packages/ui/primitives/document-flow/add-fields.tsx:934
#: packages/ui/primitives/document-flow/types.ts:53
#: packages/ui/primitives/template-flow/add-template-fields.tsx:733
#: packages/ui/primitives/template-flow/add-template-fields.tsx:729
msgid "Date"
msgstr "Datum"
#: packages/ui/primitives/document-flow/add-settings.tsx:325
#: packages/ui/primitives/document-flow/add-settings.tsx:313
#: packages/ui/primitives/template-flow/add-template-settings.tsx:408
msgid "Date Format"
msgstr "Datumsformat"
@@ -659,16 +642,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.handler.ts:140
#: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:149
msgid "Document \"{0}\" - Rejected by {1}"
msgstr "Dokument \"{0}\" - Abgelehnt von {1}"
#: packages/lib/jobs/definitions/emails/send-rejection-emails.handler.ts:100
#: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:109
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:227
#: packages/ui/primitives/document-flow/add-settings.tsx:216
#: packages/ui/primitives/template-flow/add-template-settings.tsx:202
msgid "Document access"
msgstr "Dokumentenzugriff"
@@ -687,8 +670,7 @@ msgstr "Dokument storniert"
msgid "Document completed"
msgstr "Dokument abgeschlossen"
#: packages/ui/components/document/document-email-checkboxes.tsx:208
#: packages/ui/components/document/document-email-checkboxes.tsx:286
#: packages/ui/components/document/document-email-checkboxes.tsx:168
msgid "Document completed email"
msgstr "E-Mail zum Abschluss des Dokuments"
@@ -697,7 +679,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:585
#: packages/lib/server-only/template/create-document-from-direct-template.ts:574
msgid "Document created from direct template"
msgstr "Dokument erstellt aus direkter Vorlage"
@@ -709,7 +691,7 @@ msgstr "Dokumenterstellung"
msgid "Document deleted"
msgstr "Dokument gelöscht"
#: packages/ui/components/document/document-email-checkboxes.tsx:247
#: packages/ui/components/document/document-email-checkboxes.tsx:207
msgid "Document deleted email"
msgstr "E-Mail zum Löschen des Dokuments"
@@ -734,7 +716,7 @@ msgstr "Dokument ins Team verschoben"
msgid "Document opened"
msgstr "Dokument geöffnet"
#: packages/ui/components/document/document-email-checkboxes.tsx:168
#: packages/ui/components/document/document-email-checkboxes.tsx:128
msgid "Document pending email"
msgstr "E-Mail über ausstehende Dokumente"
@@ -775,8 +757,8 @@ msgstr "Entwurf"
msgid "Drag & drop your PDF here."
msgstr "Ziehen Sie Ihr PDF hierher."
#: packages/ui/primitives/document-flow/add-fields.tsx:1069
#: packages/ui/primitives/template-flow/add-template-fields.tsx:864
#: packages/ui/primitives/document-flow/add-fields.tsx:1065
#: packages/ui/primitives/template-flow/add-template-fields.tsx:860
msgid "Dropdown"
msgstr "Dropdown"
@@ -785,12 +767,12 @@ msgid "Dropdown options"
msgstr "Dropdown-Optionen"
#: packages/lib/constants/document.ts:28
#: packages/ui/primitives/document-flow/add-fields.tsx:886
#: packages/ui/primitives/document-flow/add-fields.tsx:882
#: 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:681
#: packages/ui/primitives/template-flow/add-template-fields.tsx:677
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:471
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:478
msgid "Email"
@@ -812,7 +794,7 @@ msgstr "E-Mail erneut gesendet"
msgid "Email sent"
msgstr "E-Mail gesendet"
#: packages/ui/primitives/document-flow/add-fields.tsx:1134
#: packages/ui/primitives/document-flow/add-fields.tsx:1130
msgid "Empty field"
msgstr "Leeres Feld"
@@ -825,8 +807,8 @@ msgstr "Direktlink-Signierung aktivieren"
msgid "Enable signing order"
msgstr "Aktiviere die Signaturreihenfolge"
#: packages/ui/primitives/document-flow/add-fields.tsx:806
#: packages/ui/primitives/template-flow/add-template-fields.tsx:601
#: packages/ui/primitives/document-flow/add-fields.tsx:802
#: packages/ui/primitives/template-flow/add-template-fields.tsx:597
msgid "Enable Typed Signatures"
msgstr "Aktivieren Sie getippte Unterschriften"
@@ -834,17 +816,17 @@ msgstr "Aktivieren Sie getippte Unterschriften"
msgid "Enter password"
msgstr "Passwort eingeben"
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:258
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:257
#: packages/ui/primitives/pdf-viewer.tsx:166
msgid "Error"
msgstr "Fehler"
#: packages/ui/primitives/document-flow/add-settings.tsx:295
#: packages/ui/primitives/document-flow/add-settings.tsx:283
#: 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:259
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:258
msgid "Failed to save settings."
msgstr "Einstellungen konnten nicht gespeichert werden."
@@ -914,7 +896,7 @@ msgstr "Globale Empfängerauthentifizierung"
msgid "Go Back"
msgstr "Zurück"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:552
#: packages/ui/primitives/signature-pad/signature-pad.tsx:545
msgid "Green"
msgstr "Grün"
@@ -965,7 +947,7 @@ msgstr "Tritt {teamName} auf Documenso bei"
msgid "Label"
msgstr "Beschriftung"
#: packages/ui/primitives/document-flow/add-settings.tsx:187
#: packages/ui/primitives/document-flow/add-settings.tsx:176
#: packages/ui/primitives/template-flow/add-template-settings.tsx:162
msgid "Language"
msgstr "Sprache"
@@ -1001,12 +983,12 @@ msgstr "Nachricht <0>(Optional)</0>"
msgid "Min"
msgstr "Min"
#: packages/ui/primitives/document-flow/add-fields.tsx:912
#: packages/ui/primitives/document-flow/add-fields.tsx:908
#: 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:707
#: packages/ui/primitives/template-flow/add-template-fields.tsx:703
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:506
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:512
msgid "Name"
@@ -1024,8 +1006,8 @@ msgstr "Muss unterzeichnen"
msgid "Needs to view"
msgstr "Muss sehen"
#: packages/ui/primitives/document-flow/add-fields.tsx:697
#: packages/ui/primitives/template-flow/add-template-fields.tsx:520
#: packages/ui/primitives/document-flow/add-fields.tsx:693
#: packages/ui/primitives/template-flow/add-template-fields.tsx:516
msgid "No recipient matching this description was found."
msgstr "Kein passender Empfänger mit dieser Beschreibung gefunden."
@@ -1033,8 +1015,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:712
#: packages/ui/primitives/template-flow/add-template-fields.tsx:535
#: packages/ui/primitives/document-flow/add-fields.tsx:708
#: packages/ui/primitives/template-flow/add-template-fields.tsx:531
msgid "No recipients with this role"
msgstr "Keine Empfänger mit dieser Rolle"
@@ -1062,9 +1044,9 @@ msgstr "Kein Wert gefunden."
msgid "None"
msgstr "Keine"
#: packages/ui/primitives/document-flow/add-fields.tsx:990
#: packages/ui/primitives/document-flow/add-fields.tsx:986
#: packages/ui/primitives/document-flow/types.ts:56
#: packages/ui/primitives/template-flow/add-template-fields.tsx:785
#: packages/ui/primitives/template-flow/add-template-fields.tsx:781
msgid "Number"
msgstr "Nummer"
@@ -1130,15 +1112,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.handler.ts:99
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:111
msgid "Please {recipientActionVerb} this document"
msgstr "Bitte {recipientActionVerb} dieses Dokument"
#: packages/lib/jobs/definitions/emails/send-signing-email.handler.ts:113
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:125
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.handler.ts:105
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:117
msgid "Please {recipientActionVerb} your document"
msgstr "Bitte {recipientActionVerb} dein Dokument"
@@ -1185,28 +1167,24 @@ msgid "Recipient"
msgstr "Empfänger"
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:39
#: packages/ui/primitives/document-flow/add-settings.tsx:269
#: packages/ui/primitives/document-flow/add-settings.tsx:257
#: 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:129
#: packages/ui/components/document/document-email-checkboxes.tsx:89
msgid "Recipient removed email"
msgstr "E-Mail des entfernten Empfängers"
#: packages/ui/components/document/document-email-checkboxes.tsx:51
msgid "Recipient signed email"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx:90
#: packages/ui/components/document/document-email-checkboxes.tsx:50
msgid "Recipient signing request email"
msgstr "E-Mail zur Unterzeichnungsanfrage des Empfängers"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:538
#: packages/ui/primitives/signature-pad/signature-pad.tsx:531
msgid "Red"
msgstr "Rot"
#: packages/ui/primitives/document-flow/add-settings.tsx:383
#: packages/ui/primitives/document-flow/add-settings.tsx:371
#: packages/ui/primitives/template-flow/add-template-settings.tsx:461
msgid "Redirect URL"
msgstr "Weiterleitungs-URL"
@@ -1239,7 +1217,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:1121
#: packages/ui/primitives/document-flow/add-fields.tsx:1117
msgid "Remove"
msgstr "Entfernen"
@@ -1267,11 +1245,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:356
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:355
msgid "Save"
msgstr "Speichern"
#: packages/ui/primitives/template-flow/add-template-fields.tsx:897
#: packages/ui/primitives/template-flow/add-template-fields.tsx:893
msgid "Save Template"
msgstr "Vorlage speichern"
@@ -1307,19 +1285,15 @@ msgstr "Senden"
msgid "Send Document"
msgstr "Dokument senden"
#: packages/ui/components/document/document-email-checkboxes.tsx:198
#: packages/ui/components/document/document-email-checkboxes.tsx:158
msgid "Send document completed email"
msgstr "E-Mail über den Abschluss des Dokuments senden"
#: 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
#: packages/ui/components/document/document-email-checkboxes.tsx:197
msgid "Send document deleted email"
msgstr "E-Mail über das Löschen des Dokuments senden"
#: packages/ui/components/document/document-email-checkboxes.tsx:158
#: packages/ui/components/document/document-email-checkboxes.tsx:118
msgid "Send document pending email"
msgstr "E-Mail über ausstehende Dokumente senden"
@@ -1327,15 +1301,11 @@ 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:119
#: packages/ui/components/document/document-email-checkboxes.tsx:79
msgid "Send recipient removed email"
msgstr "E-Mail über entfernten Empfänger senden"
#: packages/ui/components/document/document-email-checkboxes.tsx:41
msgid "Send recipient signed email"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx:80
#: packages/ui/components/document/document-email-checkboxes.tsx:40
msgid "Send recipient signing request email"
msgstr "E-Mail über Unterzeichnungsanfrage des Empfängers senden"
@@ -1368,11 +1338,11 @@ msgstr "Dokument signieren"
msgid "Sign In"
msgstr "Anmelden"
#: packages/ui/primitives/document-flow/add-fields.tsx:834
#: packages/ui/primitives/document-flow/add-fields.tsx:830
#: 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:629
#: packages/ui/primitives/template-flow/add-template-fields.tsx:625
msgid "Signature"
msgstr "Unterschrift"
@@ -1396,8 +1366,8 @@ msgstr "Unterzeichner müssen eindeutige E-Mails haben"
msgid "Signing"
msgstr "Unterzeichnung"
#: packages/lib/server-only/document/send-completed-email.ts:119
#: packages/lib/server-only/document/send-completed-email.ts:199
#: packages/lib/server-only/document/send-completed-email.ts:114
#: packages/lib/server-only/document/send-completed-email.ts:194
msgid "Signing Complete!"
msgstr "Unterzeichnung abgeschlossen!"
@@ -1451,9 +1421,9 @@ msgstr "Team-E-Mail für {teamName} auf Documenso entfernt"
msgid "Template title"
msgstr "Vorlagentitel"
#: packages/ui/primitives/document-flow/add-fields.tsx:964
#: packages/ui/primitives/document-flow/add-fields.tsx:960
#: packages/ui/primitives/document-flow/types.ts:52
#: packages/ui/primitives/template-flow/add-template-fields.tsx:759
#: packages/ui/primitives/template-flow/add-template-fields.tsx:755
msgid "Text"
msgstr "Text"
@@ -1545,7 +1515,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:768
#: packages/ui/primitives/document-flow/add-fields.tsx:764
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."
@@ -1561,19 +1531,15 @@ 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: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
#: packages/ui/components/document/document-email-checkboxes.tsx:94
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:95
#: packages/ui/components/document/document-email-checkboxes.tsx:55
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:173
#: packages/ui/components/document/document-email-checkboxes.tsx:133
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."
@@ -1585,7 +1551,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:1101
#: packages/ui/primitives/document-flow/add-fields.tsx:1097
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."
@@ -1593,33 +1559,29 @@ 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:252
#: packages/ui/components/document/document-email-checkboxes.tsx:212
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:213
#: packages/ui/components/document/document-email-checkboxes.tsx:173
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:359
#: packages/ui/primitives/document-flow/add-settings.tsx:347
#: packages/ui/primitives/template-flow/add-template-settings.tsx:438
msgid "Time Zone"
msgstr "Zeitzone"
#: packages/ui/primitives/document-flow/add-settings.tsx:166
#: packages/ui/primitives/document-flow/add-settings.tsx:155
msgid "Title"
msgstr "Titel"
#: packages/ui/primitives/document-flow/add-fields.tsx:1084
#: packages/ui/primitives/template-flow/add-template-fields.tsx:877
#: packages/ui/primitives/document-flow/add-fields.tsx:1080
#: packages/ui/primitives/template-flow/add-template-fields.tsx:873
msgid "To proceed further, please set at least one value for the {0} field."
msgstr "Um fortzufahren, legen Sie bitte mindestens einen Wert für das Feld {0} fest."
@@ -1635,7 +1597,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:516
#: packages/ui/primitives/signature-pad/signature-pad.tsx:509
msgid "Upload Signature"
msgstr ""
@@ -1764,7 +1726,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:337
#: packages/lib/server-only/recipient/set-recipients-for-document.ts:327
msgid "You have been removed from a document"
msgstr "Du wurdest von einem Dokument entfernt"
@@ -1772,7 +1734,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.handler.ts:103
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:115
#: 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}."
+140 -147
View File
@@ -92,7 +92,11 @@ 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:59
#: 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
msgid "{numberOfSeats, plural, one {# member} other {# members}}"
msgstr "{numberOfSeats, plural, one {# Mitglied} other {# Mitglieder}}"
@@ -250,21 +254,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:98
#: apps/web/src/app/(dashboard)/documents/[id]/logs/document-logs-data-table.tsx:100
#: 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:116
#: apps/web/src/app/(dashboard)/settings/security/activity/user-security-activity-data-table.tsx:118
#: 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:79
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:177
#: 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)/templates/data-table-templates.tsx:140
#: 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
#: 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
msgid "Actions"
msgstr "Aktionen"
@@ -487,7 +491,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:65
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:57
msgid "An error occurred while moving the template."
msgstr "Ein Fehler ist aufgetreten, während die Vorlage verschoben wurde."
@@ -495,7 +499,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:154
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:152
#: 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
@@ -518,7 +522,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:125
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:123
#: 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
@@ -540,7 +544,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:215
#: apps/web/src/app/(signing)/sign/[token]/checkbox-field.tsx:213
msgid "An error occurred while updating the signature."
msgstr "Ein Fehler ist aufgetreten, während die Unterschrift aktualisiert wurde."
@@ -574,11 +578,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: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/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/signup.tsx:124
#: apps/web/src/components/forms/signup.tsx:138
#: apps/web/src/components/forms/token.tsx:143
@@ -593,11 +597,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:221
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:225
msgid "Any Source"
msgstr "Jede Quelle"
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:201
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:205
msgid "Any Status"
msgstr "Jeder Status"
@@ -700,7 +704,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:486
#: apps/web/src/components/forms/signin.tsx:485
msgid "Backup Code"
msgstr "Backup-Code"
@@ -717,7 +721,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:71
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:61
#: 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
@@ -733,7 +737,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:97
#: apps/web/src/app/(dashboard)/settings/security/activity/user-security-activity-data-table.tsx:99
#: apps/web/src/app/(internal)/%5F%5Fhtmltopdf/audit-log/data-table.tsx:48
msgid "Browser"
msgstr "Browser"
@@ -776,7 +780,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:131
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:119
#: 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
@@ -860,9 +864,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:76
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-recent-activity.tsx:78
#: 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:66
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:68
#: apps/web/src/components/document/document-history-sheet.tsx:133
msgid "Click here to retry"
msgstr "Klicken Sie hier, um es erneut zu versuchen"
@@ -915,7 +919,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:204
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:208
#: apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx:77
#: apps/web/src/components/formatter/document-status.tsx:28
msgid "Completed"
@@ -1145,9 +1149,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: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)/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)/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
@@ -1164,7 +1168,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:76
#: apps/web/src/components/(teams)/tables/pending-user-teams-data-table.tsx:78
msgid "Created on"
msgstr "Erstellt am"
@@ -1183,6 +1187,10 @@ 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"
@@ -1191,7 +1199,7 @@ msgstr "Täglich"
msgid "Dark Mode"
msgstr "Dunkelmodus"
#: apps/web/src/app/(dashboard)/settings/security/activity/user-security-activity-data-table.tsx:68
#: apps/web/src/app/(dashboard)/settings/security/activity/user-security-activity-data-table.tsx:70
#: apps/web/src/app/(signing)/sign/[token]/date-field.tsx:148
msgid "Date"
msgstr "Datum"
@@ -1311,7 +1319,7 @@ msgstr "Konto wird gelöscht..."
msgid "Details"
msgstr "Einzelheiten"
#: apps/web/src/app/(dashboard)/settings/security/activity/user-security-activity-data-table.tsx:73
#: apps/web/src/app/(dashboard)/settings/security/activity/user-security-activity-data-table.tsx:75
#: apps/web/src/app/(internal)/%5F%5Fhtmltopdf/certificate/page.tsx:242
msgid "Device"
msgstr "Gerät"
@@ -1326,8 +1334,8 @@ msgstr "Direkter Link"
msgid "Direct link"
msgstr "Direkter Link"
#: 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
#: 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
msgid "Direct Link"
msgstr "Direkter Link"
@@ -1431,11 +1439,11 @@ msgstr "Dokument abgeschlossen!"
msgid "Document created"
msgstr "Dokument erstellt"
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:127
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:129
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:132
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:134
msgid "Document created using a <0>direct link</0>"
msgstr "Dokument erstellt mit einem <0>direkten Link</0>"
@@ -1590,7 +1598,7 @@ msgstr "Auditprotokolle herunterladen"
msgid "Download Certificate"
msgstr "Zertifikat herunterladen"
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:210
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:214
#: apps/web/src/components/formatter/document-status.tsx:34
msgid "Draft"
msgstr "Entwurf"
@@ -1660,7 +1668,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:339
#: apps/web/src/components/forms/signin.tsx:338
#: apps/web/src/components/forms/signup.tsx:176
msgid "Email"
msgstr "E-Mail"
@@ -1770,12 +1778,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:68
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:56
#: 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: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]/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]/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
@@ -1851,7 +1859,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:371
#: apps/web/src/components/forms/signin.tsx:370
msgid "Forgot your password?"
msgstr "Haben Sie Ihr Passwort vergessen?"
@@ -2031,11 +2039,11 @@ msgstr "Einladung akzeptiert!"
msgid "Invitation declined"
msgstr "Einladung abgelehnt"
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:78
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:80
msgid "Invitation has been deleted"
msgstr "Einladung wurde gelöscht"
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:61
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:63
msgid "Invitation has been resent"
msgstr "Einladung wurde erneut gesendet"
@@ -2055,7 +2063,7 @@ msgstr "Mitglieder einladen"
msgid "Invite team members"
msgstr "Teammitglieder einladen"
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:126
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:128
msgid "Invited At"
msgstr "Eingeladen am"
@@ -2125,7 +2133,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:69
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table.tsx:71
msgid "Last used"
msgstr "Zuletzt verwendet"
@@ -2134,7 +2142,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:115
#: apps/web/src/components/(teams)/tables/current-user-teams-data-table.tsx:117
msgid "Leave"
msgstr "Verlassen"
@@ -2167,7 +2175,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:98
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-recent-activity.tsx:100
msgid "Load older activity"
msgstr "Ältere Aktivitäten laden"
@@ -2182,11 +2190,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:103
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:91
msgid "Loading teams..."
msgstr "Teams werden geladen..."
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-recent-activity.tsx:98
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-recent-activity.tsx:100
msgid "Loading..."
msgstr "Wird geladen..."
@@ -2196,7 +2204,7 @@ msgstr "Wird geladen..."
msgid "Login"
msgstr "Anmelden"
#: apps/web/src/components/(teams)/tables/current-user-teams-data-table.tsx:99
#: apps/web/src/components/(teams)/tables/current-user-teams-data-table.tsx:101
msgid "Manage"
msgstr "Verwalten"
@@ -2244,7 +2252,7 @@ msgstr "Abonnement verwalten"
msgid "Manage subscriptions"
msgstr "Abonnements verwalten"
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:87
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:81
msgid "Manage team subscription."
msgstr "Teamabonnement verwalten."
@@ -2284,8 +2292,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:88
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:111
#: 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
msgid "Member Since"
msgstr "Mitglied seit"
@@ -2301,7 +2309,6 @@ 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"
@@ -2314,7 +2321,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:134
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:122
msgid "Move"
msgstr "Verschieben"
@@ -2322,7 +2329,7 @@ msgstr "Verschieben"
msgid "Move Document to Team"
msgstr "Dokument in Team verschieben"
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:89
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:77
msgid "Move Template to Team"
msgstr "Vorlage in Team verschieben"
@@ -2332,7 +2339,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:134
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:122
msgid "Moving..."
msgstr "Verschieben..."
@@ -2345,7 +2352,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:59
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table.tsx:61
#: 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
@@ -2360,7 +2367,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:74
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table.tsx:76
msgid "Never"
msgstr "Niemals"
@@ -2395,7 +2402,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:43
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:42
msgid "No payment required"
msgstr "Keine Zahlung erforderlich"
@@ -2403,11 +2410,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:106
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-recent-activity.tsx:108
msgid "No recent activity"
msgstr "Keine aktuellen Aktivitäten"
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:101
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:103
msgid "No recent documents"
msgstr "Keine aktuellen Dokumente"
@@ -2442,7 +2449,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:161
#: apps/web/src/components/forms/signin.tsx:160
msgid "Not supported"
msgstr "Nicht unterstützt"
@@ -2528,7 +2535,7 @@ msgstr "Geöffnet"
msgid "Or"
msgstr "Oder"
#: apps/web/src/components/forms/signin.tsx:391
#: apps/web/src/components/forms/signin.tsx:390
msgid "Or continue with"
msgstr "Oder fahren Sie fort mit"
@@ -2539,8 +2546,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:84
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:107
#: 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
msgid "Owner"
msgstr "Besitzer"
@@ -2548,7 +2555,7 @@ msgstr "Besitzer"
msgid "Paid"
msgstr "Bezahlt"
#: apps/web/src/components/forms/signin.tsx:436
#: apps/web/src/components/forms/signin.tsx:435
msgid "Passkey"
msgstr "Passkey"
@@ -2585,14 +2592,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:162
#: apps/web/src/components/forms/signin.tsx:161
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:357
#: apps/web/src/components/forms/signin.tsx:356
#: apps/web/src/components/forms/signup.tsx:192
#: apps/web/src/components/forms/v2/signup.tsx:347
msgid "Password"
@@ -2622,7 +2629,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:207
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:211
#: 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
@@ -2739,7 +2746,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:204
#: apps/web/src/components/forms/signin.tsx:203
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"
@@ -2851,17 +2858,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:55
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-recent-activity.tsx:57
#: 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:45
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:47
msgid "Recent documents"
msgstr "Neueste Dokumente"
#: 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)/documents/data-table.tsx:69
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:120
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:280
msgid "Recipient"
msgstr "Empfänger"
@@ -2922,8 +2929,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:164
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:165
#: 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/forms/avatar-image.tsx:166
msgid "Remove"
msgstr "Entfernen"
@@ -2932,14 +2939,10 @@ msgstr "Entfernen"
msgid "Remove team email"
msgstr "Team-E-Mail entfernen"
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:162
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:164
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"
@@ -2954,7 +2957,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:152
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:154
msgid "Resend"
msgstr "Erneut senden"
@@ -3032,9 +3035,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: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
#: 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
msgid "Role"
msgstr "Rolle"
@@ -3091,7 +3094,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:98
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:86
msgid "Select a team"
msgstr "Wählen Sie ein Team aus"
@@ -3099,7 +3102,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:92
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:80
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."
@@ -3131,7 +3134,7 @@ msgstr "Im Namen des Teams senden"
msgid "Send reminder"
msgstr "Erinnerung senden"
#: apps/web/src/app/(dashboard)/documents/data-table.tsx:59
#: apps/web/src/app/(dashboard)/documents/data-table.tsx:65
msgid "Sender"
msgstr "Absender"
@@ -3230,8 +3233,8 @@ msgid "Sign Here"
msgstr "Hier unterzeichnen"
#: apps/web/src/app/not-found.tsx:29
#: apps/web/src/components/forms/signin.tsx:384
#: apps/web/src/components/forms/signin.tsx:511
#: apps/web/src/components/forms/signin.tsx:383
#: apps/web/src/components/forms/signin.tsx:510
msgid "Sign In"
msgstr "Einloggen"
@@ -3316,8 +3319,8 @@ msgstr "Unterzeichnungszertifikat"
msgid "Signing certificate provided by"
msgstr "Unterzeichnungszertifikat bereitgestellt von"
#: apps/web/src/components/forms/signin.tsx:384
#: apps/web/src/components/forms/signin.tsx:511
#: apps/web/src/components/forms/signin.tsx:383
#: apps/web/src/components/forms/signin.tsx:510
msgid "Signing in..."
msgstr "Anmeldung..."
@@ -3393,8 +3396,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:66
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:83
#: 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)/team-billing-portal-button.tsx:29
msgid "Something went wrong"
msgstr "Etwas ist schiefgelaufen"
@@ -3436,7 +3439,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:134
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:138
msgid "Source"
msgstr "Quelle"
@@ -3446,8 +3449,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:73
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:126
#: 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/(internal)/%5F%5Fhtmltopdf/audit-log/page.tsx:93
#: apps/web/src/components/(teams)/tables/team-billing-invoices-data-table.tsx:73
msgid "Status"
@@ -3489,8 +3492,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:60
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:77
#: 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/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
@@ -3509,8 +3512,8 @@ msgstr "Systemanforderungen"
msgid "System Theme"
msgstr "Systemthema"
#: 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
#: 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
msgid "Team"
msgstr "Team"
@@ -3556,8 +3559,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:107
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:84
#: 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
msgid "Team Member"
msgstr "Teammitglied"
@@ -3631,10 +3634,10 @@ msgstr "Teams"
msgid "Teams restricted"
msgstr "Teams beschränkt"
#: apps/web/src/app/(dashboard)/templates/[id]/edit/template-edit-page-view.tsx:64
#: apps/web/src/app/(dashboard)/templates/[id]/edit/template-edit-page-view.tsx:63
#: 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: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-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.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
@@ -3661,14 +3664,10 @@ 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:50
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:48
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"
@@ -3796,7 +3795,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:51
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:49
msgid "The template has been successfully moved to the selected team."
msgstr "Die Vorlage wurde erfolgreich in das ausgewählte Team verschoben."
@@ -3885,11 +3884,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:148
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:152
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:160
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:164
msgid "This document was created using a direct link."
msgstr "Dieses Dokument wurde mit einem direkten Link erstellt."
@@ -3909,7 +3908,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:201
#: apps/web/src/components/forms/signin.tsx:200
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."
@@ -3917,7 +3916,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:203
#: apps/web/src/components/forms/signin.tsx:202
msgid "This session has expired. Please try again."
msgstr "Diese Sitzung ist abgelaufen. Bitte versuchen Sie es erneut."
@@ -3950,7 +3949,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:71
#: apps/web/src/app/(dashboard)/documents/[id]/logs/document-logs-data-table.tsx:73
#: apps/web/src/app/(internal)/%5F%5Fhtmltopdf/audit-log/data-table.tsx:44
msgid "Time"
msgstr "Zeit"
@@ -3964,8 +3963,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:54
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-documents-table.tsx:111
#: 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)/templates/data-table-templates.tsx:61
msgid "Title"
msgstr "Titel"
@@ -4099,7 +4098,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:449
#: apps/web/src/components/forms/signin.tsx:448
msgid "Two-Factor Authentication"
msgstr "Zwei-Faktor-Authentifizierung"
@@ -4156,7 +4155,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:84
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:86
msgid "Unable to delete invitation. Please try again."
msgstr "Einladung kann nicht gelöscht werden. Bitte versuchen Sie es erneut."
@@ -4172,12 +4171,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:70
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-recent-activity.tsx:72
#: 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:60
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:62
msgid "Unable to load documents"
msgstr "Dokumente können nicht geladen werden"
@@ -4193,7 +4192,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:67
#: apps/web/src/components/(teams)/tables/team-member-invites-data-table.tsx:69
msgid "Unable to resend invitation. Please try again."
msgstr "Einladung kann nicht erneut gesendet werden. Bitte versuchen Sie es erneut."
@@ -4210,8 +4209,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:248
#: apps/web/src/components/forms/signin.tsx:256
#: apps/web/src/components/forms/signin.tsx:247
#: apps/web/src/components/forms/signin.tsx:255
msgid "Unable to sign in"
msgstr "Anmeldung nicht möglich"
@@ -4228,7 +4227,6 @@ 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"
@@ -4269,7 +4267,7 @@ msgstr "Profil aktualisieren"
msgid "Update Recipient"
msgstr "Empfänger aktualisieren"
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:144
#: apps/web/src/components/(teams)/tables/team-members-data-table.tsx:146
msgid "Update role"
msgstr "Rolle aktualisieren"
@@ -4338,12 +4336,12 @@ msgid "Use"
msgstr "Verwenden"
#: apps/web/src/components/forms/2fa/disable-authenticator-app-dialog.tsx:187
#: apps/web/src/components/forms/signin.tsx:506
#: apps/web/src/components/forms/signin.tsx:505
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:504
#: apps/web/src/components/forms/signin.tsx:503
msgid "Use Backup Code"
msgstr "Backup-Code verwenden"
@@ -4351,7 +4349,7 @@ msgstr "Backup-Code verwenden"
msgid "Use Template"
msgstr "Vorlage verwenden"
#: apps/web/src/app/(dashboard)/documents/[id]/logs/document-logs-data-table.tsx:76
#: apps/web/src/app/(dashboard)/documents/[id]/logs/document-logs-data-table.tsx:78
#: apps/web/src/app/(internal)/%5F%5Fhtmltopdf/audit-log/data-table.tsx:45
msgid "User"
msgstr "Benutzer"
@@ -4437,7 +4435,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:155
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:157
msgid "View all related documents"
msgstr "Alle verwandten Dokumente anzeigen"
@@ -4461,7 +4459,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:93
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view-recent-activity.tsx:95
msgid "View more"
msgstr "Mehr anzeigen"
@@ -4596,9 +4594,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:273
#: apps/web/src/components/forms/signin.tsx:288
#: apps/web/src/components/forms/signin.tsx:304
#: apps/web/src/components/forms/signin.tsx:272
#: apps/web/src/components/forms/signin.tsx:287
#: apps/web/src/components/forms/signin.tsx:303
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."
@@ -4799,7 +4797,6 @@ 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"
@@ -4844,10 +4841,6 @@ 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."
@@ -4972,7 +4965,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:88
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:82
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."
@@ -5126,7 +5119,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:72
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/billing/page.tsx:62
msgid "Your subscription is currently active."
msgstr "Ihr Abonnement ist derzeit aktiv."
+83 -121
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.handler.ts:125
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:137
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.handler.ts:118
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:130
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.handler.ts:124
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:136
#: 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,22 +190,6 @@ 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}\"."
@@ -300,7 +284,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.handler.ts:110
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:122
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."
@@ -316,7 +300,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.handler.ts:98
#: packages/lib/jobs/definitions/emails/send-team-member-joined-email.ts:107
msgid "A new member has joined your team"
msgstr "A new member has joined your team"
@@ -340,7 +324,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.handler.ts:87
#: packages/lib/jobs/definitions/emails/send-team-member-left-email.ts:96
msgid "A team member has left {0}"
msgstr "A team member has left {0}"
@@ -375,12 +359,12 @@ msgstr "Accept team transfer request on Documenso"
msgid "Add a document"
msgstr "Add a document"
#: packages/ui/primitives/document-flow/add-settings.tsx:390
#: packages/ui/primitives/document-flow/add-settings.tsx:378
#: 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:302
#: packages/ui/primitives/document-flow/add-settings.tsx:290
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."
@@ -425,13 +409,13 @@ msgstr "Add text to the field"
msgid "Admin"
msgstr "Admin"
#: packages/ui/primitives/document-flow/add-settings.tsx:284
#: packages/ui/primitives/document-flow/add-settings.tsx:272
#: packages/ui/primitives/template-flow/add-template-settings.tsx:367
msgid "Advanced Options"
msgstr "Advanced Options"
#: 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:576
#: packages/ui/primitives/template-flow/add-template-fields.tsx:414
msgid "Advanced settings"
msgstr "Advanced settings"
@@ -483,11 +467,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:531
#: packages/ui/primitives/signature-pad/signature-pad.tsx:524
msgid "Black"
msgstr "Black"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:545
#: packages/ui/primitives/signature-pad/signature-pad.tsx:538
msgid "Blue"
msgstr "Blue"
@@ -503,7 +487,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:357
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:356
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:58
msgid "Cancel"
msgstr "Cancel"
@@ -545,7 +529,7 @@ msgstr "Checkbox values"
msgid "Clear filters"
msgstr "Clear filters"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:565
#: packages/ui/primitives/signature-pad/signature-pad.tsx:558
msgid "Clear Signature"
msgstr "Clear Signature"
@@ -558,7 +542,6 @@ 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"
@@ -573,8 +556,8 @@ msgstr "Completed Document"
msgid "Configure Direct Recipient"
msgstr "Configure Direct Recipient"
#: packages/ui/primitives/document-flow/add-fields.tsx:578
#: packages/ui/primitives/template-flow/add-template-fields.tsx:416
#: packages/ui/primitives/document-flow/add-fields.tsx:577
#: packages/ui/primitives/template-flow/add-template-fields.tsx:415
msgid "Configure the {0} field"
msgstr "Configure the {0} field"
@@ -631,13 +614,13 @@ msgstr "Create account"
msgid "Custom Text"
msgstr "Custom Text"
#: packages/ui/primitives/document-flow/add-fields.tsx:938
#: packages/ui/primitives/document-flow/add-fields.tsx:934
#: packages/ui/primitives/document-flow/types.ts:53
#: packages/ui/primitives/template-flow/add-template-fields.tsx:733
#: packages/ui/primitives/template-flow/add-template-fields.tsx:729
msgid "Date"
msgstr "Date"
#: packages/ui/primitives/document-flow/add-settings.tsx:325
#: packages/ui/primitives/document-flow/add-settings.tsx:313
#: packages/ui/primitives/template-flow/add-template-settings.tsx:408
msgid "Date Format"
msgstr "Date Format"
@@ -654,16 +637,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.handler.ts:140
#: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:149
msgid "Document \"{0}\" - Rejected by {1}"
msgstr "Document \"{0}\" - Rejected by {1}"
#: packages/lib/jobs/definitions/emails/send-rejection-emails.handler.ts:100
#: packages/lib/jobs/definitions/emails/send-rejection-emails.ts:109
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:227
#: packages/ui/primitives/document-flow/add-settings.tsx:216
#: packages/ui/primitives/template-flow/add-template-settings.tsx:202
msgid "Document access"
msgstr "Document access"
@@ -682,8 +665,7 @@ msgstr "Document Cancelled"
msgid "Document completed"
msgstr "Document completed"
#: packages/ui/components/document/document-email-checkboxes.tsx:208
#: packages/ui/components/document/document-email-checkboxes.tsx:286
#: packages/ui/components/document/document-email-checkboxes.tsx:168
msgid "Document completed email"
msgstr "Document completed email"
@@ -692,7 +674,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:585
#: packages/lib/server-only/template/create-document-from-direct-template.ts:574
msgid "Document created from direct template"
msgstr "Document created from direct template"
@@ -704,7 +686,7 @@ msgstr "Document Creation"
msgid "Document deleted"
msgstr "Document deleted"
#: packages/ui/components/document/document-email-checkboxes.tsx:247
#: packages/ui/components/document/document-email-checkboxes.tsx:207
msgid "Document deleted email"
msgstr "Document deleted email"
@@ -729,7 +711,7 @@ msgstr "Document moved to team"
msgid "Document opened"
msgstr "Document opened"
#: packages/ui/components/document/document-email-checkboxes.tsx:168
#: packages/ui/components/document/document-email-checkboxes.tsx:128
msgid "Document pending email"
msgstr "Document pending email"
@@ -770,8 +752,8 @@ msgstr "Draft"
msgid "Drag & drop your PDF here."
msgstr "Drag & drop your PDF here."
#: packages/ui/primitives/document-flow/add-fields.tsx:1069
#: packages/ui/primitives/template-flow/add-template-fields.tsx:864
#: packages/ui/primitives/document-flow/add-fields.tsx:1065
#: packages/ui/primitives/template-flow/add-template-fields.tsx:860
msgid "Dropdown"
msgstr "Dropdown"
@@ -780,12 +762,12 @@ msgid "Dropdown options"
msgstr "Dropdown options"
#: packages/lib/constants/document.ts:28
#: packages/ui/primitives/document-flow/add-fields.tsx:886
#: packages/ui/primitives/document-flow/add-fields.tsx:882
#: 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:681
#: packages/ui/primitives/template-flow/add-template-fields.tsx:677
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:471
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:478
msgid "Email"
@@ -807,7 +789,7 @@ msgstr "Email resent"
msgid "Email sent"
msgstr "Email sent"
#: packages/ui/primitives/document-flow/add-fields.tsx:1134
#: packages/ui/primitives/document-flow/add-fields.tsx:1130
msgid "Empty field"
msgstr "Empty field"
@@ -820,8 +802,8 @@ msgstr "Enable Direct Link Signing"
msgid "Enable signing order"
msgstr "Enable signing order"
#: packages/ui/primitives/document-flow/add-fields.tsx:806
#: packages/ui/primitives/template-flow/add-template-fields.tsx:601
#: packages/ui/primitives/document-flow/add-fields.tsx:802
#: packages/ui/primitives/template-flow/add-template-fields.tsx:597
msgid "Enable Typed Signatures"
msgstr "Enable Typed Signatures"
@@ -829,17 +811,17 @@ msgstr "Enable Typed Signatures"
msgid "Enter password"
msgstr "Enter password"
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:258
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:257
#: packages/ui/primitives/pdf-viewer.tsx:166
msgid "Error"
msgstr "Error"
#: packages/ui/primitives/document-flow/add-settings.tsx:295
#: packages/ui/primitives/document-flow/add-settings.tsx:283
#: 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:259
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:258
msgid "Failed to save settings."
msgstr "Failed to save settings."
@@ -909,7 +891,7 @@ msgstr "Global recipient action authentication"
msgid "Go Back"
msgstr "Go Back"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:552
#: packages/ui/primitives/signature-pad/signature-pad.tsx:545
msgid "Green"
msgstr "Green"
@@ -960,7 +942,7 @@ msgstr "Join {teamName} on Documenso"
msgid "Label"
msgstr "Label"
#: packages/ui/primitives/document-flow/add-settings.tsx:187
#: packages/ui/primitives/document-flow/add-settings.tsx:176
#: packages/ui/primitives/template-flow/add-template-settings.tsx:162
msgid "Language"
msgstr "Language"
@@ -996,12 +978,12 @@ msgstr "Message <0>(Optional)</0>"
msgid "Min"
msgstr "Min"
#: packages/ui/primitives/document-flow/add-fields.tsx:912
#: packages/ui/primitives/document-flow/add-fields.tsx:908
#: 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:707
#: packages/ui/primitives/template-flow/add-template-fields.tsx:703
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:506
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:512
msgid "Name"
@@ -1019,8 +1001,8 @@ msgstr "Needs to sign"
msgid "Needs to view"
msgstr "Needs to view"
#: packages/ui/primitives/document-flow/add-fields.tsx:697
#: packages/ui/primitives/template-flow/add-template-fields.tsx:520
#: packages/ui/primitives/document-flow/add-fields.tsx:693
#: packages/ui/primitives/template-flow/add-template-fields.tsx:516
msgid "No recipient matching this description was found."
msgstr "No recipient matching this description was found."
@@ -1028,8 +1010,8 @@ msgstr "No recipient matching this description was found."
msgid "No recipients"
msgstr "No recipients"
#: packages/ui/primitives/document-flow/add-fields.tsx:712
#: packages/ui/primitives/template-flow/add-template-fields.tsx:535
#: packages/ui/primitives/document-flow/add-fields.tsx:708
#: packages/ui/primitives/template-flow/add-template-fields.tsx:531
msgid "No recipients with this role"
msgstr "No recipients with this role"
@@ -1057,9 +1039,9 @@ msgstr "No value found."
msgid "None"
msgstr "None"
#: packages/ui/primitives/document-flow/add-fields.tsx:990
#: packages/ui/primitives/document-flow/add-fields.tsx:986
#: packages/ui/primitives/document-flow/types.ts:56
#: packages/ui/primitives/template-flow/add-template-fields.tsx:785
#: packages/ui/primitives/template-flow/add-template-fields.tsx:781
msgid "Number"
msgstr "Number"
@@ -1125,15 +1107,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.handler.ts:99
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:111
msgid "Please {recipientActionVerb} this document"
msgstr "Please {recipientActionVerb} this document"
#: packages/lib/jobs/definitions/emails/send-signing-email.handler.ts:113
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:125
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.handler.ts:105
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:117
msgid "Please {recipientActionVerb} your document"
msgstr "Please {recipientActionVerb} your document"
@@ -1180,28 +1162,24 @@ msgid "Recipient"
msgstr "Recipient"
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:39
#: packages/ui/primitives/document-flow/add-settings.tsx:269
#: packages/ui/primitives/document-flow/add-settings.tsx:257
#: 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:129
#: packages/ui/components/document/document-email-checkboxes.tsx:89
msgid "Recipient removed email"
msgstr "Recipient removed email"
#: 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
#: packages/ui/components/document/document-email-checkboxes.tsx:50
msgid "Recipient signing request email"
msgstr "Recipient signing request email"
#: packages/ui/primitives/signature-pad/signature-pad.tsx:538
#: packages/ui/primitives/signature-pad/signature-pad.tsx:531
msgid "Red"
msgstr "Red"
#: packages/ui/primitives/document-flow/add-settings.tsx:383
#: packages/ui/primitives/document-flow/add-settings.tsx:371
#: packages/ui/primitives/template-flow/add-template-settings.tsx:461
msgid "Redirect URL"
msgstr "Redirect URL"
@@ -1234,7 +1212,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:1121
#: packages/ui/primitives/document-flow/add-fields.tsx:1117
msgid "Remove"
msgstr "Remove"
@@ -1262,11 +1240,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:356
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:355
msgid "Save"
msgstr "Save"
#: packages/ui/primitives/template-flow/add-template-fields.tsx:897
#: packages/ui/primitives/template-flow/add-template-fields.tsx:893
msgid "Save Template"
msgstr "Save Template"
@@ -1302,19 +1280,15 @@ msgstr "Send"
msgid "Send Document"
msgstr "Send Document"
#: packages/ui/components/document/document-email-checkboxes.tsx:198
#: packages/ui/components/document/document-email-checkboxes.tsx:158
msgid "Send document completed email"
msgstr "Send document completed email"
#: 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
#: packages/ui/components/document/document-email-checkboxes.tsx:197
msgid "Send document deleted email"
msgstr "Send document deleted email"
#: packages/ui/components/document/document-email-checkboxes.tsx:158
#: packages/ui/components/document/document-email-checkboxes.tsx:118
msgid "Send document pending email"
msgstr "Send document pending email"
@@ -1322,15 +1296,11 @@ 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:119
#: packages/ui/components/document/document-email-checkboxes.tsx:79
msgid "Send recipient removed email"
msgstr "Send recipient removed email"
#: 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
#: packages/ui/components/document/document-email-checkboxes.tsx:40
msgid "Send recipient signing request email"
msgstr "Send recipient signing request email"
@@ -1363,11 +1333,11 @@ msgstr "Sign Document"
msgid "Sign In"
msgstr "Sign In"
#: packages/ui/primitives/document-flow/add-fields.tsx:834
#: packages/ui/primitives/document-flow/add-fields.tsx:830
#: 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:629
#: packages/ui/primitives/template-flow/add-template-fields.tsx:625
msgid "Signature"
msgstr "Signature"
@@ -1391,8 +1361,8 @@ msgstr "Signers must have unique emails"
msgid "Signing"
msgstr "Signing"
#: packages/lib/server-only/document/send-completed-email.ts:119
#: packages/lib/server-only/document/send-completed-email.ts:199
#: packages/lib/server-only/document/send-completed-email.ts:114
#: packages/lib/server-only/document/send-completed-email.ts:194
msgid "Signing Complete!"
msgstr "Signing Complete!"
@@ -1446,9 +1416,9 @@ msgstr "Team email removed for {teamName} on Documenso"
msgid "Template title"
msgstr "Template title"
#: packages/ui/primitives/document-flow/add-fields.tsx:964
#: packages/ui/primitives/document-flow/add-fields.tsx:960
#: packages/ui/primitives/document-flow/types.ts:52
#: packages/ui/primitives/template-flow/add-template-fields.tsx:759
#: packages/ui/primitives/template-flow/add-template-fields.tsx:755
msgid "Text"
msgstr "Text"
@@ -1540,7 +1510,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:768
#: packages/ui/primitives/document-flow/add-fields.tsx:764
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."
@@ -1556,19 +1526,15 @@ 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: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
#: packages/ui/components/document/document-email-checkboxes.tsx:94
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:95
#: packages/ui/components/document/document-email-checkboxes.tsx:55
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:173
#: packages/ui/components/document/document-email-checkboxes.tsx:133
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."
@@ -1580,7 +1546,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:1101
#: packages/ui/primitives/document-flow/add-fields.tsx:1097
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."
@@ -1588,33 +1554,29 @@ 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:252
#: packages/ui/components/document/document-email-checkboxes.tsx:212
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:213
#: packages/ui/components/document/document-email-checkboxes.tsx:173
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:359
#: packages/ui/primitives/document-flow/add-settings.tsx:347
#: packages/ui/primitives/template-flow/add-template-settings.tsx:438
msgid "Time Zone"
msgstr "Time Zone"
#: packages/ui/primitives/document-flow/add-settings.tsx:166
#: packages/ui/primitives/document-flow/add-settings.tsx:155
msgid "Title"
msgstr "Title"
#: packages/ui/primitives/document-flow/add-fields.tsx:1084
#: packages/ui/primitives/template-flow/add-template-fields.tsx:877
#: packages/ui/primitives/document-flow/add-fields.tsx:1080
#: packages/ui/primitives/template-flow/add-template-fields.tsx:873
msgid "To proceed further, please set at least one value for the {0} field."
msgstr "To proceed further, please set at least one value for the {0} field."
@@ -1630,7 +1592,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:516
#: packages/ui/primitives/signature-pad/signature-pad.tsx:509
msgid "Upload Signature"
msgstr "Upload Signature"
@@ -1759,7 +1721,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:337
#: packages/lib/server-only/recipient/set-recipients-for-document.ts:327
msgid "You have been removed from a document"
msgstr "You have been removed from a document"
@@ -1767,7 +1729,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.handler.ts:103
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:115
#: 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