Compare commits
9 Commits
v1.7.1-rc.
...
feat/save-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb7732a203 | ||
|
|
7ea7740b88 | ||
|
|
ced583a525 | ||
|
|
ca0e96d4c0 | ||
|
|
1d73a0f9e6 | ||
|
|
479131822e | ||
|
|
b885aae511 | ||
|
|
da9287440b | ||
|
|
bed8cbd651 |
48
.cursorrules
48
.cursorrules
@@ -1,48 +0,0 @@
|
||||
Code Style and Structure:
|
||||
- Write concise, technical TypeScript code with accurate examples
|
||||
- Use functional and declarative programming patterns; avoid classes
|
||||
- Prefer iteration and modularization over code duplication
|
||||
- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError)
|
||||
- Structure files: exported component, subcomponents, helpers, static content, types
|
||||
|
||||
Naming Conventions:
|
||||
- Use lowercase with dashes for directories (e.g., components/auth-wizard)
|
||||
- Favor named exports for components
|
||||
|
||||
TypeScript Usage:
|
||||
- Use TypeScript for all code; prefer interfaces over types
|
||||
- Avoid enums; use maps instead
|
||||
- Use functional components with TypeScript interfaces
|
||||
|
||||
Syntax and Formatting:
|
||||
- Use the "function" keyword for pure functions
|
||||
- Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements
|
||||
- Use declarative JSX
|
||||
|
||||
Error Handling and Validation:
|
||||
- Prioritize error handling: handle errors and edge cases early
|
||||
- Use early returns and guard clauses
|
||||
- Implement proper error logging and user-friendly messages
|
||||
- Use Zod for form validation
|
||||
- Model expected errors as return values in Server Actions
|
||||
- Use error boundaries for unexpected errors
|
||||
|
||||
UI and Styling:
|
||||
- Use Shadcn UI, Radix, and Tailwind Aria for components and styling
|
||||
- Implement responsive design with Tailwind CSS; use a mobile-first approach
|
||||
|
||||
Performance Optimization:
|
||||
- Minimize 'use client', 'useEffect', and 'setState'; favor React Server Components (RSC)
|
||||
- Wrap client components in Suspense with fallback
|
||||
- Use dynamic loading for non-critical components
|
||||
- Optimize images: use WebP format, include size data, implement lazy loading
|
||||
|
||||
Key Conventions:
|
||||
- Use 'nuqs' for URL search parameter state management
|
||||
- Optimize Web Vitals (LCP, CLS, FID)
|
||||
- Limit 'use client':
|
||||
- Favor server components and Next.js SSR
|
||||
- Use only for Web API access in small components
|
||||
- Avoid for data fetching or state management
|
||||
|
||||
Follow Next.js docs for Data Fetching, Rendering, and Routing
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@documenso/marketing",
|
||||
"version": "1.7.1-rc.1",
|
||||
"version": "1.7.1-rc.0",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
|
||||
4
apps/marketing/process-env.d.ts
vendored
4
apps/marketing/process-env.d.ts
vendored
@@ -2,8 +2,8 @@ declare namespace NodeJS {
|
||||
export interface ProcessEnv {
|
||||
NEXT_PUBLIC_WEBAPP_URL?: string;
|
||||
NEXT_PUBLIC_MARKETING_URL?: string;
|
||||
NEXT_PRIVATE_INTERNAL_WEBAPP_URL?:string;
|
||||
|
||||
NEXT_PRIVATE_INTERNAL_WEBAPP_URL?: string;
|
||||
|
||||
NEXT_PRIVATE_DATABASE_URL: string;
|
||||
|
||||
NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID: string;
|
||||
|
||||
@@ -168,7 +168,6 @@ export const SinglePlayerClient = () => {
|
||||
sendStatus: 'NOT_SENT',
|
||||
role: 'SIGNER',
|
||||
authOptions: null,
|
||||
signingOrder: null,
|
||||
};
|
||||
|
||||
const onFileDrop = async (file: File) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@documenso/web",
|
||||
"version": "1.7.1-rc.1",
|
||||
"version": "1.7.1-rc.0",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
|
||||
2
apps/web/process-env.d.ts
vendored
2
apps/web/process-env.d.ts
vendored
@@ -2,7 +2,7 @@ declare namespace NodeJS {
|
||||
export interface ProcessEnv {
|
||||
NEXT_PUBLIC_WEBAPP_URL?: string;
|
||||
NEXT_PUBLIC_MARKETING_URL?: string;
|
||||
NEXT_PRIVATE_INTERNAL_WEBAPP_URL?:string;
|
||||
NEXT_PRIVATE_INTERNAL_WEBAPP_URL?: string;
|
||||
|
||||
NEXT_PRIVATE_DATABASE_URL: string;
|
||||
|
||||
|
||||
@@ -12,12 +12,10 @@ import { getDocumentById } from '@documenso/lib/server-only/document/get-documen
|
||||
import { getServerComponentFlag } from '@documenso/lib/server-only/feature-flags/get-server-component-feature-flag';
|
||||
import { getFieldsForDocument } from '@documenso/lib/server-only/field/get-fields-for-document';
|
||||
import { getRecipientsForDocument } from '@documenso/lib/server-only/recipient/get-recipients-for-document';
|
||||
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
|
||||
import { symmetricDecrypt } from '@documenso/lib/universal/crypto';
|
||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
||||
import { DocumentStatus } from '@documenso/prisma/client';
|
||||
import type { Team, TeamEmail } from '@documenso/prisma/client';
|
||||
import { TeamMemberRole } from '@documenso/prisma/client';
|
||||
import { Badge } from '@documenso/ui/primitives/badge';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||
@@ -41,7 +39,7 @@ export type DocumentPageViewProps = {
|
||||
params: {
|
||||
id: string;
|
||||
};
|
||||
team?: Team & { teamEmail: TeamEmail | null } & { currentTeamMember: { role: TeamMemberRole } };
|
||||
team?: Team & { teamEmail: TeamEmail | null };
|
||||
};
|
||||
|
||||
export const DocumentPageView = async ({ params, team }: DocumentPageViewProps) => {
|
||||
@@ -64,35 +62,11 @@ export const DocumentPageView = async ({ params, team }: DocumentPageViewProps)
|
||||
teamId: team?.id,
|
||||
}).catch(() => null);
|
||||
|
||||
if (document?.teamId && !team?.url) {
|
||||
redirect(documentRootPath);
|
||||
}
|
||||
|
||||
const documentVisibility = document?.visibility;
|
||||
const currentTeamMemberRole = team?.currentTeamMember?.role;
|
||||
const isRecipient = document?.Recipient.find((recipient) => recipient.email === user.email);
|
||||
let canAccessDocument = true;
|
||||
|
||||
if (team && !isRecipient) {
|
||||
canAccessDocument = match([documentVisibility, currentTeamMemberRole])
|
||||
.with([DocumentVisibility.EVERYONE, TeamMemberRole.ADMIN], () => true)
|
||||
.with([DocumentVisibility.EVERYONE, TeamMemberRole.MANAGER], () => true)
|
||||
.with([DocumentVisibility.EVERYONE, TeamMemberRole.MEMBER], () => true)
|
||||
.with([DocumentVisibility.MANAGER_AND_ABOVE, TeamMemberRole.ADMIN], () => true)
|
||||
.with([DocumentVisibility.MANAGER_AND_ABOVE, TeamMemberRole.MANAGER], () => true)
|
||||
.with([DocumentVisibility.ADMIN, TeamMemberRole.ADMIN], () => true)
|
||||
.otherwise(() => false);
|
||||
}
|
||||
|
||||
const isDocumentHistoryEnabled = await getServerComponentFlag(
|
||||
'app_document_page_view_history_sheet',
|
||||
);
|
||||
|
||||
if (!document || !document.documentData || (team && !canAccessDocument)) {
|
||||
redirect(documentRootPath);
|
||||
}
|
||||
|
||||
if (team && !canAccessDocument) {
|
||||
if (!document || !document.documentData) {
|
||||
redirect(documentRootPath);
|
||||
}
|
||||
|
||||
|
||||
@@ -85,20 +85,6 @@ export const EditDocumentForm = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: setSigningOrderForDocument } =
|
||||
trpc.document.setSigningOrderForDocument.useMutation({
|
||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
onSuccess: (newData) => {
|
||||
utils.document.getDocumentWithDetailsById.setData(
|
||||
{
|
||||
id: initialDocument.id,
|
||||
teamId: team?.id,
|
||||
},
|
||||
(oldData) => ({ ...(oldData || initialDocument), ...newData, id: Number(newData.id) }),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: addFields } = trpc.field.addFields.useMutation({
|
||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
onSuccess: (newFields) => {
|
||||
@@ -191,7 +177,6 @@ export const EditDocumentForm = ({
|
||||
data: {
|
||||
title: data.title,
|
||||
externalId: data.externalId || null,
|
||||
visibility: data.visibility,
|
||||
globalAccessAuth: data.globalAccessAuth ?? null,
|
||||
globalActionAuth: data.globalActionAuth ?? null,
|
||||
},
|
||||
@@ -219,22 +204,15 @@ export const EditDocumentForm = ({
|
||||
|
||||
const onAddSignersFormSubmit = async (data: TAddSignersFormSchema) => {
|
||||
try {
|
||||
await Promise.all([
|
||||
setSigningOrderForDocument({
|
||||
documentId: document.id,
|
||||
signingOrder: data.signingOrder,
|
||||
}),
|
||||
|
||||
addSigners({
|
||||
documentId: document.id,
|
||||
teamId: team?.id,
|
||||
signers: data.signers.map((signer) => ({
|
||||
...signer,
|
||||
// Explicitly set to null to indicate we want to remove auth if required.
|
||||
actionAuth: signer.actionAuth || null,
|
||||
})),
|
||||
}),
|
||||
]);
|
||||
await addSigners({
|
||||
documentId: document.id,
|
||||
teamId: team?.id,
|
||||
signers: data.signers.map((signer) => ({
|
||||
...signer,
|
||||
// Explicitly set to null to indicate we want to remove auth if required.
|
||||
actionAuth: signer.actionAuth || null,
|
||||
})),
|
||||
});
|
||||
|
||||
// Router refresh is here to clear the router cache for when navigating to /documents.
|
||||
router.refresh();
|
||||
@@ -361,7 +339,6 @@ export const EditDocumentForm = ({
|
||||
key={recipients.length}
|
||||
documentFlow={documentFlow.settings}
|
||||
document={document}
|
||||
currentTeamMemberRole={team?.currentTeamMember?.role}
|
||||
recipients={recipients}
|
||||
fields={fields}
|
||||
isDocumentEnterprise={isDocumentEnterprise}
|
||||
@@ -372,12 +349,13 @@ export const EditDocumentForm = ({
|
||||
<AddSignersFormPartial
|
||||
key={recipients.length}
|
||||
documentFlow={documentFlow.signers}
|
||||
document={document}
|
||||
recipients={recipients}
|
||||
signingOrder={document.documentMeta?.signingOrder}
|
||||
fields={fields}
|
||||
isDocumentEnterprise={isDocumentEnterprise}
|
||||
onSubmit={onAddSignersFormSubmit}
|
||||
isDocumentPdfLoaded={isDocumentPdfLoaded}
|
||||
teamId={team?.id}
|
||||
/>
|
||||
|
||||
<AddFieldsFormPartial
|
||||
|
||||
@@ -3,17 +3,14 @@ import { redirect } from 'next/navigation';
|
||||
|
||||
import { Plural, Trans } from '@lingui/macro';
|
||||
import { ChevronLeft, Users2 } from 'lucide-react';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||
import { DOCUMENSO_ENCRYPTION_KEY } from '@documenso/lib/constants/crypto';
|
||||
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
||||
import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
|
||||
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
|
||||
import { symmetricDecrypt } from '@documenso/lib/universal/crypto';
|
||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
||||
import type { Team } from '@documenso/prisma/client';
|
||||
import { TeamMemberRole } from '@documenso/prisma/client';
|
||||
import { DocumentStatus as InternalDocumentStatus } from '@documenso/prisma/client';
|
||||
|
||||
import { EditDocumentForm } from '~/app/(dashboard)/documents/[id]/edit-document';
|
||||
@@ -24,7 +21,7 @@ export type DocumentEditPageViewProps = {
|
||||
params: {
|
||||
id: string;
|
||||
};
|
||||
team?: Team & { currentTeamMember: { role: TeamMemberRole } };
|
||||
team?: Team;
|
||||
};
|
||||
|
||||
export const DocumentEditPageView = async ({ params, team }: DocumentEditPageViewProps) => {
|
||||
@@ -46,34 +43,10 @@ export const DocumentEditPageView = async ({ params, team }: DocumentEditPageVie
|
||||
teamId: team?.id,
|
||||
}).catch(() => null);
|
||||
|
||||
if (document?.teamId && !team?.url) {
|
||||
redirect(documentRootPath);
|
||||
}
|
||||
|
||||
const documentVisibility = document?.visibility;
|
||||
const currentTeamMemberRole = team?.currentTeamMember?.role;
|
||||
const isRecipient = document?.Recipient.find((recipient) => recipient.email === user.email);
|
||||
let canAccessDocument = true;
|
||||
|
||||
if (!isRecipient) {
|
||||
canAccessDocument = match([documentVisibility, currentTeamMemberRole])
|
||||
.with([DocumentVisibility.EVERYONE, TeamMemberRole.ADMIN], () => true)
|
||||
.with([DocumentVisibility.EVERYONE, TeamMemberRole.MANAGER], () => true)
|
||||
.with([DocumentVisibility.EVERYONE, TeamMemberRole.MEMBER], () => true)
|
||||
.with([DocumentVisibility.MANAGER_AND_ABOVE, TeamMemberRole.ADMIN], () => true)
|
||||
.with([DocumentVisibility.MANAGER_AND_ABOVE, TeamMemberRole.MANAGER], () => true)
|
||||
.with([DocumentVisibility.ADMIN, TeamMemberRole.ADMIN], () => true)
|
||||
.otherwise(() => false);
|
||||
}
|
||||
|
||||
if (!document) {
|
||||
redirect(documentRootPath);
|
||||
}
|
||||
|
||||
if (team && !canAccessDocument) {
|
||||
redirect(documentRootPath);
|
||||
}
|
||||
|
||||
if (document.status === InternalDocumentStatus.COMPLETED) {
|
||||
redirect(`${documentRootPath}/${documentId}`);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { GetStatsInput } from '@documenso/lib/server-only/document/get-stat
|
||||
import { getStats } from '@documenso/lib/server-only/document/get-stats';
|
||||
import { parseToIntegerArray } from '@documenso/lib/utils/params';
|
||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
||||
import type { Team, TeamEmail, TeamMemberRole } from '@documenso/prisma/client';
|
||||
import type { Team, TeamEmail } from '@documenso/prisma/client';
|
||||
import { isExtendedDocumentStatus } from '@documenso/prisma/guards/is-extended-document-status';
|
||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@documenso/ui/primitives/avatar';
|
||||
@@ -33,7 +33,7 @@ export type DocumentsPageViewProps = {
|
||||
perPage?: string;
|
||||
senderIds?: string;
|
||||
};
|
||||
team?: Team & { teamEmail?: TeamEmail | null } & { currentTeamMember?: { role: TeamMemberRole } };
|
||||
team?: Team & { teamEmail?: TeamEmail | null };
|
||||
};
|
||||
|
||||
export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPageViewProps) => {
|
||||
@@ -47,7 +47,6 @@ export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPa
|
||||
const currentTeam = team
|
||||
? { id: team.id, url: team.url, teamEmail: team.teamEmail?.email }
|
||||
: undefined;
|
||||
const currentTeamMemberRole = team?.currentTeamMember?.role;
|
||||
|
||||
const getStatOptions: GetStatsInput = {
|
||||
user,
|
||||
@@ -59,9 +58,6 @@ export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPa
|
||||
teamId: team.id,
|
||||
teamEmail: team.teamEmail?.email,
|
||||
senderIds,
|
||||
currentTeamMemberRole,
|
||||
currentUserEmail: user.email,
|
||||
userId: user.id,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ export const UserPasskeysDataTable = () => {
|
||||
cell: ({ row }) =>
|
||||
row.original.lastUsedAt
|
||||
? DateTime.fromJSDate(row.original.lastUsedAt).toRelative()
|
||||
: _(msg`Never`),
|
||||
: msg`Never`,
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
|
||||
@@ -103,19 +103,6 @@ export const EditTemplateForm = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: setSigningOrderForTemplate } =
|
||||
trpc.template.setSigningOrderForTemplate.useMutation({
|
||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
onSuccess: (newData) => {
|
||||
utils.template.getTemplateWithDetailsById.setData(
|
||||
{
|
||||
id: initialTemplate.id,
|
||||
},
|
||||
(oldData) => ({ ...(oldData || initialTemplate), ...newData }),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: addTemplateFields } = trpc.field.addTemplateFields.useMutation({
|
||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
onSuccess: (newData) => {
|
||||
@@ -173,19 +160,11 @@ export const EditTemplateForm = ({
|
||||
data: TAddTemplatePlacholderRecipientsFormSchema,
|
||||
) => {
|
||||
try {
|
||||
await Promise.all([
|
||||
setSigningOrderForTemplate({
|
||||
templateId: template.id,
|
||||
teamId: team?.id,
|
||||
signingOrder: data.signingOrder,
|
||||
}),
|
||||
|
||||
addTemplateSigners({
|
||||
templateId: template.id,
|
||||
teamId: team?.id,
|
||||
signers: data.signers,
|
||||
}),
|
||||
]);
|
||||
await addTemplateSigners({
|
||||
templateId: template.id,
|
||||
teamId: team?.id,
|
||||
signers: data.signers,
|
||||
});
|
||||
|
||||
// Router refresh is here to clear the router cache for when navigating to /documents.
|
||||
router.refresh();
|
||||
@@ -283,11 +262,11 @@ export const EditTemplateForm = ({
|
||||
documentFlow={documentFlow.signers}
|
||||
recipients={recipients}
|
||||
fields={fields}
|
||||
signingOrder={template.templateMeta?.signingOrder}
|
||||
templateDirectLink={template.directLink}
|
||||
onSubmit={onAddTemplatePlaceholderFormSubmit}
|
||||
isEnterprise={isEnterprise}
|
||||
isDocumentPdfLoaded={isDocumentPdfLoaded}
|
||||
template={template}
|
||||
/>
|
||||
|
||||
<AddTemplateFieldsFormPartial
|
||||
|
||||
@@ -267,14 +267,14 @@ export const CheckboxField = ({
|
||||
)}
|
||||
|
||||
{field.inserted && (
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<div className="flex flex-col gap-y-2">
|
||||
{values?.map((item: { id: number; value: string; checked: boolean }, index: number) => {
|
||||
const itemValue = item.value || `empty-value-${item.id}`;
|
||||
|
||||
return (
|
||||
<div key={index} className="flex items-center gap-x-1.5">
|
||||
<Checkbox
|
||||
className="h-3 w-3"
|
||||
className="h-4 w-4"
|
||||
checkClassName="text-white"
|
||||
id={`checkbox-${index}`}
|
||||
checked={field.customText
|
||||
@@ -283,7 +283,7 @@ export const CheckboxField = ({
|
||||
disabled={isLoading}
|
||||
onCheckedChange={() => void handleCheckboxOptionClick(item)}
|
||||
/>
|
||||
<Label htmlFor={`checkbox-${index}`} className="text-xs">
|
||||
<Label htmlFor={`checkbox-${index}`}>
|
||||
{item.value.includes('empty-value-') ? '' : item.value}
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
@@ -150,7 +150,7 @@ export const DateField = ({
|
||||
)}
|
||||
|
||||
{field.inserted && (
|
||||
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.625rem,1cqw,0.825rem)] duration-200">
|
||||
<p className="text-muted-foreground dark:text-background/80 text-sm duration-200">
|
||||
{localDateString}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -189,10 +189,7 @@ export const DropdownField = ({
|
||||
},
|
||||
)}
|
||||
>
|
||||
<SelectValue
|
||||
className="text-[clamp(0.625rem,1cqw,0.825rem)]"
|
||||
placeholder={`${_(msg`Select`)}`}
|
||||
/>
|
||||
<SelectValue placeholder={`${_(msg`Select`)}`} />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="w-full ring-0 focus:ring-0" position="popper">
|
||||
{parsedFieldMeta?.values?.map((item, index) => (
|
||||
@@ -206,7 +203,7 @@ export const DropdownField = ({
|
||||
)}
|
||||
|
||||
{field.inserted && (
|
||||
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.625rem,1cqw,0.825rem)] duration-200">
|
||||
<p className="text-muted-foreground dark:text-background/80 flex items-center justify-center gap-x-1 duration-200">
|
||||
{field.customText}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -128,7 +128,7 @@ export const EmailField = ({ field, recipient, onSignField, onUnsignField }: Ema
|
||||
)}
|
||||
|
||||
{field.inserted && (
|
||||
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.625rem,1cqw,0.825rem)] duration-200">
|
||||
<p className="text-muted-foreground dark:text-background/80 truncate duration-200">
|
||||
{field.customText}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -29,16 +29,9 @@ export type SigningFormProps = {
|
||||
recipient: Recipient;
|
||||
fields: Field[];
|
||||
redirectUrl?: string | null;
|
||||
isRecipientsTurn: boolean;
|
||||
};
|
||||
|
||||
export const SigningForm = ({
|
||||
document,
|
||||
recipient,
|
||||
fields,
|
||||
redirectUrl,
|
||||
isRecipientsTurn,
|
||||
}: SigningFormProps) => {
|
||||
export const SigningForm = ({ document, recipient, fields, redirectUrl }: SigningFormProps) => {
|
||||
const router = useRouter();
|
||||
const analytics = useAnalytics();
|
||||
const { data: session } = useSession();
|
||||
@@ -157,7 +150,6 @@ export const SigningForm = ({
|
||||
fields={fields}
|
||||
fieldsValidated={fieldsValidated}
|
||||
role={recipient.role}
|
||||
disabled={!isRecipientsTurn}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -221,7 +213,6 @@ export const SigningForm = ({
|
||||
fields={fields}
|
||||
fieldsValidated={fieldsValidated}
|
||||
role={recipient.role}
|
||||
disabled={!isRecipientsTurn}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -131,7 +131,7 @@ export const InitialsField = ({
|
||||
)}
|
||||
|
||||
{field.inserted && (
|
||||
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.625rem,1cqw,0.825rem)] duration-200">
|
||||
<p className="text-muted-foreground dark:text-background/80 truncate duration-200">
|
||||
{field.customText}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -172,7 +172,7 @@ export const NameField = ({ field, recipient, onSignField, onUnsignField }: Name
|
||||
)}
|
||||
|
||||
{field.inserted && (
|
||||
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.625rem,1cqw,0.825rem)] duration-200">
|
||||
<p className="text-muted-foreground dark:text-background/80 truncate duration-200">
|
||||
{field.customText}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -259,7 +259,7 @@ export const NumberField = ({ field, recipient, onSignField, onUnsignField }: Nu
|
||||
)}
|
||||
|
||||
{field.inserted && (
|
||||
<p className="text-muted-foreground dark:text-background/80 text-[clamp(0.625rem,1cqw,0.825rem)] duration-200">
|
||||
<p className="text-muted-foreground dark:text-background/80 flex items-center justify-center gap-x-1 duration-200">
|
||||
{field.customText}
|
||||
</p>
|
||||
)}
|
||||
@@ -267,7 +267,7 @@ export const NumberField = ({ field, recipient, onSignField, onUnsignField }: Nu
|
||||
<Dialog open={showRadioModal} onOpenChange={setShowRadioModal}>
|
||||
<DialogContent>
|
||||
<DialogTitle>
|
||||
{parsedFieldMeta?.label ? parsedFieldMeta?.label : <Trans>Number</Trans>}
|
||||
{parsedFieldMeta?.label ? parsedFieldMeta?.label : <Trans>Add number</Trans>}
|
||||
</DialogTitle>
|
||||
|
||||
<div>
|
||||
|
||||
@@ -9,7 +9,6 @@ import { isRecipientAuthorized } from '@documenso/lib/server-only/document/is-re
|
||||
import { viewedDocument } from '@documenso/lib/server-only/document/viewed-document';
|
||||
import { getCompletedFieldsForToken } from '@documenso/lib/server-only/field/get-completed-fields-for-token';
|
||||
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
|
||||
import { getIsRecipientsTurnToSign } from '@documenso/lib/server-only/recipient/get-is-recipient-turn';
|
||||
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
|
||||
import { getRecipientSignatures } from '@documenso/lib/server-only/recipient/get-recipient-signatures';
|
||||
import { getUserByEmail } from '@documenso/lib/server-only/user/get-user-by-email';
|
||||
@@ -43,12 +42,6 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
|
||||
|
||||
const requestMetadata = extractNextHeaderRequestMetadata(requestHeaders);
|
||||
|
||||
const isRecipientsTurn = await getIsRecipientsTurnToSign({ token });
|
||||
|
||||
if (!isRecipientsTurn) {
|
||||
return redirect(`/sign/${token}/waiting`);
|
||||
}
|
||||
|
||||
const [document, fields, recipient, completedFields] = await Promise.all([
|
||||
getDocumentAndSenderByToken({
|
||||
token,
|
||||
@@ -153,7 +146,6 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
|
||||
document={document}
|
||||
fields={fields}
|
||||
completedFields={completedFields}
|
||||
isRecipientsTurn={isRecipientsTurn}
|
||||
/>
|
||||
</DocumentAuthProvider>
|
||||
</SigningProvider>
|
||||
|
||||
@@ -173,16 +173,16 @@ export const RadioField = ({ field, recipient, onSignField, onUnsignField }: Rad
|
||||
)}
|
||||
|
||||
{field.inserted && (
|
||||
<RadioGroup className="gap-y-1">
|
||||
<RadioGroup>
|
||||
{values?.map((item, index) => (
|
||||
<div key={index} className="flex items-center gap-x-1.5">
|
||||
<RadioGroupItem
|
||||
className="h-3 w-3"
|
||||
className=""
|
||||
value={item.value}
|
||||
id={`option-${index}`}
|
||||
checked={item.value === field.customText}
|
||||
/>
|
||||
<Label htmlFor={`option-${index}`} className="text-xs">
|
||||
<Label htmlFor={`option-${index}`}>
|
||||
{item.value.includes('empty-value-') ? '' : item.value}
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,6 @@ export type SignDialogProps = {
|
||||
fieldsValidated: () => void | Promise<void>;
|
||||
onSignatureComplete: () => void | Promise<void>;
|
||||
role: RecipientRole;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const SignDialog = ({
|
||||
@@ -33,7 +32,6 @@ export const SignDialog = ({
|
||||
fieldsValidated,
|
||||
onSignatureComplete,
|
||||
role,
|
||||
disabled = false,
|
||||
}: SignDialogProps) => {
|
||||
const [showDialog, setShowDialog] = useState(false);
|
||||
const truncatedTitle = truncateTitle(documentTitle);
|
||||
@@ -56,7 +54,6 @@ export const SignDialog = ({
|
||||
size="lg"
|
||||
onClick={fieldsValidated}
|
||||
loading={isSubmitting}
|
||||
disabled={disabled}
|
||||
>
|
||||
{isComplete ? <Trans>Complete</Trans> : <Trans>Next field</Trans>}
|
||||
</Button>
|
||||
|
||||
@@ -128,7 +128,7 @@ export const SigningFieldContainer = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn('[container-type:size]', type === 'Checkbox' ? 'group' : '')}>
|
||||
<div className={cn(type === 'Checkbox' ? 'group' : '')}>
|
||||
<FieldRootContainer field={field}>
|
||||
{!field.inserted && !loading && !readOnlyField && (
|
||||
<button
|
||||
|
||||
@@ -39,7 +39,6 @@ export type SigningPageViewProps = {
|
||||
recipient: Recipient;
|
||||
fields: Field[];
|
||||
completedFields: CompletedField[];
|
||||
isRecipientsTurn: boolean;
|
||||
};
|
||||
|
||||
export const SigningPageView = ({
|
||||
@@ -47,7 +46,6 @@ export const SigningPageView = ({
|
||||
recipient,
|
||||
fields,
|
||||
completedFields,
|
||||
isRecipientsTurn,
|
||||
}: SigningPageViewProps) => {
|
||||
const { documentData, documentMeta } = document;
|
||||
|
||||
@@ -101,7 +99,6 @@ export const SigningPageView = ({
|
||||
recipient={recipient}
|
||||
fields={fields}
|
||||
redirectUrl={documentMeta?.redirectUrl}
|
||||
isRecipientsTurn={isRecipientsTurn}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -253,7 +253,7 @@ export const TextField = ({ field, recipient, onSignField, onUnsignField }: Text
|
||||
>
|
||||
<span className="flex items-center justify-center gap-x-1">
|
||||
<Type />
|
||||
{fieldDisplayName || <Trans>Text</Trans>}
|
||||
{fieldDisplayName || <Trans>Add text</Trans>}
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
@@ -269,7 +269,7 @@ export const TextField = ({ field, recipient, onSignField, onUnsignField }: Text
|
||||
<Dialog open={showCustomTextModal} onOpenChange={setShowCustomTextModal}>
|
||||
<DialogContent>
|
||||
<DialogTitle>
|
||||
{parsedFieldMeta?.label ? parsedFieldMeta?.label : <Trans>Text</Trans>}
|
||||
{parsedFieldMeta?.label ? parsedFieldMeta?.label : <Trans>Add Text</Trans>}
|
||||
</DialogTitle>
|
||||
|
||||
<div>
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
import Link from 'next/link';
|
||||
import { notFound, redirect } from 'next/navigation';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
|
||||
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
||||
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
||||
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
||||
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
|
||||
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
|
||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
||||
import type { Team } from '@documenso/prisma/client';
|
||||
import { DocumentStatus } from '@documenso/prisma/client';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
|
||||
type WaitingForTurnToSignPageProps = {
|
||||
params: { token?: string };
|
||||
};
|
||||
|
||||
export default async function WaitingForTurnToSignPage({
|
||||
params: { token },
|
||||
}: WaitingForTurnToSignPageProps) {
|
||||
setupI18nSSR();
|
||||
|
||||
if (!token) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const { user } = await getServerComponentSession();
|
||||
|
||||
const [document, recipient] = await Promise.all([
|
||||
getDocumentAndSenderByToken({ token }).catch(() => null),
|
||||
getRecipientByToken({ token }).catch(() => null),
|
||||
]);
|
||||
|
||||
if (!document || !recipient) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
if (document.status === DocumentStatus.COMPLETED) {
|
||||
return redirect(`/sign/${token}/complete`);
|
||||
}
|
||||
|
||||
let isOwnerOrTeamMember = false;
|
||||
|
||||
let team: Team | null = null;
|
||||
|
||||
if (user) {
|
||||
isOwnerOrTeamMember = await getDocumentById({
|
||||
id: document.id,
|
||||
userId: user.id,
|
||||
teamId: document.teamId ?? undefined,
|
||||
})
|
||||
.then((document) => !!document)
|
||||
.catch(() => false);
|
||||
|
||||
if (document.teamId) {
|
||||
team = await getTeamById({
|
||||
userId: user.id,
|
||||
teamId: document.teamId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col items-center justify-center px-4 py-12 sm:px-6 lg:px-8">
|
||||
<div className="w-full max-w-md text-center">
|
||||
<h2 className="tracking-tigh text-3xl font-bold">
|
||||
<Trans>Waiting for Your Turn</Trans>
|
||||
</h2>
|
||||
|
||||
<p className="text-muted-foreground mt-2 text-sm">
|
||||
<Trans>
|
||||
It's currently not your turn to sign. You will receive an email with instructions once
|
||||
it's your turn to sign the document.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<p className="text-muted-foreground mt-4 text-sm">
|
||||
<Trans>Please check your email for updates.</Trans>
|
||||
</p>
|
||||
|
||||
<div className="mt-4">
|
||||
{isOwnerOrTeamMember ? (
|
||||
<Button variant="link" asChild>
|
||||
<Link href={`${formatDocumentsPath(team?.url)}/${document.id}`}>
|
||||
<Trans>Were you trying to edit this document instead?</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant="link" asChild>
|
||||
<Link href="/documents">Return Home</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -335,23 +335,6 @@ export const DocumentHistorySheet = ({
|
||||
]}
|
||||
/>
|
||||
))
|
||||
.with(
|
||||
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_VISIBILITY_UPDATED },
|
||||
({ data }) => (
|
||||
<DocumentHistorySheetChanges
|
||||
values={[
|
||||
{
|
||||
key: 'Old',
|
||||
value: data.from,
|
||||
},
|
||||
{
|
||||
key: 'New',
|
||||
value: data.to,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
)
|
||||
.exhaustive()}
|
||||
|
||||
{isUserDetailsVisible && (
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import type { GetTeamResponse } from '@documenso/lib/server-only/team/get-team';
|
||||
import type { Team } from '@documenso/prisma/client';
|
||||
|
||||
interface TeamProviderProps {
|
||||
children: React.ReactNode;
|
||||
team: GetTeamResponse;
|
||||
team: Team;
|
||||
}
|
||||
|
||||
const TeamContext = createContext<GetTeamResponse | null>(null);
|
||||
const TeamContext = createContext<Team | null>(null);
|
||||
|
||||
export const useCurrentTeam = () => {
|
||||
const context = useContext(TeamContext);
|
||||
|
||||
132
package-lock.json
generated
132
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@documenso/root",
|
||||
"version": "1.7.1-rc.1",
|
||||
"version": "1.7.1-rc.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@documenso/root",
|
||||
"version": "1.7.1-rc.1",
|
||||
"version": "1.7.1-rc.0",
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
"packages/*"
|
||||
@@ -80,7 +80,7 @@
|
||||
},
|
||||
"apps/marketing": {
|
||||
"name": "@documenso/marketing",
|
||||
"version": "1.7.1-rc.1",
|
||||
"version": "1.7.1-rc.0",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@documenso/assets": "*",
|
||||
@@ -424,7 +424,7 @@
|
||||
},
|
||||
"apps/web": {
|
||||
"name": "@documenso/web",
|
||||
"version": "1.7.1-rc.1",
|
||||
"version": "1.7.1-rc.0",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@documenso/api": "*",
|
||||
@@ -3433,34 +3433,6 @@
|
||||
"react-dom": "^16 || ^17 || ^18"
|
||||
}
|
||||
},
|
||||
"node_modules/@hello-pangea/dnd": {
|
||||
"version": "16.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@hello-pangea/dnd/-/dnd-16.6.0.tgz",
|
||||
"integrity": "sha512-vfZ4GydqbtUPXSLfAvKvXQ6xwRzIjUSjVU0Sx+70VOhc2xx6CdmJXJ8YhH70RpbTUGjxctslQTHul9sIOxCfFQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.24.1",
|
||||
"css-box-model": "^1.2.1",
|
||||
"memoize-one": "^6.0.0",
|
||||
"raf-schd": "^4.0.3",
|
||||
"react-redux": "^8.1.3",
|
||||
"redux": "^4.2.1",
|
||||
"use-memo-one": "^1.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.5 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@hello-pangea/dnd/node_modules/redux": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.9.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@hexagon/base64": {
|
||||
"version": "1.1.28",
|
||||
"resolved": "https://registry.npmjs.org/@hexagon/base64/-/base64-1.1.28.tgz",
|
||||
@@ -11076,16 +11048,6 @@
|
||||
"@types/unist": "^2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hoist-non-react-statics": {
|
||||
"version": "3.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
|
||||
"integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/http-cache-semantics": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
|
||||
@@ -14701,15 +14663,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/css-box-model": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
|
||||
"integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tiny-invariant": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/css.escape": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
|
||||
@@ -19303,15 +19256,6 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/hoist-non-react-statics": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"react-is": "^16.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hosted-git-info": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
|
||||
@@ -22695,12 +22639,6 @@
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/memoize-one": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/meow": {
|
||||
"version": "8.1.2",
|
||||
"resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz",
|
||||
@@ -27748,12 +27686,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/raf-schd": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
|
||||
"integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/railroad-diagrams": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz",
|
||||
@@ -28591,51 +28523,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-property/-/react-property-2.0.0.tgz",
|
||||
"integrity": "sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw=="
|
||||
},
|
||||
"node_modules/react-redux": {
|
||||
"version": "8.1.3",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz",
|
||||
"integrity": "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.1",
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"@types/use-sync-external-store": "^0.0.3",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"react-is": "^18.0.0",
|
||||
"use-sync-external-store": "^1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8 || ^17.0 || ^18.0",
|
||||
"@types/react-dom": "^16.8 || ^17.0 || ^18.0",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-native": ">=0.59",
|
||||
"redux": "^4 || ^5.0.0-beta.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
},
|
||||
"redux": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-redux/node_modules/react-is": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-remove-scroll": {
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz",
|
||||
@@ -34195,15 +34082,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/use-memo-one": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz",
|
||||
"integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sidecar": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",
|
||||
@@ -36950,7 +36828,6 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@documenso/lib": "*",
|
||||
"@hello-pangea/dnd": "^16.6.0",
|
||||
"@hookform/resolvers": "^3.3.0",
|
||||
"@lingui/macro": "^4.11.3",
|
||||
"@lingui/react": "^4.11.3",
|
||||
@@ -36997,7 +36874,6 @@
|
||||
"react-hook-form": "^7.45.4",
|
||||
"react-pdf": "7.7.3",
|
||||
"react-rnd": "^10.4.1",
|
||||
"remeda": "^1.27.1",
|
||||
"tailwind-merge": "^1.12.0",
|
||||
"tailwindcss-animate": "^1.0.5",
|
||||
"ts-pattern": "^5.0.5",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "1.7.1-rc.1",
|
||||
"version": "1.7.1-rc.0",
|
||||
"scripts": {
|
||||
"build": "turbo run build",
|
||||
"build:web": "turbo run build --filter=@documenso/web",
|
||||
|
||||
@@ -293,7 +293,6 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
||||
timezone,
|
||||
dateFormat: dateFormat?.value,
|
||||
redirectUrl: body.meta.redirectUrl,
|
||||
signingOrder: body.meta.signingOrder,
|
||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
||||
});
|
||||
|
||||
@@ -326,7 +325,6 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
||||
email: recipient.email,
|
||||
token: recipient.token,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
|
||||
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
|
||||
})),
|
||||
@@ -498,7 +496,6 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
||||
email: recipient.email,
|
||||
token: recipient.token,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
|
||||
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
|
||||
})),
|
||||
@@ -591,7 +588,6 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
||||
email: recipient.email,
|
||||
token: recipient.token,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
|
||||
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
|
||||
})),
|
||||
@@ -717,7 +713,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
||||
|
||||
createRecipient: authenticatedMiddleware(async (args, user, team) => {
|
||||
const { id: documentId } = args.params;
|
||||
const { name, email, role, authOptions, signingOrder } = args.body;
|
||||
const { name, email, role, authOptions } = args.body;
|
||||
|
||||
const document = await getDocumentById({
|
||||
id: Number(documentId),
|
||||
@@ -766,16 +762,11 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
||||
userId: user.id,
|
||||
teamId: team?.id,
|
||||
recipients: [
|
||||
...recipients.map(({ email, name }) => ({
|
||||
email,
|
||||
name,
|
||||
role,
|
||||
})),
|
||||
...recipients,
|
||||
{
|
||||
email,
|
||||
name,
|
||||
role,
|
||||
signingOrder,
|
||||
actionAuth: authOptions?.actionAuth ?? null,
|
||||
},
|
||||
],
|
||||
@@ -808,7 +799,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
||||
|
||||
updateRecipient: authenticatedMiddleware(async (args, user, team) => {
|
||||
const { id: documentId, recipientId } = args.params;
|
||||
const { name, email, role, authOptions, signingOrder } = args.body;
|
||||
const { name, email, role, authOptions } = args.body;
|
||||
|
||||
const document = await getDocumentById({
|
||||
id: Number(documentId),
|
||||
@@ -842,7 +833,6 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
||||
email,
|
||||
name,
|
||||
role,
|
||||
signingOrder,
|
||||
actionAuth: authOptions?.actionAuth,
|
||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
||||
}).catch(() => null);
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import {
|
||||
DocumentDataType,
|
||||
DocumentSigningOrder,
|
||||
FieldType,
|
||||
ReadStatus,
|
||||
RecipientRole,
|
||||
@@ -104,7 +103,6 @@ export const ZCreateDocumentMutationSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
email: z.string().email().min(1),
|
||||
role: z.nativeEnum(RecipientRole).optional().default(RecipientRole.SIGNER),
|
||||
signingOrder: z.number().nullish(),
|
||||
}),
|
||||
),
|
||||
meta: z
|
||||
@@ -125,7 +123,6 @@ export const ZCreateDocumentMutationSchema = z.object({
|
||||
enum: DATE_FORMATS.map((format) => format.value),
|
||||
}),
|
||||
redirectUrl: z.string(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||
})
|
||||
.partial(),
|
||||
authOptions: z
|
||||
@@ -150,7 +147,6 @@ export const ZCreateDocumentMutationResponseSchema = z.object({
|
||||
email: z.string().email().min(1),
|
||||
token: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().nullish(),
|
||||
|
||||
signingUrl: z.string(),
|
||||
}),
|
||||
@@ -169,7 +165,6 @@ export const ZCreateDocumentFromTemplateMutationSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
email: z.string().email().min(1),
|
||||
role: z.nativeEnum(RecipientRole).optional().default(RecipientRole.SIGNER),
|
||||
signingOrder: z.number().nullish(),
|
||||
}),
|
||||
),
|
||||
meta: z
|
||||
@@ -179,7 +174,6 @@ export const ZCreateDocumentFromTemplateMutationSchema = z.object({
|
||||
timezone: z.string(),
|
||||
dateFormat: z.string(),
|
||||
redirectUrl: z.string(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||
})
|
||||
.partial()
|
||||
.optional(),
|
||||
@@ -206,7 +200,6 @@ export const ZCreateDocumentFromTemplateMutationResponseSchema = z.object({
|
||||
email: z.string().email().min(1),
|
||||
token: z.string(),
|
||||
role: z.nativeEnum(RecipientRole).optional().default(RecipientRole.SIGNER),
|
||||
signingOrder: z.number().nullish(),
|
||||
|
||||
signingUrl: z.string(),
|
||||
}),
|
||||
@@ -226,7 +219,6 @@ export const ZGenerateDocumentFromTemplateMutationSchema = z.object({
|
||||
id: z.number(),
|
||||
name: z.string().optional(),
|
||||
email: z.string().email().min(1),
|
||||
signingOrder: z.number().nullish(),
|
||||
}),
|
||||
)
|
||||
.refine(
|
||||
@@ -245,7 +237,6 @@ export const ZGenerateDocumentFromTemplateMutationSchema = z.object({
|
||||
timezone: z.string(),
|
||||
dateFormat: z.string(),
|
||||
redirectUrl: ZUrlSchema,
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||
})
|
||||
.partial()
|
||||
.optional(),
|
||||
@@ -272,7 +263,6 @@ export const ZGenerateDocumentFromTemplateMutationResponseSchema = z.object({
|
||||
email: z.string().email().min(1),
|
||||
token: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().nullish(),
|
||||
|
||||
signingUrl: z.string(),
|
||||
}),
|
||||
@@ -287,7 +277,6 @@ export const ZCreateRecipientMutationSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
email: z.string().email().min(1),
|
||||
role: z.nativeEnum(RecipientRole).optional().default(RecipientRole.SIGNER),
|
||||
signingOrder: z.number().nullish(),
|
||||
authOptions: z
|
||||
.object({
|
||||
actionAuth: ZRecipientActionAuthTypesSchema.optional(),
|
||||
@@ -316,7 +305,6 @@ export const ZSuccessfulRecipientResponseSchema = z.object({
|
||||
email: z.string().email().min(1),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().nullish(),
|
||||
token: z.string(),
|
||||
// !: Not used for now
|
||||
// expired: z.string(),
|
||||
@@ -434,7 +422,6 @@ export const ZTemplateMetaSchema = z.object({
|
||||
dateFormat: z.string().nullish(),
|
||||
templateId: z.number(),
|
||||
redirectUrl: z.string().nullish(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder).nullish().default(DocumentSigningOrder.PARALLEL),
|
||||
});
|
||||
|
||||
export const ZTemplateSchema = z.object({
|
||||
@@ -456,7 +443,6 @@ export const ZRecipientSchema = z.object({
|
||||
email: z.string().email().min(1),
|
||||
name: z.string(),
|
||||
token: z.string(),
|
||||
signingOrder: z.number().nullish(),
|
||||
documentDeletedAt: z.date().nullish(),
|
||||
expired: z.date().nullish(),
|
||||
signedAt: z.date().nullish(),
|
||||
@@ -510,7 +496,6 @@ export const ZTemplateWithDataSchema = ZTemplateSchema.extend({
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
signingOrder: true,
|
||||
authOptions: true,
|
||||
role: true,
|
||||
}).array(),
|
||||
|
||||
@@ -41,10 +41,11 @@ test.describe('[EE_ONLY]', () => {
|
||||
// Add 2 signers.
|
||||
await page.getByPlaceholder('Email').fill('recipient1@documenso.com');
|
||||
await page.getByPlaceholder('Name').fill('Recipient 1');
|
||||
|
||||
await page.getByRole('button', { name: 'Add Signer' }).click();
|
||||
await page.getByLabel('Email').nth(1).fill('recipient2@documenso.com');
|
||||
await page.getByLabel('Name').nth(1).fill('Recipient 2');
|
||||
await page
|
||||
.getByRole('textbox', { name: 'Email', exact: true })
|
||||
.fill('recipient2@documenso.com');
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).nth(1).fill('Recipient 2');
|
||||
|
||||
// Display advanced settings.
|
||||
await page.getByLabel('Show advanced settings').check();
|
||||
@@ -76,11 +77,9 @@ test('[DOCUMENT_FLOW]: add signers', async ({ page }) => {
|
||||
// Add 2 signers.
|
||||
await page.getByPlaceholder('Email').fill('recipient1@documenso.com');
|
||||
await page.getByPlaceholder('Name').fill('Recipient 1');
|
||||
|
||||
await page.getByRole('button', { name: 'Add Signer' }).click();
|
||||
|
||||
await page.getByLabel('Email').nth(1).fill('recipient2@documenso.com');
|
||||
await page.getByLabel('Name').nth(1).fill('Recipient 2');
|
||||
await page.getByRole('textbox', { name: 'Email', exact: true }).fill('recipient2@documenso.com');
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).nth(1).fill('Recipient 2');
|
||||
|
||||
// Advanced settings should not be visible for non EE users.
|
||||
await expect(page.getByLabel('Show advanced settings')).toBeHidden();
|
||||
|
||||
@@ -4,13 +4,7 @@ import path from 'node:path';
|
||||
|
||||
import { getRecipientByEmail } from '@documenso/lib/server-only/recipient/get-recipient-by-email';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import {
|
||||
DocumentSigningOrder,
|
||||
DocumentStatus,
|
||||
FieldType,
|
||||
RecipientRole,
|
||||
SigningStatus,
|
||||
} from '@documenso/prisma/client';
|
||||
import { DocumentStatus, FieldType, RecipientRole } from '@documenso/prisma/client';
|
||||
import {
|
||||
seedBlankDocument,
|
||||
seedPendingDocumentWithFullFields,
|
||||
@@ -143,9 +137,8 @@ test('[DOCUMENT_FLOW]: should be able to create a document with multiple recipie
|
||||
await page.getByPlaceholder('Email').fill('user1@example.com');
|
||||
await page.getByPlaceholder('Name').fill('User 1');
|
||||
await page.getByRole('button', { name: 'Add Signer' }).click();
|
||||
|
||||
await page.getByLabel('Email').nth(1).fill('user2@example.com');
|
||||
await page.getByLabel('Name').nth(1).fill('User 2');
|
||||
await page.getByRole('textbox', { name: 'Email', exact: true }).fill('user2@example.com');
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).nth(1).fill('User 2');
|
||||
|
||||
await page.getByRole('button', { name: 'Continue' }).click();
|
||||
|
||||
@@ -224,20 +217,20 @@ test('[DOCUMENT_FLOW]: should be able to create a document with multiple recipie
|
||||
await page.getByPlaceholder('Name').fill('User 1');
|
||||
await page.getByRole('button', { name: 'Add Signer' }).click();
|
||||
|
||||
await page.getByLabel('Email').nth(1).fill('user2@example.com');
|
||||
await page.getByLabel('Name').nth(1).fill('User 2');
|
||||
await page.getByRole('textbox', { name: 'Email', exact: true }).fill('user2@example.com');
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).nth(1).fill('User 2');
|
||||
await page.locator('button[role="combobox"]').nth(1).click();
|
||||
await page.getByLabel('Receives copy').click();
|
||||
await page.getByRole('button', { name: 'Add Signer' }).click();
|
||||
|
||||
await page.getByLabel('Email').nth(2).fill('user3@example.com');
|
||||
await page.getByLabel('Name').nth(2).fill('User 3');
|
||||
await page.getByRole('textbox', { name: 'Email', exact: true }).nth(1).fill('user3@example.com');
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).nth(2).fill('User 3');
|
||||
await page.locator('button[role="combobox"]').nth(2).click();
|
||||
await page.getByLabel('Needs to approve').click();
|
||||
await page.getByRole('button', { name: 'Add Signer' }).click();
|
||||
|
||||
await page.getByLabel('Email').nth(3).fill('user4@example.com');
|
||||
await page.getByLabel('Name').nth(3).fill('User 4');
|
||||
await page.getByRole('textbox', { name: 'Email', exact: true }).nth(2).fill('user4@example.com');
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).nth(3).fill('User 4');
|
||||
await page.locator('button[role="combobox"]').nth(3).click();
|
||||
await page.getByLabel('Needs to view').click();
|
||||
|
||||
@@ -510,163 +503,3 @@ test('[DOCUMENT_FLOW]: should be able to sign a document with custom date', asyn
|
||||
expect(completedStatus).toBe(DocumentStatus.COMPLETED);
|
||||
}).toPass();
|
||||
});
|
||||
|
||||
test('[DOCUMENT_FLOW]: should be able to create and sign a document with 3 recipients in sequential order', async ({
|
||||
page,
|
||||
}) => {
|
||||
const user = await seedUser();
|
||||
const document = await seedBlankDocument(user);
|
||||
|
||||
await apiSignin({
|
||||
page,
|
||||
email: user.email,
|
||||
redirectPath: `/documents/${document.id}/edit`,
|
||||
});
|
||||
|
||||
const documentTitle = `Sequential-Signing-${Date.now()}.pdf`;
|
||||
|
||||
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
|
||||
await page.getByLabel('Title').fill(documentTitle);
|
||||
await page.getByRole('button', { name: 'Continue' }).click();
|
||||
|
||||
await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible();
|
||||
await page.getByLabel('Enable signing order').check();
|
||||
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
if (i > 1) {
|
||||
await page.getByRole('button', { name: 'Add Signer' }).click();
|
||||
}
|
||||
await page
|
||||
.getByPlaceholder('Email')
|
||||
.nth(i - 1)
|
||||
.fill(`user${i}@example.com`);
|
||||
await page
|
||||
.getByPlaceholder('Name')
|
||||
.nth(i - 1)
|
||||
.fill(`User ${i}`);
|
||||
}
|
||||
|
||||
await page.getByRole('button', { name: 'Continue' }).click();
|
||||
|
||||
await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();
|
||||
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
if (i > 1) {
|
||||
await page.getByText(`User ${i} (user${i}@example.com)`).click();
|
||||
}
|
||||
await page.getByRole('button', { name: 'Signature' }).click();
|
||||
await page.locator('canvas').click({
|
||||
position: {
|
||||
x: 100,
|
||||
y: 100 * i,
|
||||
},
|
||||
});
|
||||
await page.getByText(`User ${i} (user${i}@example.com)`).click();
|
||||
}
|
||||
|
||||
await page.getByRole('button', { name: 'Continue' }).click();
|
||||
|
||||
await expect(page.getByRole('heading', { name: 'Add Subject' })).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Send' }).click();
|
||||
|
||||
await page.waitForURL('/documents');
|
||||
|
||||
await expect(page.getByRole('link', { name: documentTitle })).toBeVisible();
|
||||
|
||||
const createdDocument = await prisma.document.findFirst({
|
||||
where: { title: documentTitle },
|
||||
include: { Recipient: true },
|
||||
});
|
||||
|
||||
expect(createdDocument).not.toBeNull();
|
||||
expect(createdDocument?.Recipient.length).toBe(3);
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const recipient = createdDocument?.Recipient.find(
|
||||
(r) => r.email === `user${i + 1}@example.com`,
|
||||
);
|
||||
expect(recipient).not.toBeNull();
|
||||
|
||||
const fields = await prisma.field.findMany({
|
||||
where: { recipientId: recipient?.id, documentId: createdDocument?.id },
|
||||
});
|
||||
const recipientField = fields[0];
|
||||
|
||||
if (i > 0) {
|
||||
const previousRecipient = await prisma.recipient.findFirst({
|
||||
where: { email: `user${i}@example.com`, documentId: createdDocument?.id },
|
||||
});
|
||||
|
||||
expect(previousRecipient?.signingStatus).toBe(SigningStatus.SIGNED);
|
||||
}
|
||||
|
||||
await page.goto(`/sign/${recipient?.token}`);
|
||||
await expect(page.getByRole('heading', { name: 'Sign Document' })).toBeVisible();
|
||||
|
||||
await page.locator(`#field-${recipientField.id}`).getByRole('button').click();
|
||||
|
||||
const canvas = page.locator('canvas#signature');
|
||||
const box = await canvas.boundingBox();
|
||||
if (box) {
|
||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(box.x + box.width / 4, box.y + box.height / 4);
|
||||
await page.mouse.up();
|
||||
}
|
||||
|
||||
await page.getByRole('button', { name: 'Sign', exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Complete' }).click();
|
||||
await page.getByRole('button', { name: 'Sign' }).click();
|
||||
|
||||
await page.waitForURL(`/sign/${recipient?.token}/complete`);
|
||||
await expect(page.getByText('Document Signed')).toBeVisible();
|
||||
|
||||
const updatedRecipient = await prisma.recipient.findFirst({
|
||||
where: { id: recipient?.id },
|
||||
});
|
||||
|
||||
expect(updatedRecipient?.signingStatus).toBe(SigningStatus.SIGNED);
|
||||
}
|
||||
|
||||
// Wait for the document to be signed.
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
const finalDocument = await prisma.document.findFirst({
|
||||
where: { id: createdDocument?.id },
|
||||
});
|
||||
|
||||
expect(finalDocument?.status).toBe(DocumentStatus.COMPLETED);
|
||||
});
|
||||
|
||||
test('[DOCUMENT_FLOW]: should prevent out-of-order signing in sequential mode', async ({
|
||||
page,
|
||||
}) => {
|
||||
const user = await seedUser();
|
||||
|
||||
const { document, recipients } = await seedPendingDocumentWithFullFields({
|
||||
owner: user,
|
||||
recipients: ['user1@example.com', 'user2@example.com', 'user3@example.com'],
|
||||
fields: [FieldType.SIGNATURE],
|
||||
recipientsCreateOptions: [{ signingOrder: 1 }, { signingOrder: 2 }, { signingOrder: 3 }],
|
||||
updateDocumentOptions: {
|
||||
documentMeta: {
|
||||
create: {
|
||||
signingOrder: DocumentSigningOrder.SEQUENTIAL,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const pendingRecipient = recipients.find((r) => r.signingOrder === 2);
|
||||
|
||||
await page.goto(`/sign/${pendingRecipient?.token}`);
|
||||
|
||||
await expect(page).toHaveURL(`/sign/${pendingRecipient?.token}/waiting`);
|
||||
|
||||
const activeRecipient = recipients.find((r) => r.signingOrder === 1);
|
||||
|
||||
await page.goto(`/sign/${activeRecipient?.token}`);
|
||||
|
||||
await expect(page).not.toHaveURL(`/sign/${activeRecipient?.token}/waiting`);
|
||||
await expect(page.getByRole('heading', { name: 'Sign Document' })).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { DocumentStatus, TeamMemberRole } from '@documenso/prisma/client';
|
||||
import { DocumentStatus } from '@documenso/prisma/client';
|
||||
import { seedDocuments, seedTeamDocuments } from '@documenso/prisma/seed/documents';
|
||||
import { seedTeam, seedTeamEmail, seedTeamMember } from '@documenso/prisma/seed/teams';
|
||||
import { seedTeamEmail } from '@documenso/prisma/seed/teams';
|
||||
import { seedUser } from '@documenso/prisma/seed/users';
|
||||
|
||||
import { apiSignin, apiSignout } from '../fixtures/authentication';
|
||||
@@ -355,354 +355,3 @@ test('[TEAMS]: delete completed team document', async ({ page }) => {
|
||||
await apiSignout({ page });
|
||||
}
|
||||
});
|
||||
|
||||
test('[TEAMS]: check document visibility based on team member role', async ({ page }) => {
|
||||
const team = await seedTeam();
|
||||
|
||||
// Seed users with different roles
|
||||
const adminUser = await seedTeamMember({
|
||||
teamId: team.id,
|
||||
role: TeamMemberRole.ADMIN,
|
||||
});
|
||||
|
||||
const managerUser = await seedTeamMember({
|
||||
teamId: team.id,
|
||||
role: TeamMemberRole.MANAGER,
|
||||
});
|
||||
|
||||
const memberUser = await seedTeamMember({
|
||||
teamId: team.id,
|
||||
role: TeamMemberRole.MEMBER,
|
||||
});
|
||||
|
||||
const outsideUser = await seedUser();
|
||||
|
||||
// Seed documents with different visibility levels
|
||||
await seedDocuments([
|
||||
{
|
||||
sender: team.owner,
|
||||
recipients: [],
|
||||
type: DocumentStatus.COMPLETED,
|
||||
documentOptions: {
|
||||
teamId: team.id,
|
||||
visibility: 'EVERYONE',
|
||||
title: 'Document Visible to Everyone',
|
||||
},
|
||||
},
|
||||
{
|
||||
sender: team.owner,
|
||||
recipients: [],
|
||||
type: DocumentStatus.COMPLETED,
|
||||
documentOptions: {
|
||||
teamId: team.id,
|
||||
visibility: 'MANAGER_AND_ABOVE',
|
||||
title: 'Document Visible to Manager and Above',
|
||||
},
|
||||
},
|
||||
{
|
||||
sender: team.owner,
|
||||
recipients: [],
|
||||
type: DocumentStatus.COMPLETED,
|
||||
documentOptions: {
|
||||
teamId: team.id,
|
||||
visibility: 'ADMIN',
|
||||
title: 'Document Visible to Admin',
|
||||
},
|
||||
},
|
||||
{
|
||||
sender: team.owner,
|
||||
recipients: [outsideUser],
|
||||
type: DocumentStatus.COMPLETED,
|
||||
documentOptions: {
|
||||
teamId: team.id,
|
||||
visibility: 'ADMIN',
|
||||
title: 'Document Visible to Admin with Recipient',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// Test cases for each role
|
||||
const testCases = [
|
||||
{
|
||||
user: adminUser,
|
||||
expectedDocuments: [
|
||||
'Document Visible to Everyone',
|
||||
'Document Visible to Manager and Above',
|
||||
'Document Visible to Admin',
|
||||
'Document Visible to Admin with Recipient',
|
||||
],
|
||||
},
|
||||
{
|
||||
user: managerUser,
|
||||
expectedDocuments: ['Document Visible to Everyone', 'Document Visible to Manager and Above'],
|
||||
},
|
||||
{
|
||||
user: memberUser,
|
||||
expectedDocuments: ['Document Visible to Everyone'],
|
||||
},
|
||||
{
|
||||
user: outsideUser,
|
||||
expectedDocuments: ['Document Visible to Admin with Recipient'],
|
||||
},
|
||||
];
|
||||
|
||||
for (const testCase of testCases) {
|
||||
await apiSignin({
|
||||
page,
|
||||
email: testCase.user.email,
|
||||
redirectPath: `/t/${team.url}/documents?status=COMPLETED`,
|
||||
});
|
||||
|
||||
// Check that the user sees the expected documents
|
||||
for (const documentTitle of testCase.expectedDocuments) {
|
||||
await expect(page.getByRole('link', { name: documentTitle, exact: true })).toBeVisible();
|
||||
}
|
||||
|
||||
await apiSignout({ page });
|
||||
}
|
||||
});
|
||||
|
||||
test('[TEAMS]: ensure recipient can see document regardless of visibility', async ({ page }) => {
|
||||
const team = await seedTeam();
|
||||
|
||||
// Seed a member user
|
||||
const memberUser = await seedTeamMember({
|
||||
teamId: team.id,
|
||||
role: TeamMemberRole.MEMBER,
|
||||
});
|
||||
|
||||
// Seed a document with ADMIN visibility but make the member user a recipient
|
||||
await seedDocuments([
|
||||
{
|
||||
sender: team.owner,
|
||||
recipients: [memberUser],
|
||||
type: DocumentStatus.COMPLETED,
|
||||
documentOptions: {
|
||||
teamId: team.id,
|
||||
visibility: 'ADMIN',
|
||||
title: 'Admin Document with Member Recipient',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
await apiSignin({
|
||||
page,
|
||||
email: memberUser.email,
|
||||
redirectPath: `/t/${team.url}/documents?status=COMPLETED`,
|
||||
});
|
||||
|
||||
// Check that the member user can see the document
|
||||
await expect(
|
||||
page.getByRole('link', { name: 'Admin Document with Member Recipient', exact: true }),
|
||||
).toBeVisible();
|
||||
|
||||
await apiSignout({ page });
|
||||
});
|
||||
|
||||
test('[TEAMS]: check that members cannot see ADMIN-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: 'ADMIN',
|
||||
title: 'Admin 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 managers cannot see ADMIN-only documents', async ({ page }) => {
|
||||
const team = await seedTeam();
|
||||
|
||||
// Seed a manager user
|
||||
const managerUser = await seedTeamMember({
|
||||
teamId: team.id,
|
||||
role: TeamMemberRole.MANAGER,
|
||||
});
|
||||
|
||||
// Seed an ADMIN-only document
|
||||
await seedDocuments([
|
||||
{
|
||||
sender: team.owner,
|
||||
recipients: [],
|
||||
type: DocumentStatus.COMPLETED,
|
||||
documentOptions: {
|
||||
teamId: team.id,
|
||||
visibility: 'ADMIN',
|
||||
title: 'Admin Only Document',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
await apiSignin({
|
||||
page,
|
||||
email: managerUser.email,
|
||||
redirectPath: `/t/${team.url}/documents?status=COMPLETED`,
|
||||
});
|
||||
|
||||
// Check that the manager 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 admin can see MANAGER_AND_ABOVE documents', async ({ page }) => {
|
||||
const team = await seedTeam();
|
||||
|
||||
// Seed an admin user
|
||||
const adminUser = await seedTeamMember({
|
||||
teamId: team.id,
|
||||
role: TeamMemberRole.ADMIN,
|
||||
});
|
||||
|
||||
// Seed a MANAGER_AND_ABOVE document
|
||||
await seedDocuments([
|
||||
{
|
||||
sender: team.owner,
|
||||
recipients: [],
|
||||
type: DocumentStatus.COMPLETED,
|
||||
documentOptions: {
|
||||
teamId: team.id,
|
||||
visibility: 'MANAGER_AND_ABOVE',
|
||||
title: 'Manager and Above Document',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
await apiSignin({
|
||||
page,
|
||||
email: adminUser.email,
|
||||
redirectPath: `/t/${team.url}/documents?status=COMPLETED`,
|
||||
});
|
||||
|
||||
// Check that the admin user can see the MANAGER_AND_ABOVE document
|
||||
await expect(
|
||||
page.getByRole('link', { name: 'Manager and Above Document', exact: true }),
|
||||
).toBeVisible();
|
||||
|
||||
await apiSignout({ page });
|
||||
});
|
||||
|
||||
test('[TEAMS]: users cannot see documents from other teams', async ({ page }) => {
|
||||
// Seed two teams with documents
|
||||
const { team: teamA, teamMember2: teamAMember } = await seedTeamDocuments();
|
||||
const { team: teamB, teamMember2: teamBMember } = await seedTeamDocuments();
|
||||
|
||||
// Seed a document in team B
|
||||
await seedDocuments([
|
||||
{
|
||||
sender: teamB.owner,
|
||||
recipients: [],
|
||||
type: DocumentStatus.COMPLETED,
|
||||
documentOptions: {
|
||||
teamId: teamB.id,
|
||||
visibility: 'EVERYONE',
|
||||
title: 'Team B Document',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// Sign in as a member of team A
|
||||
await apiSignin({
|
||||
page,
|
||||
email: teamAMember.email,
|
||||
redirectPath: `/t/${teamA.url}/documents?status=COMPLETED`,
|
||||
});
|
||||
|
||||
// Verify that the user cannot see the document from team B
|
||||
await expect(page.getByRole('link', { name: 'Team B Document', exact: true })).not.toBeVisible();
|
||||
|
||||
await apiSignout({ page });
|
||||
});
|
||||
|
||||
test('[TEAMS]: personal documents are not visible in team context', async ({ page }) => {
|
||||
// Seed a team and a user with personal documents
|
||||
const { team, teamMember2 } = await seedTeamDocuments();
|
||||
const personalUser = await seedUser();
|
||||
|
||||
// Seed a personal document for teamMember2
|
||||
await seedDocuments([
|
||||
{
|
||||
sender: teamMember2,
|
||||
recipients: [],
|
||||
type: DocumentStatus.COMPLETED,
|
||||
documentOptions: {
|
||||
teamId: null, // Indicates a personal document
|
||||
visibility: 'EVERYONE',
|
||||
title: 'Personal Document',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// Sign in as teamMember2 in the team context
|
||||
await apiSignin({
|
||||
page,
|
||||
email: teamMember2.email,
|
||||
redirectPath: `/t/${team.url}/documents?status=COMPLETED`,
|
||||
});
|
||||
|
||||
// Verify that the personal document is not visible in the team context
|
||||
await expect(
|
||||
page.getByRole('link', { name: 'Personal Document', exact: true }),
|
||||
).not.toBeVisible();
|
||||
|
||||
await apiSignout({ page });
|
||||
});
|
||||
|
||||
test('[PERSONAL]: team documents are not visible in personal account', async ({ page }) => {
|
||||
// Seed a team and a user with personal documents
|
||||
const { team, teamMember2 } = await seedTeamDocuments();
|
||||
|
||||
// Seed a team document
|
||||
await seedDocuments([
|
||||
{
|
||||
sender: teamMember2,
|
||||
recipients: [],
|
||||
type: DocumentStatus.COMPLETED,
|
||||
documentOptions: {
|
||||
teamId: team.id,
|
||||
visibility: 'EVERYONE',
|
||||
title: 'Team Document',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// Sign in as teamMember2 in the personal context
|
||||
await apiSignin({
|
||||
page,
|
||||
email: teamMember2.email,
|
||||
redirectPath: `/documents?status=COMPLETED`,
|
||||
});
|
||||
|
||||
// Verify that the team document is not visible in the personal context
|
||||
await expect(page.getByRole('link', { name: 'Team Document', exact: true })).not.toBeVisible();
|
||||
|
||||
await apiSignout({ page });
|
||||
});
|
||||
|
||||
@@ -42,8 +42,10 @@ test.describe('[EE_ONLY]', () => {
|
||||
await page.getByPlaceholder('Email').fill('recipient1@documenso.com');
|
||||
await page.getByPlaceholder('Name').fill('Recipient 1');
|
||||
await page.getByRole('button', { name: 'Add Placeholder Recipient' }).click();
|
||||
await page.getByPlaceholder('Email').nth(1).fill('recipient2@documenso.com');
|
||||
await page.getByPlaceholder('Name').nth(1).fill('Recipient 2');
|
||||
await page
|
||||
.getByRole('textbox', { name: 'Email', exact: true })
|
||||
.fill('recipient2@documenso.com');
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).nth(1).fill('Recipient 2');
|
||||
|
||||
// Display advanced settings.
|
||||
await page.getByLabel('Show advanced settings').check();
|
||||
@@ -92,8 +94,8 @@ test('[TEMPLATE_FLOW]: add placeholder', async ({ page }) => {
|
||||
await page.getByPlaceholder('Email').fill('recipient1@documenso.com');
|
||||
await page.getByPlaceholder('Name').fill('Recipient 1');
|
||||
await page.getByRole('button', { name: 'Add Placeholder Recipient' }).click();
|
||||
await page.getByPlaceholder('Email').nth(1).fill('recipient2@documenso.com');
|
||||
await page.getByPlaceholder('Name').nth(1).fill('Recipient 2');
|
||||
await page.getByRole('textbox', { name: 'Email', exact: true }).fill('recipient2@documenso.com');
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).nth(1).fill('Recipient 2');
|
||||
|
||||
// Advanced settings should not be visible for non EE users.
|
||||
await expect(page.getByLabel('Show advanced settings')).toBeHidden();
|
||||
|
||||
@@ -76,8 +76,8 @@ test('[TEMPLATE]: should create a document from a template', async ({ page }) =>
|
||||
await page.getByPlaceholder('Email').fill('recipient1@documenso.com');
|
||||
await page.getByPlaceholder('Name').fill('Recipient 1');
|
||||
await page.getByRole('button', { name: 'Add Placeholder Recipient' }).click();
|
||||
await page.getByPlaceholder('Email').nth(1).fill('recipient2@documenso.com');
|
||||
await page.getByPlaceholder('Name').nth(1).fill('Recipient 2');
|
||||
await page.getByRole('textbox', { name: 'Email', exact: true }).fill('recipient2@documenso.com');
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).nth(1).fill('Recipient 2');
|
||||
|
||||
// Apply require passkey for Recipient 1.
|
||||
if (isBillingEnabled) {
|
||||
@@ -211,8 +211,8 @@ test('[TEMPLATE]: should create a team document from a team template', async ({
|
||||
await page.getByPlaceholder('Email').fill('recipient1@documenso.com');
|
||||
await page.getByPlaceholder('Name').fill('Recipient 1');
|
||||
await page.getByRole('button', { name: 'Add Placeholder Recipient' }).click();
|
||||
await page.getByPlaceholder('Email').nth(1).fill('recipient2@documenso.com');
|
||||
await page.getByPlaceholder('Name').nth(1).fill('Recipient 2');
|
||||
await page.getByRole('textbox', { name: 'Email', exact: true }).fill('recipient2@documenso.com');
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).nth(1).fill('Recipient 2');
|
||||
|
||||
// Apply require passkey for Recipient 1.
|
||||
if (isBillingEnabled) {
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
|
||||
|
||||
import type { TDocumentVisibility } from '../types/document-visibility';
|
||||
|
||||
type DocumentVisibilityTypeData = {
|
||||
key: TDocumentVisibility;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export const DOCUMENT_VISIBILITY: Record<string, DocumentVisibilityTypeData> = {
|
||||
[DocumentVisibility.ADMIN]: {
|
||||
key: DocumentVisibility.ADMIN,
|
||||
value: 'Admins only',
|
||||
},
|
||||
[DocumentVisibility.EVERYONE]: {
|
||||
key: DocumentVisibility.EVERYONE,
|
||||
value: 'Everyone',
|
||||
},
|
||||
[DocumentVisibility.MANAGER_AND_ABOVE]: {
|
||||
key: DocumentVisibility.MANAGER_AND_ABOVE,
|
||||
value: 'Managers and above',
|
||||
},
|
||||
} satisfies Record<TDocumentVisibility, DocumentVisibilityTypeData>;
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
diffDocumentMetaChanges,
|
||||
} from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { DocumentSigningOrder } from '@documenso/prisma/client';
|
||||
|
||||
export type CreateDocumentMetaOptions = {
|
||||
documentId: number;
|
||||
@@ -17,7 +16,6 @@ export type CreateDocumentMetaOptions = {
|
||||
password?: string;
|
||||
dateFormat?: string;
|
||||
redirectUrl?: string;
|
||||
signingOrder?: DocumentSigningOrder;
|
||||
userId: number;
|
||||
requestMetadata: RequestMetadata;
|
||||
};
|
||||
@@ -31,7 +29,6 @@ export const upsertDocumentMeta = async ({
|
||||
password,
|
||||
userId,
|
||||
redirectUrl,
|
||||
signingOrder,
|
||||
requestMetadata,
|
||||
}: CreateDocumentMetaOptions) => {
|
||||
const user = await prisma.user.findFirstOrThrow({
|
||||
@@ -81,7 +78,6 @@ export const upsertDocumentMeta = async ({
|
||||
timezone,
|
||||
documentId,
|
||||
redirectUrl,
|
||||
signingOrder,
|
||||
},
|
||||
update: {
|
||||
subject,
|
||||
@@ -90,7 +86,6 @@ export const upsertDocumentMeta = async ({
|
||||
dateFormat,
|
||||
timezone,
|
||||
redirectUrl,
|
||||
signingOrder,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -2,18 +2,11 @@ import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-log
|
||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import {
|
||||
DocumentSigningOrder,
|
||||
DocumentStatus,
|
||||
RecipientRole,
|
||||
SendStatus,
|
||||
SigningStatus,
|
||||
} from '@documenso/prisma/client';
|
||||
import { DocumentStatus, SigningStatus } from '@documenso/prisma/client';
|
||||
import { WebhookTriggerEvents } from '@documenso/prisma/client';
|
||||
|
||||
import { jobs } from '../../jobs/client';
|
||||
import type { TRecipientActionAuth } from '../../types/document-auth';
|
||||
import { getIsRecipientsTurnToSign } from '../recipient/get-is-recipient-turn';
|
||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||
import { sendPendingEmail } from './send-pending-email';
|
||||
|
||||
@@ -36,7 +29,6 @@ const getDocument = async ({ token, documentId }: CompleteDocumentWithTokenOptio
|
||||
},
|
||||
},
|
||||
include: {
|
||||
documentMeta: true,
|
||||
Recipient: {
|
||||
where: {
|
||||
token,
|
||||
@@ -67,16 +59,6 @@ export const completeDocumentWithToken = async ({
|
||||
throw new Error(`Recipient ${recipient.id} has already signed`);
|
||||
}
|
||||
|
||||
if (document.documentMeta?.signingOrder === DocumentSigningOrder.SEQUENTIAL) {
|
||||
const isRecipientsTurn = await getIsRecipientsTurnToSign({ token: recipient.token });
|
||||
|
||||
if (!isRecipientsTurn) {
|
||||
throw new Error(
|
||||
`Recipient ${recipient.id} attempted to complete the document before it was their turn`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const fields = await prisma.field.findMany({
|
||||
where: {
|
||||
documentId: document.id,
|
||||
@@ -138,48 +120,17 @@ export const completeDocumentWithToken = async ({
|
||||
});
|
||||
});
|
||||
|
||||
const pendingRecipients = await prisma.recipient.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
signingOrder: true,
|
||||
},
|
||||
const pendingRecipients = await prisma.recipient.count({
|
||||
where: {
|
||||
documentId: document.id,
|
||||
signingStatus: {
|
||||
not: SigningStatus.SIGNED,
|
||||
},
|
||||
role: {
|
||||
not: RecipientRole.CC,
|
||||
},
|
||||
},
|
||||
// Composite sort so our next recipient is always the one with the lowest signing order or id
|
||||
// if there is a tie.
|
||||
orderBy: [{ signingOrder: { sort: 'asc', nulls: 'last' } }, { id: 'asc' }],
|
||||
});
|
||||
|
||||
if (pendingRecipients.length > 0) {
|
||||
if (pendingRecipients > 0) {
|
||||
await sendPendingEmail({ documentId, recipientId: recipient.id });
|
||||
|
||||
if (document.documentMeta?.signingOrder === DocumentSigningOrder.SEQUENTIAL) {
|
||||
const [nextRecipient] = pendingRecipients;
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.recipient.update({
|
||||
where: { id: nextRecipient.id },
|
||||
data: { sendStatus: SendStatus.SENT },
|
||||
});
|
||||
|
||||
await jobs.triggerJob({
|
||||
name: 'send.signing.requested.email',
|
||||
payload: {
|
||||
userId: document.userId,
|
||||
documentId: document.id,
|
||||
recipientId: nextRecipient.id,
|
||||
requestMetadata,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const haveAllRecipientsSigned = await prisma.document.findFirst({
|
||||
@@ -187,7 +138,7 @@ export const completeDocumentWithToken = async ({
|
||||
id: document.id,
|
||||
Recipient: {
|
||||
every: {
|
||||
OR: [{ signingStatus: SigningStatus.SIGNED }, { role: RecipientRole.CC }],
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -2,11 +2,10 @@ import { DateTime } from 'luxon';
|
||||
import { P, match } from 'ts-pattern';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { RecipientRole, SigningStatus, TeamMemberRole } from '@documenso/prisma/client';
|
||||
import { RecipientRole, SigningStatus } from '@documenso/prisma/client';
|
||||
import type { Document, Prisma, Team, TeamEmail, User } from '@documenso/prisma/client';
|
||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||
|
||||
import { DocumentVisibility } from '../../types/document-visibility';
|
||||
import type { FindResultSet } from '../../types/find-result-set';
|
||||
import { maskRecipientTokensForDocument } from '../../utils/mask-recipient-tokens-for-document';
|
||||
|
||||
@@ -59,14 +58,6 @@ export const findDocuments = async ({
|
||||
},
|
||||
include: {
|
||||
teamEmail: true,
|
||||
members: {
|
||||
where: {
|
||||
userId,
|
||||
},
|
||||
select: {
|
||||
role: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -79,7 +70,6 @@ export const findDocuments = async ({
|
||||
|
||||
const orderByColumn = orderBy?.column ?? 'createdAt';
|
||||
const orderByDirection = orderBy?.direction ?? 'desc';
|
||||
const teamMemberRole = team?.members[0].role ?? null;
|
||||
|
||||
const termFilters = match(term)
|
||||
.with(P.string.minLength(1), () => {
|
||||
@@ -92,37 +82,7 @@ export const findDocuments = async ({
|
||||
})
|
||||
.otherwise(() => undefined);
|
||||
|
||||
const visibilityFilters = [
|
||||
match(teamMemberRole)
|
||||
.with(TeamMemberRole.ADMIN, () => ({
|
||||
visibility: {
|
||||
in: [
|
||||
DocumentVisibility.EVERYONE,
|
||||
DocumentVisibility.MANAGER_AND_ABOVE,
|
||||
DocumentVisibility.ADMIN,
|
||||
],
|
||||
},
|
||||
}))
|
||||
.with(TeamMemberRole.MANAGER, () => ({
|
||||
visibility: {
|
||||
in: [DocumentVisibility.EVERYONE, DocumentVisibility.MANAGER_AND_ABOVE],
|
||||
},
|
||||
}))
|
||||
.otherwise(() => ({ visibility: DocumentVisibility.EVERYONE })),
|
||||
{
|
||||
Recipient: {
|
||||
some: {
|
||||
email: user.email,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
let filters: Prisma.DocumentWhereInput | null = findDocumentsFilter(status, user);
|
||||
|
||||
if (team) {
|
||||
filters = findTeamDocumentsFilter(status, team, visibilityFilters);
|
||||
}
|
||||
const filters = team ? findTeamDocumentsFilter(status, team) : findDocumentsFilter(status, user);
|
||||
|
||||
if (filters === null) {
|
||||
return {
|
||||
@@ -188,7 +148,9 @@ export const findDocuments = async ({
|
||||
}
|
||||
|
||||
const whereClause: Prisma.DocumentWhereInput = {
|
||||
AND: [{ ...termFilters }, { ...filters }, { ...deletedFilter }],
|
||||
...termFilters,
|
||||
...filters,
|
||||
...deletedFilter,
|
||||
};
|
||||
|
||||
if (period) {
|
||||
@@ -371,7 +333,6 @@ const findDocumentsFilter = (status: ExtendedDocumentStatus, user: User) => {
|
||||
const findTeamDocumentsFilter = (
|
||||
status: ExtendedDocumentStatus,
|
||||
team: Team & { teamEmail: TeamEmail | null },
|
||||
visibilityFilters: Prisma.DocumentWhereInput[],
|
||||
) => {
|
||||
const teamEmail = team.teamEmail?.email ?? null;
|
||||
|
||||
@@ -382,7 +343,6 @@ const findTeamDocumentsFilter = (
|
||||
OR: [
|
||||
{
|
||||
teamId: team.id,
|
||||
OR: visibilityFilters,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -398,7 +358,6 @@ const findTeamDocumentsFilter = (
|
||||
email: teamEmail,
|
||||
},
|
||||
},
|
||||
OR: visibilityFilters,
|
||||
});
|
||||
|
||||
// Filter to display all documents that have been sent by the team email.
|
||||
@@ -406,7 +365,6 @@ const findTeamDocumentsFilter = (
|
||||
User: {
|
||||
email: teamEmail,
|
||||
},
|
||||
OR: visibilityFilters,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -431,7 +389,6 @@ const findTeamDocumentsFilter = (
|
||||
},
|
||||
},
|
||||
},
|
||||
OR: visibilityFilters,
|
||||
};
|
||||
})
|
||||
.with(ExtendedDocumentStatus.DRAFT, () => {
|
||||
@@ -440,7 +397,6 @@ const findTeamDocumentsFilter = (
|
||||
{
|
||||
teamId: team.id,
|
||||
status: ExtendedDocumentStatus.DRAFT,
|
||||
OR: visibilityFilters,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -451,7 +407,6 @@ const findTeamDocumentsFilter = (
|
||||
User: {
|
||||
email: teamEmail,
|
||||
},
|
||||
OR: visibilityFilters,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -463,7 +418,6 @@ const findTeamDocumentsFilter = (
|
||||
{
|
||||
teamId: team.id,
|
||||
status: ExtendedDocumentStatus.PENDING,
|
||||
OR: visibilityFilters,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -482,13 +436,11 @@ const findTeamDocumentsFilter = (
|
||||
},
|
||||
},
|
||||
},
|
||||
OR: visibilityFilters,
|
||||
},
|
||||
{
|
||||
User: {
|
||||
email: teamEmail,
|
||||
},
|
||||
OR: visibilityFilters,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -502,7 +454,6 @@ const findTeamDocumentsFilter = (
|
||||
OR: [
|
||||
{
|
||||
teamId: team.id,
|
||||
OR: visibilityFilters,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -515,13 +466,11 @@ const findTeamDocumentsFilter = (
|
||||
email: teamEmail,
|
||||
},
|
||||
},
|
||||
OR: visibilityFilters,
|
||||
},
|
||||
{
|
||||
User: {
|
||||
email: teamEmail,
|
||||
},
|
||||
OR: visibilityFilters,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Prisma } from '@documenso/prisma/client';
|
||||
import { TeamMemberRole } from '@documenso/prisma/client';
|
||||
|
||||
import { DocumentVisibility } from '../../types/document-visibility';
|
||||
import { getTeamById } from '../team/get-team';
|
||||
|
||||
export type GetDocumentByIdOptions = {
|
||||
@@ -32,11 +28,6 @@ export const getDocumentById = async ({ id, userId, teamId }: GetDocumentByIdOpt
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
Recipient: {
|
||||
select: {
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
@@ -124,35 +115,5 @@ export const getDocumentWhereInput = async ({
|
||||
);
|
||||
}
|
||||
|
||||
const user = await prisma.user.findFirstOrThrow({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
const visibilityFilters = [
|
||||
...match(team.currentTeamMember?.role)
|
||||
.with(TeamMemberRole.ADMIN, () => [
|
||||
{ visibility: DocumentVisibility.EVERYONE },
|
||||
{ visibility: DocumentVisibility.MANAGER_AND_ABOVE },
|
||||
{ visibility: DocumentVisibility.ADMIN },
|
||||
])
|
||||
.with(TeamMemberRole.MANAGER, () => [
|
||||
{ visibility: DocumentVisibility.EVERYONE },
|
||||
{ visibility: DocumentVisibility.MANAGER_AND_ABOVE },
|
||||
])
|
||||
.otherwise(() => [{ visibility: DocumentVisibility.EVERYONE }]),
|
||||
{
|
||||
Recipient: {
|
||||
some: {
|
||||
email: user.email,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
...documentWhereInput,
|
||||
OR: [...visibilityFilters],
|
||||
};
|
||||
return documentWhereInput;
|
||||
};
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import type { PeriodSelectorValue } from '@documenso/lib/server-only/document/find-documents';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { TeamMemberRole } from '@documenso/prisma/client';
|
||||
import type { Prisma, User } from '@documenso/prisma/client';
|
||||
import { SigningStatus } from '@documenso/prisma/client';
|
||||
import { isExtendedDocumentStatus } from '@documenso/prisma/guards/is-extended-document-status';
|
||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||
|
||||
import { DocumentVisibility } from '../../types/document-visibility';
|
||||
|
||||
export type GetStatsInput = {
|
||||
user: User;
|
||||
team?: Omit<GetTeamCountsOption, 'createdAt'>;
|
||||
@@ -31,7 +27,7 @@ export const getStats = async ({ user, period, ...options }: GetStatsInput) => {
|
||||
}
|
||||
|
||||
const [ownerCounts, notSignedCounts, hasSignedCounts] = await (options.team
|
||||
? getTeamCounts({ ...options.team, createdAt, currentUserEmail: user.email, userId: user.id })
|
||||
? getTeamCounts({ ...options.team, createdAt })
|
||||
: getCounts({ user, createdAt }));
|
||||
|
||||
const stats: Record<ExtendedDocumentStatus, number> = {
|
||||
@@ -151,10 +147,7 @@ type GetTeamCountsOption = {
|
||||
teamId: number;
|
||||
teamEmail?: string;
|
||||
senderIds?: number[];
|
||||
currentUserEmail: string;
|
||||
userId: number;
|
||||
createdAt: Prisma.DocumentWhereInput['createdAt'];
|
||||
currentTeamMemberRole?: TeamMemberRole;
|
||||
};
|
||||
|
||||
const getTeamCounts = async (options: GetTeamCountsOption) => {
|
||||
@@ -179,49 +172,6 @@ const getTeamCounts = async (options: GetTeamCountsOption) => {
|
||||
let notSignedCountsGroupByArgs = null;
|
||||
let hasSignedCountsGroupByArgs = null;
|
||||
|
||||
const visibilityFilters = [
|
||||
...match(options.currentTeamMemberRole)
|
||||
.with(TeamMemberRole.ADMIN, () => [
|
||||
{ visibility: DocumentVisibility.EVERYONE },
|
||||
{ visibility: DocumentVisibility.MANAGER_AND_ABOVE },
|
||||
{ visibility: DocumentVisibility.ADMIN },
|
||||
])
|
||||
.with(TeamMemberRole.MANAGER, () => [
|
||||
{ visibility: DocumentVisibility.EVERYONE },
|
||||
{ visibility: DocumentVisibility.MANAGER_AND_ABOVE },
|
||||
])
|
||||
.otherwise(() => [{ visibility: DocumentVisibility.EVERYONE }]),
|
||||
];
|
||||
|
||||
ownerCountsWhereInput = {
|
||||
...ownerCountsWhereInput,
|
||||
OR: [
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
visibility: {
|
||||
in: visibilityFilters.map((filter) => filter.visibility),
|
||||
},
|
||||
},
|
||||
{
|
||||
Recipient: {
|
||||
none: {
|
||||
email: options.currentUserEmail,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
Recipient: {
|
||||
some: {
|
||||
email: options.currentUserEmail,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (teamEmail) {
|
||||
ownerCountsWhereInput = {
|
||||
userId: userIdWhereClause,
|
||||
|
||||
@@ -3,13 +3,7 @@ import type { RequestMetadata } from '@documenso/lib/universal/extract-request-m
|
||||
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import {
|
||||
DocumentSigningOrder,
|
||||
DocumentStatus,
|
||||
RecipientRole,
|
||||
SendStatus,
|
||||
SigningStatus,
|
||||
} from '@documenso/prisma/client';
|
||||
import { DocumentStatus, RecipientRole, SendStatus, SigningStatus } from '@documenso/prisma/client';
|
||||
import { WebhookTriggerEvents } from '@documenso/prisma/client';
|
||||
|
||||
import { jobs } from '../../jobs/client';
|
||||
@@ -63,9 +57,7 @@ export const sendDocument = async ({
|
||||
}),
|
||||
},
|
||||
include: {
|
||||
Recipient: {
|
||||
orderBy: [{ signingOrder: { sort: 'asc', nulls: 'last' } }, { id: 'asc' }],
|
||||
},
|
||||
Recipient: true,
|
||||
documentMeta: true,
|
||||
documentData: true,
|
||||
},
|
||||
@@ -83,21 +75,6 @@ export const sendDocument = async ({
|
||||
throw new Error('Can not send completed document');
|
||||
}
|
||||
|
||||
const signingOrder = document.documentMeta?.signingOrder || DocumentSigningOrder.PARALLEL;
|
||||
|
||||
let recipientsToNotify = document.Recipient;
|
||||
|
||||
if (signingOrder === DocumentSigningOrder.SEQUENTIAL) {
|
||||
// Get the currently active recipient.
|
||||
recipientsToNotify = document.Recipient.filter(
|
||||
(r) => r.signingStatus === SigningStatus.NOT_SIGNED && r.role !== RecipientRole.CC,
|
||||
).slice(0, 1);
|
||||
|
||||
// Secondary filter so we aren't resending if the current active recipient has already
|
||||
// received the document.
|
||||
recipientsToNotify.filter((r) => r.sendStatus !== SendStatus.SENT);
|
||||
}
|
||||
|
||||
const { documentData } = document;
|
||||
|
||||
if (!documentData.data) {
|
||||
@@ -158,7 +135,7 @@ export const sendDocument = async ({
|
||||
|
||||
if (sendEmail) {
|
||||
await Promise.all(
|
||||
recipientsToNotify.map(async (recipient) => {
|
||||
document.Recipient.map(async (recipient) => {
|
||||
if (recipient.sendStatus === SendStatus.SENT || recipient.role === RecipientRole.CC) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import type { RequestMetadata } from '@documenso/lib/universal/extract-request-m
|
||||
import type { CreateDocumentAuditLogDataResponse } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { DocumentVisibility } from '@documenso/prisma/client';
|
||||
import { DocumentStatus } from '@documenso/prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
@@ -20,7 +19,6 @@ export type UpdateDocumentSettingsOptions = {
|
||||
data: {
|
||||
title?: string;
|
||||
externalId?: string | null;
|
||||
visibility?: string | null;
|
||||
globalAccessAuth?: TDocumentAccessAuthTypes | null;
|
||||
globalActionAuth?: TDocumentActionAuthTypes | null;
|
||||
};
|
||||
@@ -97,7 +95,6 @@ export const updateDocumentSettings = async ({
|
||||
const isExternalIdSame = data.externalId === document.externalId;
|
||||
const isGlobalAccessSame = documentGlobalAccessAuth === newGlobalAccessAuth;
|
||||
const isGlobalActionSame = documentGlobalActionAuth === newGlobalActionAuth;
|
||||
const isDocumentVisibilitySame = data.visibility === document.visibility;
|
||||
|
||||
const auditLogs: CreateDocumentAuditLogDataResponse[] = [];
|
||||
|
||||
@@ -168,21 +165,6 @@ export const updateDocumentSettings = async ({
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDocumentVisibilitySame) {
|
||||
auditLogs.push(
|
||||
createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_VISIBILITY_UPDATED,
|
||||
documentId,
|
||||
user,
|
||||
requestMetadata,
|
||||
data: {
|
||||
from: document.visibility,
|
||||
to: data.visibility || '',
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Early return if nothing is required.
|
||||
if (auditLogs.length === 0) {
|
||||
return document;
|
||||
@@ -201,7 +183,6 @@ export const updateDocumentSettings = async ({
|
||||
data: {
|
||||
title: data.title,
|
||||
externalId: data.externalId || null,
|
||||
visibility: data.visibility as DocumentVisibility,
|
||||
authOptions,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,7 +5,11 @@ import { getToken } from 'next-auth/jwt';
|
||||
import { LOCAL_FEATURE_FLAGS } from '@documenso/lib/constants/feature-flags';
|
||||
import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-post-hog-server-client';
|
||||
|
||||
import { NEXT_PUBLIC_MARKETING_URL, NEXT_PUBLIC_WEBAPP_URL, NEXT_PRIVATE_INTERNAL_WEBAPP_URL } from '../../constants/app';
|
||||
import {
|
||||
NEXT_PRIVATE_INTERNAL_WEBAPP_URL,
|
||||
NEXT_PUBLIC_MARKETING_URL,
|
||||
NEXT_PUBLIC_WEBAPP_URL,
|
||||
} from '../../constants/app';
|
||||
import { extractDistinctUserId, mapJwtToFlagProperties } from './get';
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,11 @@ import { getToken } from 'next-auth/jwt';
|
||||
import { LOCAL_FEATURE_FLAGS, extractPostHogConfig } from '@documenso/lib/constants/feature-flags';
|
||||
import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-post-hog-server-client';
|
||||
|
||||
import { NEXT_PUBLIC_MARKETING_URL, NEXT_PUBLIC_WEBAPP_URL, NEXT_PRIVATE_INTERNAL_WEBAPP_URL } from '../../constants/app';
|
||||
import {
|
||||
NEXT_PRIVATE_INTERNAL_WEBAPP_URL,
|
||||
NEXT_PUBLIC_MARKETING_URL,
|
||||
NEXT_PUBLIC_WEBAPP_URL,
|
||||
} from '../../constants/app';
|
||||
|
||||
/**
|
||||
* Evaluate a single feature flag based on the current user if possible.
|
||||
@@ -67,7 +71,7 @@ export default async function handleFeatureFlagGet(req: Request) {
|
||||
if (origin.startsWith(NEXT_PUBLIC_MARKETING_URL() ?? 'http://localhost:3001')) {
|
||||
res.headers.set('Access-Control-Allow-Origin', origin);
|
||||
}
|
||||
|
||||
|
||||
if (origin.startsWith(NEXT_PRIVATE_INTERNAL_WEBAPP_URL ?? 'http://localhost:3000')) {
|
||||
res.headers.set('Access-Control-Allow-Origin', origin);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { SendStatus } from '@documenso/prisma/client';
|
||||
|
||||
export type DeleteRecipientForTemplateOptions = {
|
||||
templateId: number;
|
||||
recipientId: number;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
};
|
||||
|
||||
export const deleteRecipientFromTemplate = async ({
|
||||
templateId,
|
||||
recipientId,
|
||||
userId,
|
||||
teamId,
|
||||
}: DeleteRecipientForTemplateOptions) => {
|
||||
const recipient = await prisma.recipient.findFirst({
|
||||
where: {
|
||||
id: recipientId,
|
||||
Template: {
|
||||
id: templateId,
|
||||
...(teamId
|
||||
? {
|
||||
team: {
|
||||
id: teamId,
|
||||
members: {
|
||||
some: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
: {
|
||||
userId,
|
||||
teamId: null,
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!recipient) {
|
||||
throw new Error('Recipient not found');
|
||||
}
|
||||
|
||||
if (recipient.sendStatus !== SendStatus.NOT_SENT) {
|
||||
throw new Error('Can not delete a recipient that has already been sent a document');
|
||||
}
|
||||
|
||||
return await prisma.recipient.delete({
|
||||
where: {
|
||||
id: recipient.id,
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentSigningOrder, SigningStatus } from '@documenso/prisma/client';
|
||||
|
||||
export type GetIsRecipientTurnOptions = {
|
||||
token: string;
|
||||
};
|
||||
|
||||
export async function getIsRecipientsTurnToSign({ token }: GetIsRecipientTurnOptions) {
|
||||
const document = await prisma.document.findFirstOrThrow({
|
||||
where: {
|
||||
Recipient: {
|
||||
some: {
|
||||
token,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
documentMeta: true,
|
||||
Recipient: {
|
||||
orderBy: {
|
||||
signingOrder: 'asc',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (document.documentMeta?.signingOrder !== DocumentSigningOrder.SEQUENTIAL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const recipients = document.Recipient;
|
||||
|
||||
const currentRecipientIndex = recipients.findIndex((r) => r.token === token);
|
||||
|
||||
if (currentRecipientIndex === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < currentRecipientIndex; i++) {
|
||||
if (recipients[i].signingStatus !== SigningStatus.SIGNED) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -27,7 +27,6 @@ export interface SetRecipientsForDocumentOptions {
|
||||
email: string;
|
||||
name: string;
|
||||
role: RecipientRole;
|
||||
signingOrder?: number | null;
|
||||
actionAuth?: TRecipientActionAuthTypes | null;
|
||||
}[];
|
||||
requestMetadata?: RequestMetadata;
|
||||
@@ -157,7 +156,6 @@ export const setRecipientsForDocument = async ({
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
documentId,
|
||||
sendStatus: recipient.role === RecipientRole.CC ? SendStatus.SENT : SendStatus.NOT_SENT,
|
||||
signingStatus:
|
||||
@@ -168,7 +166,6 @@ export const setRecipientsForDocument = async ({
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
token: nanoid(),
|
||||
documentId,
|
||||
sendStatus: recipient.role === RecipientRole.CC ? SendStatus.SENT : SendStatus.NOT_SENT,
|
||||
|
||||
@@ -24,7 +24,6 @@ export type SetRecipientsForTemplateOptions = {
|
||||
email: string;
|
||||
name: string;
|
||||
role: RecipientRole;
|
||||
signingOrder?: number | null;
|
||||
actionAuth?: TRecipientActionAuthTypes | null;
|
||||
}[];
|
||||
};
|
||||
@@ -163,7 +162,6 @@ export const setRecipientsForTemplate = async ({
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
templateId,
|
||||
authOptions,
|
||||
},
|
||||
@@ -171,7 +169,6 @@ export const setRecipientsForTemplate = async ({
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
token: nanoid(),
|
||||
templateId,
|
||||
authOptions,
|
||||
|
||||
@@ -18,7 +18,6 @@ export type UpdateRecipientOptions = {
|
||||
email?: string;
|
||||
name?: string;
|
||||
role?: RecipientRole;
|
||||
signingOrder?: number | null;
|
||||
actionAuth?: TRecipientActionAuthTypes | null;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
@@ -31,7 +30,6 @@ export const updateRecipient = async ({
|
||||
email,
|
||||
name,
|
||||
role,
|
||||
signingOrder,
|
||||
actionAuth,
|
||||
userId,
|
||||
teamId,
|
||||
@@ -114,7 +112,6 @@ export const updateRecipient = async ({
|
||||
email: email?.toLowerCase() ?? recipient.email,
|
||||
name: name ?? recipient.name,
|
||||
role: role ?? recipient.role,
|
||||
signingOrder,
|
||||
authOptions: createRecipientAuthOptions({
|
||||
accessAuth: recipientAuthOptions.accessAuth,
|
||||
actionAuth: actionAuth ?? null,
|
||||
|
||||
@@ -6,8 +6,6 @@ export type GetTeamByIdOptions = {
|
||||
teamId: number;
|
||||
};
|
||||
|
||||
export type GetTeamResponse = Awaited<ReturnType<typeof getTeamById>>;
|
||||
|
||||
/**
|
||||
* Get a team given a teamId.
|
||||
*
|
||||
|
||||
@@ -10,7 +10,6 @@ export type CreateDocumentFromTemplateLegacyOptions = {
|
||||
name?: string;
|
||||
email: string;
|
||||
role?: RecipientRole;
|
||||
signingOrder?: number | null;
|
||||
}[];
|
||||
};
|
||||
|
||||
@@ -74,7 +73,6 @@ export const createDocumentFromTemplateLegacy = async ({
|
||||
email: recipient.email,
|
||||
name: recipient.name,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
token: nanoid(),
|
||||
})),
|
||||
},
|
||||
@@ -131,14 +129,12 @@ export const createDocumentFromTemplateLegacy = async ({
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
},
|
||||
create: {
|
||||
documentId: document.id,
|
||||
email: recipient.email,
|
||||
name: recipient.name,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
token: nanoid(),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { DocumentSigningOrder, Field } from '@documenso/prisma/client';
|
||||
import type { Field } from '@documenso/prisma/client';
|
||||
import {
|
||||
DocumentSource,
|
||||
type Recipient,
|
||||
@@ -41,7 +41,6 @@ export type CreateDocumentFromTemplateOptions = {
|
||||
id: number;
|
||||
name?: string;
|
||||
email: string;
|
||||
signingOrder?: number | null;
|
||||
}[];
|
||||
|
||||
/**
|
||||
@@ -55,7 +54,6 @@ export type CreateDocumentFromTemplateOptions = {
|
||||
password?: string;
|
||||
dateFormat?: string;
|
||||
redirectUrl?: string;
|
||||
signingOrder?: DocumentSigningOrder;
|
||||
};
|
||||
requestMetadata?: RequestMetadata;
|
||||
};
|
||||
@@ -136,7 +134,6 @@ export const createDocumentFromTemplate = async ({
|
||||
name: foundRecipient ? foundRecipient.name ?? '' : templateRecipient.name,
|
||||
email: foundRecipient ? foundRecipient.email : templateRecipient.email,
|
||||
role: templateRecipient.role,
|
||||
signingOrder: foundRecipient?.signingOrder ?? templateRecipient.signingOrder,
|
||||
authOptions: templateRecipient.authOptions,
|
||||
};
|
||||
});
|
||||
@@ -171,8 +168,6 @@ export const createDocumentFromTemplate = async ({
|
||||
password: override?.password || template.templateMeta?.password,
|
||||
dateFormat: override?.dateFormat || template.templateMeta?.dateFormat,
|
||||
redirectUrl: override?.redirectUrl || template.templateMeta?.redirectUrl,
|
||||
signingOrder:
|
||||
override?.signingOrder || template.templateMeta?.signingOrder || undefined,
|
||||
},
|
||||
},
|
||||
Recipient: {
|
||||
|
||||
@@ -33,7 +33,7 @@ export const updateTemplateSettings = async ({
|
||||
meta,
|
||||
data,
|
||||
}: UpdateTemplateSettingsOptions) => {
|
||||
if (Object.values(data).length === 0 && Object.keys(meta ?? {}).length === 0) {
|
||||
if (Object.values(data).length === 0) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, 'Missing data to update');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"POT-Creation-Date: 2024-07-24 13:01+1000\n"
|
||||
"POT-Creation-Date: 2024-08-22 05:16+0000\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: @lingui/cli\n"
|
||||
"Language: de\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2024-09-05 06:04\n"
|
||||
"Last-Translator: \n"
|
||||
@@ -20,125 +20,125 @@ msgstr ""
|
||||
|
||||
#: packages/ui/primitives/data-table-pagination.tsx:30
|
||||
msgid "{0} of {1} row(s) selected."
|
||||
msgstr ""
|
||||
msgstr "{0} von {1} Zeile(n) ausgewählt."
|
||||
|
||||
#: packages/ui/primitives/data-table-pagination.tsx:41
|
||||
msgid "{visibleRows, plural, one {Showing # result.} other {Showing # results.}}"
|
||||
msgstr ""
|
||||
msgstr "{visibleRows, plural, one {Eine # Ergebnis wird angezeigt.} other {# Ergebnisse werden angezeigt.}}"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:53
|
||||
msgid "<0>Inherit authentication method</0> - Use the global action signing authentication method configured in the \"General Settings\" step"
|
||||
msgstr ""
|
||||
msgstr "<0>Authentifizierungsmethode erben</0> - Verwenden Sie die in den \"Allgemeinen Einstellungen\" konfigurierte globale Aktionssignatur-Authentifizierungsmethode"
|
||||
|
||||
#: packages/ui/components/document/document-global-auth-action-select.tsx:95
|
||||
msgid "<0>No restrictions</0> - No authentication required"
|
||||
msgstr ""
|
||||
msgstr "<0>Keine Einschränkungen</0> - Keine Authentifizierung erforderlich"
|
||||
|
||||
#: packages/ui/components/document/document-global-auth-access-select.tsx:77
|
||||
msgid "<0>No restrictions</0> - The document can be accessed directly by the URL sent to the recipient"
|
||||
msgstr ""
|
||||
msgstr "<0>Keine Einschränkungen</0> - Das Dokument kann direkt über die dem Empfänger gesendete URL abgerufen werden"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:75
|
||||
msgid "<0>None</0> - No authentication required"
|
||||
msgstr ""
|
||||
msgstr "<0>Keine</0> - Keine Authentifizierung erforderlich"
|
||||
|
||||
#: packages/ui/components/document/document-global-auth-action-select.tsx:89
|
||||
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:69
|
||||
msgid "<0>Require 2FA</0> - The recipient must have an account and 2FA enabled via their settings"
|
||||
msgstr ""
|
||||
msgstr "<0>2FA erforderlich</0> - Der Empfänger muss ein Konto haben und die 2FA über seine Einstellungen aktiviert haben"
|
||||
|
||||
#: packages/ui/components/document/document-global-auth-access-select.tsx:72
|
||||
msgid "<0>Require account</0> - The recipient must be signed in to view the document"
|
||||
msgstr ""
|
||||
msgstr "<0>Konto erforderlich</0> - Der Empfänger muss angemeldet sein, um das Dokument anzeigen zu können"
|
||||
|
||||
#: packages/ui/components/document/document-global-auth-action-select.tsx:83
|
||||
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:63
|
||||
msgid "<0>Require passkey</0> - The recipient must have an account and passkey configured via their settings"
|
||||
msgstr ""
|
||||
msgstr "<0>Passkey erforderlich</0> - Der Empfänger muss ein Konto haben und den Passkey über seine Einstellungen konfiguriert haben"
|
||||
|
||||
#: packages/ui/primitives/document-dropzone.tsx:69
|
||||
msgid "Add a document"
|
||||
msgstr ""
|
||||
msgstr "Dokument hinzufügen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:336
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:305
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:339
|
||||
msgid "Add a URL to redirect the user to once the document is signed"
|
||||
msgstr ""
|
||||
msgstr "Fügen Sie eine URL hinzu, um den Benutzer nach der Unterzeichnung des Dokuments weiterzuleiten"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:248
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:217
|
||||
msgid "Add an external ID to the document. This can be used to identify the document in external systems."
|
||||
msgstr ""
|
||||
msgstr "Fügen Sie dem Dokument eine externe ID hinzu. Diese kann verwendet werden, um das Dokument in externen Systemen zu identifizieren."
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:256
|
||||
msgid "Add an external ID to the template. This can be used to identify in external systems."
|
||||
msgstr ""
|
||||
msgstr "Fügen Sie der Vorlage eine externe ID hinzu. Diese kann zur Identifizierung in externen Systemen verwendet werden."
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:177
|
||||
msgid "Add another option"
|
||||
msgstr ""
|
||||
msgstr "Weitere Option hinzufügen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:230
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/radio-field.tsx:167
|
||||
msgid "Add another value"
|
||||
msgstr ""
|
||||
msgstr "Weiteren Wert hinzufügen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:653
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:459
|
||||
msgid "Add myself"
|
||||
msgstr ""
|
||||
msgstr "Mich selbst hinzufügen"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:637
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:450
|
||||
msgid "Add Myself"
|
||||
msgstr ""
|
||||
msgstr "Mich hinzufügen"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:623
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:436
|
||||
msgid "Add Placeholder Recipient"
|
||||
msgstr ""
|
||||
msgstr "Platzhalterempfänger hinzufügen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:642
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:448
|
||||
msgid "Add Signer"
|
||||
msgstr ""
|
||||
msgstr "Unterzeichner hinzufügen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:70
|
||||
msgid "Add text"
|
||||
msgstr ""
|
||||
msgstr "Text hinzufügen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:75
|
||||
msgid "Add text to the field"
|
||||
msgstr ""
|
||||
msgstr "Text zum Feld hinzufügen"
|
||||
|
||||
#: packages/lib/constants/teams.ts:10
|
||||
msgid "Admin"
|
||||
msgstr ""
|
||||
msgstr "Admin"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:230
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:199
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:238
|
||||
msgid "Advanced Options"
|
||||
msgstr ""
|
||||
msgstr "Erweiterte Optionen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:522
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:510
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:402
|
||||
msgid "Advanced settings"
|
||||
msgstr ""
|
||||
msgstr "Erweiterte Einstellungen"
|
||||
|
||||
#: packages/lib/constants/template.ts:21
|
||||
msgid "After submission, a document will be automatically generated and added to your documents page. You will also receive a notification via email."
|
||||
msgstr ""
|
||||
msgstr "Nach der Übermittlung wird ein Dokument automatisch generiert und zu Ihrer Dokumentenseite hinzugefügt. Sie erhalten außerdem eine Benachrichtigung per E-Mail."
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:8
|
||||
msgid "Approve"
|
||||
msgstr ""
|
||||
msgstr "Genehmigen"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:9
|
||||
msgid "Approved"
|
||||
msgstr ""
|
||||
msgstr "Genehmigt"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:11
|
||||
msgid "Approver"
|
||||
msgstr ""
|
||||
msgstr "Genehmiger"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:10
|
||||
msgid "Approving"
|
||||
msgstr ""
|
||||
msgstr "Genehmigung"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:276
|
||||
msgid "Black"
|
||||
@@ -151,178 +151,174 @@ msgstr ""
|
||||
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:287
|
||||
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:58
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
msgstr "Abbrechen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:194
|
||||
msgid "Cannot remove signer"
|
||||
msgstr ""
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:164
|
||||
#~ msgid "Cannot remove signer"
|
||||
#~ msgstr "Unterzeichner kann nicht entfernt werden"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:17
|
||||
msgid "Cc"
|
||||
msgstr ""
|
||||
msgstr "Cc"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:14
|
||||
#: packages/lib/constants/recipient-roles.ts:16
|
||||
msgid "CC"
|
||||
msgstr ""
|
||||
msgstr "CC"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:15
|
||||
msgid "CC'd"
|
||||
msgstr ""
|
||||
msgstr "CC'd"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:83
|
||||
msgid "Character Limit"
|
||||
msgstr ""
|
||||
msgstr "Zeichenbeschränkung"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:944
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:932
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:788
|
||||
msgid "Checkbox"
|
||||
msgstr ""
|
||||
msgstr "Checkbox"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:195
|
||||
msgid "Checkbox values"
|
||||
msgstr ""
|
||||
msgstr "Checkbox-Werte"
|
||||
|
||||
#: packages/ui/primitives/data-table.tsx:156
|
||||
msgid "Clear filters"
|
||||
msgstr ""
|
||||
msgstr "Filter löschen"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:310
|
||||
msgid "Clear Signature"
|
||||
msgstr ""
|
||||
msgstr "Unterschrift löschen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signature.tsx:394
|
||||
msgid "Click to insert field"
|
||||
msgstr ""
|
||||
msgstr "Klicken, um das Feld einzufügen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx:44
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
msgstr "Schließen"
|
||||
|
||||
#: packages/lib/constants/template.ts:12
|
||||
msgid "Configure Direct Recipient"
|
||||
msgstr ""
|
||||
msgstr "Direkten Empfänger konfigurieren"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:523
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:511
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:403
|
||||
msgid "Configure the {0} field"
|
||||
msgstr ""
|
||||
msgstr "Konfigurieren Sie das Feld {0}"
|
||||
|
||||
#: packages/ui/primitives/document-flow/document-flow-root.tsx:141
|
||||
msgid "Continue"
|
||||
msgstr ""
|
||||
msgstr "Fortsetzen"
|
||||
|
||||
#: packages/ui/components/document/document-share-button.tsx:46
|
||||
msgid "Copied to clipboard"
|
||||
msgstr ""
|
||||
msgstr "In die Zwischenablage kopiert"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signature.tsx:360
|
||||
msgid "Custom Text"
|
||||
msgstr ""
|
||||
msgstr "Benutzerdefinierter Text"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:840
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:828
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:684
|
||||
msgid "Date"
|
||||
msgstr ""
|
||||
msgstr "Datum"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:271
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:240
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:279
|
||||
msgid "Date Format"
|
||||
msgstr ""
|
||||
msgstr "Datumsformat"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:570
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:393
|
||||
msgid "Direct link receiver"
|
||||
msgstr ""
|
||||
msgstr "Empfänger des direkten Links"
|
||||
|
||||
#: packages/ui/components/document/document-global-auth-access-select.tsx:62
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:174
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:166
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:151
|
||||
msgid "Document access"
|
||||
msgstr ""
|
||||
msgstr "Dokumentenzugriff"
|
||||
|
||||
#: packages/lib/constants/template.ts:20
|
||||
msgid "Document Creation"
|
||||
msgstr ""
|
||||
msgstr "Dokumenterstellung"
|
||||
|
||||
#: packages/ui/components/document/document-download-button.tsx:68
|
||||
msgid "Download"
|
||||
msgstr ""
|
||||
msgstr "Herunterladen"
|
||||
|
||||
#: packages/ui/primitives/document-dropzone.tsx:162
|
||||
msgid "Drag & drop your PDF here."
|
||||
msgstr ""
|
||||
msgstr "Ziehen Sie Ihr PDF hierher."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:970
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:958
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:814
|
||||
msgid "Dropdown"
|
||||
msgstr ""
|
||||
msgstr "Dropdown"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:148
|
||||
msgid "Dropdown options"
|
||||
msgstr ""
|
||||
msgstr "Dropdown-Optionen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:788
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:776
|
||||
#: packages/ui/primitives/document-flow/add-signature.tsx:272
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:491
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:330
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:337
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:632
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:463
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:470
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:296
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:303
|
||||
msgid "Email"
|
||||
msgstr ""
|
||||
msgstr "E-Mail"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:184
|
||||
msgid "Email Options"
|
||||
msgstr ""
|
||||
msgstr "E-Mail-Optionen"
|
||||
|
||||
#: packages/lib/constants/template.ts:8
|
||||
msgid "Enable Direct Link Signing"
|
||||
msgstr ""
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:392
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:362
|
||||
msgid "Enable signing order"
|
||||
msgstr ""
|
||||
msgstr "Direktlink-Signierung aktivieren"
|
||||
|
||||
#: packages/ui/primitives/document-password-dialog.tsx:84
|
||||
msgid "Enter password"
|
||||
msgstr ""
|
||||
msgstr "Passwort eingeben"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:216
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
msgstr "Fehler"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:241
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:210
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:249
|
||||
msgid "External ID"
|
||||
msgstr ""
|
||||
msgstr "Externe ID"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:217
|
||||
msgid "Failed to save settings."
|
||||
msgstr ""
|
||||
msgstr "Einstellungen konnten nicht gespeichert werden."
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:90
|
||||
msgid "Field character limit"
|
||||
msgstr ""
|
||||
msgstr "Zeichenbeschränkung des Feldes"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:107
|
||||
msgid "Field format"
|
||||
msgstr ""
|
||||
msgstr "Feldformat"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:50
|
||||
msgid "Field label"
|
||||
msgstr ""
|
||||
msgstr "Feldbeschriftung"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:62
|
||||
msgid "Field placeholder"
|
||||
msgstr ""
|
||||
msgstr "Feldplatzhalter"
|
||||
|
||||
#: packages/ui/components/document/document-global-auth-action-select.tsx:64
|
||||
msgid "Global recipient action authentication"
|
||||
msgstr ""
|
||||
msgstr "Globale Empfängerauthentifizierung"
|
||||
|
||||
#: packages/ui/primitives/document-flow/document-flow-root.tsx:142
|
||||
msgid "Go Back"
|
||||
msgstr ""
|
||||
msgstr "Zurück"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:297
|
||||
msgid "Green"
|
||||
@@ -330,147 +326,150 @@ msgstr ""
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:72
|
||||
msgid "I am a signer of this document"
|
||||
msgstr ""
|
||||
msgstr "Ich bin ein Unterzeichner dieses Dokuments"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:75
|
||||
msgid "I am a viewer of this document"
|
||||
msgstr ""
|
||||
msgstr "Ich bin ein Betrachter dieses Dokuments"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:73
|
||||
msgid "I am an approver of this document"
|
||||
msgstr ""
|
||||
msgstr "Ich bin ein Genehmiger dieses Dokuments"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:74
|
||||
msgid "I am required to receive a copy of this document"
|
||||
msgstr ""
|
||||
msgstr "Ich bin verpflichtet, eine Kopie dieses Dokuments zu erhalten"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:74
|
||||
#~ msgid "I am required to recieve a copy of this document"
|
||||
#~ msgstr "I am required to recieve a copy of this document"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:29
|
||||
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:87
|
||||
msgid "Inherit authentication method"
|
||||
msgstr ""
|
||||
msgstr "Authentifizierungsmethode erben"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:64
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:69
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:45
|
||||
msgid "Label"
|
||||
msgstr ""
|
||||
msgstr "Beschriftung"
|
||||
|
||||
#: packages/lib/constants/teams.ts:11
|
||||
msgid "Manager"
|
||||
msgstr ""
|
||||
msgstr "Manager"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:168
|
||||
msgid "Max"
|
||||
msgstr ""
|
||||
msgstr "Max"
|
||||
|
||||
#: packages/lib/constants/teams.ts:12
|
||||
msgid "Member"
|
||||
msgstr ""
|
||||
msgstr "Mitglied"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx:95
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:215
|
||||
msgid "Message <0>(Optional)</0>"
|
||||
msgstr ""
|
||||
msgstr "Nachricht <0>(Optional)</0>"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:156
|
||||
msgid "Min"
|
||||
msgstr ""
|
||||
msgstr "Min"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:814
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:802
|
||||
#: packages/ui/primitives/document-flow/add-signature.tsx:298
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:526
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:532
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:364
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:658
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:498
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:504
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:327
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:333
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
msgstr "Name"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-role-select.tsx:52
|
||||
msgid "Needs to approve"
|
||||
msgstr ""
|
||||
msgstr "Muss genehmigen"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-role-select.tsx:31
|
||||
msgid "Needs to sign"
|
||||
msgstr ""
|
||||
msgstr "Muss unterzeichnen"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-role-select.tsx:73
|
||||
msgid "Needs to view"
|
||||
msgstr ""
|
||||
msgstr "Muss sehen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:625
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:613
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:497
|
||||
msgid "No recipient matching this description was found."
|
||||
msgstr ""
|
||||
msgstr "Kein passender Empfänger mit dieser Beschreibung gefunden."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:641
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:629
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:513
|
||||
msgid "No recipients with this role"
|
||||
msgstr ""
|
||||
msgstr "Keine Empfänger mit dieser Rolle"
|
||||
|
||||
#: packages/ui/components/document/document-global-auth-access-select.tsx:30
|
||||
#: packages/ui/components/document/document-global-auth-access-select.tsx:43
|
||||
#: packages/ui/components/document/document-global-auth-action-select.tsx:31
|
||||
#: packages/ui/components/document/document-global-auth-action-select.tsx:46
|
||||
msgid "No restrictions"
|
||||
msgstr ""
|
||||
msgstr "Keine Einschränkungen"
|
||||
|
||||
#: packages/ui/primitives/data-table.tsx:148
|
||||
msgid "No results found"
|
||||
msgstr ""
|
||||
msgstr "Keine Ergebnisse gefunden"
|
||||
|
||||
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx:30
|
||||
msgid "No signature field found"
|
||||
msgstr ""
|
||||
msgstr "Kein Unterschriftsfeld gefunden"
|
||||
|
||||
#: packages/ui/primitives/combobox.tsx:60
|
||||
#: packages/ui/primitives/multi-select-combobox.tsx:153
|
||||
msgid "No value found."
|
||||
msgstr ""
|
||||
msgstr "Kein Wert gefunden."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:892
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:880
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:736
|
||||
msgid "Number"
|
||||
msgstr ""
|
||||
msgstr "Nummer"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:100
|
||||
msgid "Number format"
|
||||
msgstr ""
|
||||
msgstr "Zahlenformat"
|
||||
|
||||
#: packages/lib/constants/template.ts:9
|
||||
msgid "Once enabled, you can select any active recipient to be a direct link signing recipient, or create a new one. This recipient type cannot be edited or deleted."
|
||||
msgstr ""
|
||||
msgstr "Sobald aktiviert, können Sie einen aktiven Empfänger für die Direktlink-Signierung auswählen oder einen neuen erstellen. Dieser Empfängertyp kann nicht bearbeitet oder gelöscht werden."
|
||||
|
||||
#: packages/lib/constants/template.ts:17
|
||||
msgid "Once your template is set up, share the link anywhere you want. The person who opens the link will be able to enter their information in the direct link recipient field and complete any other fields assigned to them."
|
||||
msgstr ""
|
||||
msgstr "Sobald Ihre Vorlage eingerichtet ist, teilen Sie den Link überall, wo Sie möchten. Die Person, die den Link öffnet, kann ihre Informationen im Feld für direkte Empfänger eingeben und alle anderen ihr zugewiesenen Felder ausfüllen."
|
||||
|
||||
#: packages/ui/primitives/data-table-pagination.tsx:77
|
||||
msgid "Page {0} of {1}"
|
||||
msgstr ""
|
||||
msgstr "Seite {0} von {1}"
|
||||
|
||||
#: packages/ui/primitives/document-password-dialog.tsx:62
|
||||
msgid "Password Required"
|
||||
msgstr ""
|
||||
msgstr "Passwort erforderlich"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:154
|
||||
msgid "Pick a number"
|
||||
msgstr ""
|
||||
msgstr "Wählen Sie eine Zahl"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:76
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:81
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:57
|
||||
msgid "Placeholder"
|
||||
msgstr ""
|
||||
msgstr "Platzhalter"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:918
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:906
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:762
|
||||
msgid "Radio"
|
||||
msgstr ""
|
||||
msgstr "Radio"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/radio-field.tsx:133
|
||||
msgid "Radio values"
|
||||
msgstr ""
|
||||
msgstr "Radio-Werte"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:184
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:137
|
||||
@@ -478,30 +477,30 @@ msgstr ""
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/radio-field.tsx:122
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:114
|
||||
msgid "Read only"
|
||||
msgstr ""
|
||||
msgstr "Nur lesen"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-role-select.tsx:95
|
||||
msgid "Receives copy"
|
||||
msgstr ""
|
||||
msgstr "Erhält Kopie"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:39
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:215
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:184
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:169
|
||||
msgid "Recipient action authentication"
|
||||
msgstr ""
|
||||
msgstr "Empfängeraktion Authentifizierung"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:283
|
||||
msgid "Red"
|
||||
msgstr ""
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:329
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:298
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:332
|
||||
msgid "Redirect URL"
|
||||
msgstr ""
|
||||
msgstr "Weiterleitungs-URL"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:1008
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:996
|
||||
msgid "Remove"
|
||||
msgstr ""
|
||||
msgstr "Entfernen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:174
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:127
|
||||
@@ -509,264 +508,268 @@ msgstr ""
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/radio-field.tsx:112
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:104
|
||||
msgid "Required field"
|
||||
msgstr ""
|
||||
msgstr "Pflichtfeld"
|
||||
|
||||
#: packages/ui/primitives/data-table-pagination.tsx:55
|
||||
msgid "Rows per page"
|
||||
msgstr ""
|
||||
msgstr "Zeilen pro Seite"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:286
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
msgstr "Speichern"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:848
|
||||
msgid "Save Template"
|
||||
msgstr ""
|
||||
msgstr "Vorlage speichern"
|
||||
|
||||
#: packages/ui/components/common/language-switcher-dialog.tsx:34
|
||||
msgid "Search languages..."
|
||||
msgstr ""
|
||||
msgstr "Sprachen suchen..."
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:105
|
||||
msgid "Select"
|
||||
msgstr ""
|
||||
msgstr "Auswählen"
|
||||
|
||||
#: packages/ui/primitives/combobox.tsx:38
|
||||
msgid "Select an option"
|
||||
msgstr ""
|
||||
msgstr "Option auswählen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:137
|
||||
msgid "Select at least"
|
||||
msgstr ""
|
||||
msgstr "Wählen Sie mindestens"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:95
|
||||
msgid "Select default option"
|
||||
msgstr ""
|
||||
msgstr "Standardoption auswählen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx:124
|
||||
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:34
|
||||
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:64
|
||||
msgid "Send"
|
||||
msgstr ""
|
||||
msgstr "Senden"
|
||||
|
||||
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:41
|
||||
msgid "Send Document"
|
||||
msgstr ""
|
||||
msgstr "Dokument senden"
|
||||
|
||||
#: packages/ui/components/document/document-share-button.tsx:135
|
||||
msgid "Share Signature Card"
|
||||
msgstr ""
|
||||
msgstr "Unterschriftenkarte teilen"
|
||||
|
||||
#: packages/lib/constants/template.ts:16
|
||||
msgid "Share the Link"
|
||||
msgstr ""
|
||||
msgstr "Link teilen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:671
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:655
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:477
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:468
|
||||
msgid "Show advanced settings"
|
||||
msgstr ""
|
||||
msgstr "Erweiterte Einstellungen anzeigen"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:20
|
||||
msgid "Sign"
|
||||
msgstr ""
|
||||
msgstr "Unterschreiben"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:736
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:724
|
||||
#: packages/ui/primitives/document-flow/add-signature.tsx:323
|
||||
#: packages/ui/primitives/document-flow/field-icon.tsx:52
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:580
|
||||
msgid "Signature"
|
||||
msgstr ""
|
||||
msgstr "Unterschrift"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:21
|
||||
msgid "Signed"
|
||||
msgstr ""
|
||||
msgstr "Unterzeichnet"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:23
|
||||
msgid "Signer"
|
||||
msgstr ""
|
||||
msgstr "Unterzeichner"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:22
|
||||
msgid "Signing"
|
||||
msgstr ""
|
||||
msgstr "Unterzeichnung"
|
||||
|
||||
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx:34
|
||||
msgid "Some signers have not been assigned a signature field. Please assign at least 1 signature field to each signer before proceeding."
|
||||
msgstr ""
|
||||
msgstr "Einige Unterzeichner haben noch kein Unterschriftsfeld zugewiesen bekommen. Bitte weisen Sie jedem Unterzeichner mindestens ein Unterschriftsfeld zu, bevor Sie fortfahren."
|
||||
|
||||
#: packages/ui/components/document/document-share-button.tsx:51
|
||||
msgid "Something went wrong"
|
||||
msgstr ""
|
||||
msgstr "Etwas ist schief gelaufen"
|
||||
|
||||
#: packages/ui/primitives/data-table.tsx:136
|
||||
msgid "Something went wrong."
|
||||
msgstr ""
|
||||
msgstr "Etwas ist schief gelaufen."
|
||||
|
||||
#: packages/ui/primitives/document-flow/document-flow-root.tsx:107
|
||||
msgid "Step <0>{step} of {maxStep}</0>"
|
||||
msgstr ""
|
||||
msgstr "Schritt <0>{step} von {maxStep}</0>"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx:78
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:195
|
||||
msgid "Subject <0>(Optional)</0>"
|
||||
msgstr ""
|
||||
msgstr "Betreff <0>(Optional)</0>"
|
||||
|
||||
#: packages/ui/primitives/document-password-dialog.tsx:97
|
||||
msgid "Submit"
|
||||
msgstr ""
|
||||
msgstr "Einreichen"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:134
|
||||
msgid "Template title"
|
||||
msgstr ""
|
||||
msgstr "Vorlagentitel"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:866
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:854
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:710
|
||||
msgid "Text"
|
||||
msgstr ""
|
||||
msgstr "Text"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:44
|
||||
msgid "The authentication required for recipients to sign fields"
|
||||
msgstr ""
|
||||
msgstr "Die Authentifizierung, die erforderlich ist, damit Empfänger Felder signieren"
|
||||
|
||||
#: packages/ui/components/document/document-global-auth-action-select.tsx:68
|
||||
msgid "The authentication required for recipients to sign the signature field."
|
||||
msgstr ""
|
||||
msgstr "Die Authentifizierung, die erforderlich ist, damit Empfänger das Signaturfeld signieren können."
|
||||
|
||||
#: packages/ui/components/document/document-global-auth-access-select.tsx:67
|
||||
msgid "The authentication required for recipients to view the document."
|
||||
msgstr ""
|
||||
msgstr "Die Authentifizierung, die erforderlich ist, damit Empfänger das Dokument anzeigen können."
|
||||
|
||||
#: packages/ui/components/document/document-send-email-message-helper.tsx:31
|
||||
msgid "The document's name"
|
||||
msgstr ""
|
||||
msgstr "Der Name des Dokuments"
|
||||
|
||||
#: packages/ui/primitives/document-password-dialog.tsx:52
|
||||
msgid "The password you have entered is incorrect. Please try again."
|
||||
msgstr ""
|
||||
msgstr "Das eingegebene Passwort ist falsch. Bitte versuchen Sie es erneut."
|
||||
|
||||
#: packages/ui/components/recipient/recipient-role-select.tsx:103
|
||||
msgid "The recipient is not required to take any action and receives a copy of the document after it is completed."
|
||||
msgstr ""
|
||||
msgstr "Der Empfänger muss keine Aktion ausführen und erhält nach Abschluss eine Kopie des Dokuments."
|
||||
|
||||
#: packages/ui/components/recipient/recipient-role-select.tsx:60
|
||||
msgid "The recipient is required to approve the document for it to be completed."
|
||||
msgstr ""
|
||||
msgstr "Der Empfänger muss das Dokument genehmigen, damit es abgeschlossen werden kann."
|
||||
|
||||
#: packages/ui/components/recipient/recipient-role-select.tsx:39
|
||||
msgid "The recipient is required to sign the document for it to be completed."
|
||||
msgstr ""
|
||||
msgstr "Der Empfänger muss das Dokument unterschreiben, damit es abgeschlossen werden kann."
|
||||
|
||||
#: packages/ui/components/recipient/recipient-role-select.tsx:81
|
||||
msgid "The recipient is required to view the document for it to be completed."
|
||||
msgstr ""
|
||||
msgstr "Der Empfänger muss das Dokument anzeigen, damit es abgeschlossen werden kann."
|
||||
|
||||
#: packages/ui/components/document/document-share-button.tsx:52
|
||||
msgid "The sharing link could not be created at this time. Please try again."
|
||||
msgstr ""
|
||||
msgstr "Der Freigabelink konnte in diesem Moment nicht erstellt werden. Bitte versuchen Sie es erneut."
|
||||
|
||||
#: packages/ui/components/document/document-share-button.tsx:47
|
||||
msgid "The sharing link has been copied to your clipboard."
|
||||
msgstr ""
|
||||
msgstr "Der Freigabelink wurde in Ihre Zwischenablage kopiert."
|
||||
|
||||
#: packages/ui/components/document/document-send-email-message-helper.tsx:25
|
||||
msgid "The signer's email"
|
||||
msgstr ""
|
||||
msgstr "Die E-Mail des Unterzeichners"
|
||||
|
||||
#: packages/ui/components/document/document-send-email-message-helper.tsx:19
|
||||
msgid "The signer's name"
|
||||
msgstr ""
|
||||
msgstr "Der Name des Unterzeichners"
|
||||
|
||||
#: packages/ui/components/document/document-global-auth-action-select.tsx:72
|
||||
msgid "This can be overriden by setting the authentication requirements directly on each recipient in the next step."
|
||||
msgstr ""
|
||||
msgstr "Dies kann überschrieben werden, indem die Authentifizierungsanforderungen im nächsten Schritt direkt für jeden Empfänger festgelegt werden."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:697
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:685
|
||||
msgid "This document has already been sent to this recipient. You can no longer edit this recipient."
|
||||
msgstr ""
|
||||
msgstr "Dieses Dokument wurde bereits an diesen Empfänger gesendet. Sie können diesen Empfänger nicht mehr bearbeiten."
|
||||
|
||||
#: packages/ui/primitives/document-password-dialog.tsx:66
|
||||
msgid "This document is password protected. Please enter the password to view the document."
|
||||
msgstr ""
|
||||
msgstr "Dieses Dokument ist durch ein Passwort geschützt. Bitte geben Sie das Passwort ein, um das Dokument anzusehen."
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:573
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:396
|
||||
msgid "This field cannot be modified or deleted. When you share this template's direct link or add it to your public profile, anyone who accesses it can input their name and email, and fill in the fields assigned to them."
|
||||
msgstr ""
|
||||
msgstr "Dieses Feld kann nicht geändert oder gelöscht werden. Wenn Sie den direkten Link dieser Vorlage teilen oder zu Ihrem öffentlichen Profil hinzufügen, kann jeder, der darauf zugreift, seinen Namen und seine E-Mail-Adresse eingeben und die ihm zugewiesenen Felder ausfüllen."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:195
|
||||
msgid "This signer has already received the document."
|
||||
msgstr ""
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:165
|
||||
#~ msgid "This signer has already received the document."
|
||||
#~ msgstr "Dieser Unterzeichner hat das Dokument bereits erhalten."
|
||||
|
||||
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:48
|
||||
msgid "This will override any global settings."
|
||||
msgstr ""
|
||||
msgstr "Dies überschreibt alle globalen Einstellungen."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:305
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:274
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:309
|
||||
msgid "Time Zone"
|
||||
msgstr ""
|
||||
msgstr "Zeitzone"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:153
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:145
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
msgstr "Titel"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:983
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:971
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:828
|
||||
msgid "To proceed further, please set at least one value for the {0} field."
|
||||
msgstr ""
|
||||
msgstr "Um fortzufahren, legen Sie bitte mindestens einen Wert für das Feld {0} fest."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx:124
|
||||
msgid "Update"
|
||||
msgstr ""
|
||||
msgstr "Aktualisieren"
|
||||
|
||||
#: packages/lib/constants/template.ts:13
|
||||
msgid "Update the role and add fields as required for the direct recipient. The individual who uses the direct link will sign the document as the direct recipient."
|
||||
msgstr ""
|
||||
msgstr "Aktualisieren Sie die Rolle und fügen Sie Felder nach Bedarf für den direkten Empfänger hinzu. Die Person, die den direkten Link verwendet, wird das Dokument als direkter Empfänger unterzeichnen."
|
||||
|
||||
#: packages/ui/primitives/document-dropzone.tsx:168
|
||||
msgid "Upgrade"
|
||||
msgstr ""
|
||||
msgstr "Upgrade"
|
||||
|
||||
#: packages/ui/primitives/document-dropzone.tsx:70
|
||||
msgid "Upload Template Document"
|
||||
msgstr ""
|
||||
msgstr "Vorlagendokument hochladen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:130
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:147
|
||||
msgid "Validation"
|
||||
msgstr ""
|
||||
msgstr "Validierung"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:88
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:93
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
msgstr "Wert"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:26
|
||||
msgid "View"
|
||||
msgstr ""
|
||||
msgstr "View"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:27
|
||||
msgid "Viewed"
|
||||
msgstr ""
|
||||
msgstr "Viewed"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:29
|
||||
msgid "Viewer"
|
||||
msgstr ""
|
||||
msgstr "Viewer"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:28
|
||||
msgid "Viewing"
|
||||
msgstr ""
|
||||
msgstr "Viewing"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:280
|
||||
#~ msgid "White"
|
||||
#~ msgstr ""
|
||||
|
||||
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:44
|
||||
msgid "You are about to send this document to the recipients. Are you sure you want to continue?"
|
||||
msgstr ""
|
||||
msgstr "Sie sind dabei, dieses Dokument an die Empfänger zu senden. Sind Sie sicher, dass Sie fortfahren möchten?"
|
||||
|
||||
#: packages/ui/components/document/document-send-email-message-helper.tsx:11
|
||||
msgid "You can use the following variables in your message:"
|
||||
msgstr ""
|
||||
msgstr "Sie können die folgenden Variablen in Ihrer Nachricht verwenden:"
|
||||
|
||||
#: packages/ui/primitives/document-dropzone.tsx:43
|
||||
msgid "You cannot upload documents at this time."
|
||||
msgstr ""
|
||||
msgstr "Sie können derzeit keine Dokumente hochladen."
|
||||
|
||||
#: packages/ui/primitives/document-dropzone.tsx:69
|
||||
msgid "You have reached your document limit."
|
||||
msgstr ""
|
||||
msgstr "Sie haben Ihr Dokumentenlimit erreicht."
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,38 +1,33 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"POT-Creation-Date: 2024-07-24 13:01+1000\n"
|
||||
"POT-Creation-Date: 2024-08-22 05:16+0000\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: @lingui/cli\n"
|
||||
"Language: de\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2024-09-05 06:04\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: documenso-app\n"
|
||||
"X-Crowdin-Project-ID: 694691\n"
|
||||
"X-Crowdin-Language: de\n"
|
||||
"X-Crowdin-File: marketing.po\n"
|
||||
"X-Crowdin-File-ID: 6\n"
|
||||
"Language-Team: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/blog/page.tsx:45
|
||||
msgid "{0}"
|
||||
msgstr "{0}"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:100
|
||||
msgid "5 standard documents per month"
|
||||
msgstr "5 Standarddokumente pro Monat"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:198
|
||||
msgid "5 Users Included"
|
||||
msgstr "5 Benutzer inbegriffen"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:34
|
||||
msgid "A 10x better signing experience."
|
||||
msgstr "Eine 10x bessere Signaturerfahrung."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/singleplayer/client.tsx:51
|
||||
msgid "Add document"
|
||||
@@ -40,142 +35,138 @@ msgstr "Dokument hinzufügen"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:201
|
||||
msgid "Add More Users for {0}"
|
||||
msgstr "Mehr Benutzer hinzufügen für {0}"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:165
|
||||
msgid "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>"
|
||||
msgstr "Alle unsere Kennzahlen, Finanzen und Erkenntnisse sind öffentlich. Wir glauben an Transparenz und möchten unsere Reise mit Ihnen teilen. Mehr erfahren Sie hier: <0>Ankündigung Offene Kennzahlen</0>"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/funding-raised.tsx:58
|
||||
#: apps/marketing/src/app/(marketing)/open/funding-raised.tsx:65
|
||||
msgid "Amount Raised"
|
||||
msgstr "Erhobener Betrag"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:145
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:189
|
||||
msgid "API Access"
|
||||
msgstr "API-Zugriff"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:67
|
||||
msgid "Beautiful."
|
||||
msgstr "Schön."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:69
|
||||
msgid "Because signing should be celebrated. That’s why we care about the smallest detail in our product."
|
||||
msgstr "Weil Unterschriften gefeiert werden sollten. Deshalb kümmern wir uns um jedes kleinste Detail in unserem Produkt."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/footer.tsx:35
|
||||
#: apps/marketing/src/components/(marketing)/header.tsx:57
|
||||
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:36
|
||||
msgid "Blog"
|
||||
msgstr "Blog"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:64
|
||||
msgid "Build on top."
|
||||
msgstr "Aufbauen oben drauf."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:163
|
||||
msgid "Can I use Documenso commercially?"
|
||||
msgstr "Kann ich Documenso kommerziell nutzen?"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/footer.tsx:42
|
||||
msgid "Careers"
|
||||
msgstr "Karrieren"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/footer.tsx:36
|
||||
msgid "Changelog"
|
||||
msgstr "Änderungsprotokoll"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:85
|
||||
msgid "Choose a template from the community app store. Or submit your own template for others to use."
|
||||
msgstr "Wählen Sie eine Vorlage aus dem Community-App-Store. Oder reichen Sie Ihre eigene Vorlage ein, damit andere sie benutzen können."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:219
|
||||
msgid "Community"
|
||||
msgstr "Gemeinschaft"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/monthly-completed-documents-chart.tsx:55
|
||||
msgid "Completed Documents"
|
||||
msgstr "Fertige Dokumente"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/monthly-completed-documents-chart.tsx:33
|
||||
msgid "Completed Documents per Month"
|
||||
msgstr "Fertige Dokumente pro Monat"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:65
|
||||
msgid "Connections"
|
||||
msgstr "Verbindungen"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/enterprise.tsx:35
|
||||
msgid "Contact Us"
|
||||
msgstr "Kontaktiere uns"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:67
|
||||
msgid "Create connections and automations with Zapier and more to integrate with your favorite tools."
|
||||
msgstr "Erstellen Sie Verbindungen und Automatisierungen mit Zapier und mehr, um sich mit Ihren Lieblingstools zu integrieren."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/call-to-action.tsx:23
|
||||
msgid "Create your account and start using state-of-the-art document signing. Open and beautiful signing is within your grasp."
|
||||
msgstr "Erstellen Sie Ihr Konto und beginnen Sie mit der Nutzung modernster Dokumentensignaturen. Offene und schöne Signaturen sind zum Greifen nah."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/tooltip.tsx:35
|
||||
msgid "Customers with an Active Subscriptions."
|
||||
msgstr "Kunden mit einer aktiven Abonnements."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:33
|
||||
msgid "Customise and expand."
|
||||
msgstr "Anpassen und erweitern."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/footer.tsx:38
|
||||
msgid "Design"
|
||||
msgstr "Design"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:44
|
||||
msgid "Designed for every stage of your journey."
|
||||
msgstr "Entwickelt für jede Phase Ihrer Reise."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:40
|
||||
msgid "Direct Link"
|
||||
msgstr "Direktlink"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:181
|
||||
msgid "Documenso is a community effort to create an open and vibrant ecosystem around a tool, everybody is free to use and adapt. By being truly open we want to create trusted infrastructure for the future of the internet."
|
||||
msgstr "Documenso ist eine Gemeinschaftsanstrengung, um ein offenes und lebendiges Ökosystem um ein Werkzeug zu schaffen, das jeder frei nutzen und anpassen kann. Indem wir wirklich offen sind, wollen wir vertrauenswürdige Infrastruktur für die Zukunft des Internets schaffen."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/typefully.tsx:28
|
||||
msgid "Documenso on X"
|
||||
msgstr "Documenso auf X"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/hero.tsx:104
|
||||
msgid "Document signing,<0/>finally open source."
|
||||
msgstr "Unterschriften,<0/>endlich Open Source."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/footer.tsx:33
|
||||
#: apps/marketing/src/components/(marketing)/header.tsx:50
|
||||
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:28
|
||||
msgid "Documentation"
|
||||
msgstr "Dokumentation"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:110
|
||||
msgid "Easily embed Documenso into your product. Simply copy and paste our react widget into your application."
|
||||
msgstr "Betten Sie Documenso ganz einfach in Ihr Produkt ein. Kopieren und fügen Sie einfach unser React-Widget in Ihre Anwendung ein."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:42
|
||||
#~ msgid "Easy Sharing (Soon)."
|
||||
#~ msgstr "Easy Sharing (Soon)."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:46
|
||||
msgid "Easy Sharing."
|
||||
msgstr "Einfaches Teilen."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:148
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:192
|
||||
msgid "Email and Discord Support"
|
||||
msgstr "E-Mail- und Discord-Support"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/team-members.tsx:43
|
||||
msgid "Engagement"
|
||||
msgstr "Beteiligung"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/singleplayer/client.tsx:64
|
||||
msgid "Enter your details."
|
||||
@@ -183,43 +174,43 @@ msgstr "Geben Sie Ihre Details ein."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/enterprise.tsx:16
|
||||
msgid "Enterprise Compliance, License or Technical Needs?"
|
||||
msgstr "Enterprise-Konformität, Lizenz- oder technische Bedürfnisse?"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:128
|
||||
msgid "Everything you need for a great signing experience."
|
||||
msgstr "Alles, was Sie für ein großartiges Signaturerlebnis benötigen."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:45
|
||||
msgid "Fast."
|
||||
msgstr "Schnell."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:36
|
||||
msgid "Faster, smarter and more beautiful."
|
||||
msgstr "Schneller, intelligenter und schöner."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:210
|
||||
msgid "Finances"
|
||||
msgstr "Finanzen"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/typefully.tsx:38
|
||||
msgid "Follow us on X"
|
||||
msgstr "Folgen Sie uns auf X"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:172
|
||||
msgid "For companies looking to scale across multiple teams."
|
||||
msgstr "Für Unternehmen, die über mehrere Teams skalieren möchten."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:85
|
||||
msgid "For small teams and individuals with basic needs."
|
||||
msgstr "Für kleine Teams und Einzelpersonen mit grundlegenden Bedürfnissen."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:80
|
||||
msgid "Free"
|
||||
msgstr "Kostenlos"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/blog/page.tsx:26
|
||||
msgid "From the blog"
|
||||
msgstr "Aus dem Blog"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/data.ts:9
|
||||
#: apps/marketing/src/app/(marketing)/open/data.ts:17
|
||||
@@ -227,227 +218,222 @@ msgstr "Aus dem Blog"
|
||||
#: apps/marketing/src/app/(marketing)/open/data.ts:41
|
||||
#: apps/marketing/src/app/(marketing)/open/data.ts:49
|
||||
msgid "Full-Time"
|
||||
msgstr "Vollzeit"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:87
|
||||
msgid "Get paid (Soon)."
|
||||
msgstr "Lassen Sie sich bezahlen (Bald)."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/call-to-action.tsx:31
|
||||
msgid "Get started"
|
||||
msgstr "Loslegen"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:75
|
||||
msgid "Get Started"
|
||||
msgstr "Loslegen"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:47
|
||||
msgid "Get started today."
|
||||
msgstr "Fangen Sie heute an."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/blog/page.tsx:30
|
||||
msgid "Get the latest news from Documenso, including product updates, team announcements and more!"
|
||||
msgstr "Erhalten Sie die neuesten Nachrichten von Documenso, einschließlich Produkt-Updates, Team-Ankündigungen und mehr!"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:233
|
||||
msgid "GitHub: Total Merged PRs"
|
||||
msgstr "GitHub: Gesamte PRs zusammengeführt"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:251
|
||||
msgid "GitHub: Total Open Issues"
|
||||
msgstr "GitHub: Gesamte offene Issues"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:225
|
||||
msgid "GitHub: Total Stars"
|
||||
msgstr "GitHub: Gesamtanzahl Sterne"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/salary-bands.tsx:23
|
||||
msgid "Global Salary Bands"
|
||||
msgstr "Globale Gehaltsbänder"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:261
|
||||
msgid "Growth"
|
||||
msgstr "Wachstum"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:134
|
||||
msgid "How can I contribute?"
|
||||
msgstr "Wie kann ich beitragen?"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:105
|
||||
msgid "How do you handle my data?"
|
||||
msgstr "Wie gehen Sie mit meinen Daten um?"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:118
|
||||
msgid "Individual"
|
||||
msgstr "Einzelperson"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:89
|
||||
msgid "Integrated payments with Stripe so you don’t have to worry about getting paid."
|
||||
msgstr "Integrierte Zahlungen mit Stripe, sodass Sie sich keine Sorgen ums Bezahlen machen müssen."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:35
|
||||
msgid "Integrates with all your favourite tools."
|
||||
msgstr "Integriert sich mit all Ihren Lieblingstools."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:289
|
||||
msgid "Is there more?"
|
||||
msgstr "Gibt es mehr?"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:44
|
||||
msgid "It’s up to you. Either clone our repository or rely on our easy to use hosting solution."
|
||||
msgstr "Es liegt an Ihnen. Entweder klonen Sie unser Repository oder nutzen unsere einfach zu bedienende Hosting-Lösung."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/team-members.tsx:49
|
||||
msgid "Join Date"
|
||||
msgstr "Eintrittsdatum"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/call-to-action.tsx:19
|
||||
msgid "Join the Open Signing Movement"
|
||||
msgstr "Treten Sie der Open Signing-Bewegung bei"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/team-members.tsx:46
|
||||
msgid "Location"
|
||||
msgstr "Standort"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:66
|
||||
msgid "Make it your own through advanced customization and adjustability."
|
||||
msgstr "Machen Sie es zu Ihrem eigenen durch erweiterte Anpassung und Einstellbarkeit."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:199
|
||||
msgid "Merged PR's"
|
||||
msgstr "Zusammengeführte PRs"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:234
|
||||
msgid "Merged PRs"
|
||||
msgstr "Zusammengeführte PRs"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:40
|
||||
msgid "Monthly"
|
||||
msgstr "Monatlich"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/team-members.tsx:34
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/monthly-new-users-chart.tsx:30
|
||||
#: apps/marketing/src/app/(marketing)/open/monthly-new-users-chart.tsx:43
|
||||
#: apps/marketing/src/app/(marketing)/open/monthly-new-users-chart.tsx:52
|
||||
msgid "New Users"
|
||||
msgstr "Neue Benutzer"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:106
|
||||
msgid "No credit card required"
|
||||
msgstr "Keine Kreditkarte erforderlich"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/callout.tsx:29
|
||||
#: apps/marketing/src/components/(marketing)/hero.tsx:125
|
||||
msgid "No Credit Card required"
|
||||
msgstr "Keine Kreditkarte erforderlich"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:61
|
||||
msgid "None of these work for you? Try self-hosting!"
|
||||
msgstr "Keines dieser Angebote passt zu Ihnen? Versuchen Sie das Selbst-Hosting!"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:194
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:252
|
||||
msgid "Open Issues"
|
||||
msgstr "Offene Issues"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:42
|
||||
msgid "Open Source or Hosted."
|
||||
msgstr "Open Source oder Hosted."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:161
|
||||
#: apps/marketing/src/components/(marketing)/footer.tsx:37
|
||||
#: apps/marketing/src/components/(marketing)/header.tsx:64
|
||||
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:40
|
||||
msgid "Open Startup"
|
||||
msgstr "Offenes Startup"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/footer.tsx:41
|
||||
msgid "OSS Friends"
|
||||
msgstr "OSS-Freunde"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:91
|
||||
msgid "Our custom templates come with smart rules that can help you save time and energy."
|
||||
msgstr "Unsere benutzerdefinierten Vorlagen verfügen über intelligente Regeln, die Ihnen Zeit und Energie sparen können."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/enterprise.tsx:20
|
||||
msgid "Our Enterprise License is great for large organizations looking to switch to Documenso for all their signing needs. It's available for our cloud offering as well as self-hosted setups and offers a wide range of compliance and Adminstration Features."
|
||||
msgstr "Unsere Enterprise-Lizenz ist ideal für große Organisationen, die auf Documenso für all ihre Signaturanforderungen umsteigen möchten. Sie ist sowohl für unser Cloud-Angebot als auch für selbstgehostete Setups verfügbar und bietet eine breite Palette an Compliance- und Verwaltungsfunktionen."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/enterprise.tsx:20
|
||||
#~ msgid "Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features."
|
||||
#~ msgstr "Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:65
|
||||
msgid "Our self-hosted option is great for small teams and individuals who need a simple solution. You can use our docker based setup to get started in minutes. Take control with full customizability and data ownership."
|
||||
msgstr "Unsere selbstgehostete Option ist ideal für kleine Teams und Einzelpersonen, die eine einfache Lösung benötigen. Sie können unser docker-basiertes Setup verwenden, um in wenigen Minuten loszulegen. Übernehmen Sie die Kontrolle mit vollständiger Anpassbarkeit und Datenhoheit."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/data.ts:25
|
||||
msgid "Part-Time"
|
||||
msgstr "Teilzeit"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:151
|
||||
msgid "Premium Profile Name"
|
||||
msgstr "Premium Profilname"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:40
|
||||
#: apps/marketing/src/components/(marketing)/footer.tsx:31
|
||||
#: apps/marketing/src/components/(marketing)/header.tsx:42
|
||||
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:24
|
||||
msgid "Pricing"
|
||||
msgstr "Preise"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/footer.tsx:43
|
||||
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:53
|
||||
msgid "Privacy"
|
||||
msgstr "Datenschutz"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:58
|
||||
msgid "Profile"
|
||||
msgstr "Profil"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:108
|
||||
msgid "React Widget (Soon)."
|
||||
msgstr "React Widget (Demnächst)."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:48
|
||||
msgid "Receive your personal link to share with everyone you care about."
|
||||
msgstr "Erhalten Sie Ihren persönlichen Link zum Teilen mit allen, die Ihnen wichtig sind."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/team-members.tsx:37
|
||||
msgid "Role"
|
||||
msgstr "Rolle"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/salary-bands.tsx:37
|
||||
#: apps/marketing/src/app/(marketing)/open/team-members.tsx:40
|
||||
msgid "Salary"
|
||||
msgstr "Gehalt"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:62
|
||||
msgid "Save $60 or $120"
|
||||
msgstr "Sparen Sie $60 oder $120"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/i18n-switcher.tsx:47
|
||||
#~ msgid "Search languages..."
|
||||
#~ msgstr "Sprachen suchen..."
|
||||
#~ msgstr "Sprachen suchen...>>>>>>> main"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
|
||||
msgid "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."
|
||||
msgstr "Sicher. Unsere Rechenzentren befinden sich in Frankfurt (Deutschland) und bieten uns die besten lokalen Datenschutzgesetze. Uns ist die sensible Natur unserer Daten sehr bewusst und wir folgen bewährten Praktiken, um die Sicherheit und Integrität der uns anvertrauten Daten zu gewährleisten."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:37
|
||||
msgid "Send, connect, receive and embed everywhere."
|
||||
msgstr "Überall senden, verbinden, empfangen und einbetten."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/salary-bands.tsx:34
|
||||
msgid "Seniority"
|
||||
msgstr "Dienstalter"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/footer.tsx:39
|
||||
msgid "Shop"
|
||||
msgstr "Shop"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/singleplayer/client.tsx:63
|
||||
msgid "Sign"
|
||||
@@ -456,115 +442,115 @@ msgstr "Signieren"
|
||||
#: apps/marketing/src/components/(marketing)/header.tsx:72
|
||||
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:61
|
||||
msgid "Sign in"
|
||||
msgstr "Anmelden"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/header.tsx:77
|
||||
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:57
|
||||
msgid "Sign up"
|
||||
msgstr "Registrieren"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:22
|
||||
msgid "Signing Process"
|
||||
msgstr "Signaturprozess"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:94
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:136
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:180
|
||||
msgid "Signup Now"
|
||||
msgstr "Jetzt registrieren"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:89
|
||||
msgid "Smart."
|
||||
msgstr "Intelligent."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/hero.tsx:132
|
||||
msgid "Star on GitHub"
|
||||
msgstr "Auf GitHub favorisieren"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:226
|
||||
msgid "Stars"
|
||||
msgstr "Favoriten"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/footer.tsx:40
|
||||
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:44
|
||||
msgid "Status"
|
||||
msgstr "Status"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/footer.tsx:34
|
||||
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:48
|
||||
msgid "Support"
|
||||
msgstr "Support"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/team-members.tsx:26
|
||||
msgid "Team"
|
||||
msgstr "Team"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:195
|
||||
msgid "Team Inbox"
|
||||
msgstr "Team-Posteingang"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:28
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:162
|
||||
msgid "Teams"
|
||||
msgstr "Teams"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:83
|
||||
msgid "Template Store (Soon)."
|
||||
msgstr "Vorlagen-Shop (Demnächst)."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:138
|
||||
msgid "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️"
|
||||
msgstr "Das ist großartig. Sie können sich die aktuellen <0>Issues</0> ansehen und unserer <1>Discord-Community</1> beitreten, um auf dem neuesten Stand zu bleiben, was die aktuellen Prioritäten sind. In jedem Fall sind wir eine offene Gemeinschaft und begrüßen jegliche Beiträge, technische und nicht-technische ❤️"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:293
|
||||
msgid "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share."
|
||||
msgstr "Diese Seite entwickelt sich weiter, während wir lernen, was ein großartiges Signing-Unternehmen ausmacht. Wir werden sie aktualisieren, wenn wir mehr zu teilen haben."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/salary-bands.tsx:31
|
||||
msgid "Title"
|
||||
msgstr "Titel"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/total-signed-documents-chart.tsx:30
|
||||
#: apps/marketing/src/app/(marketing)/open/total-signed-documents-chart.tsx:55
|
||||
msgid "Total Completed Documents"
|
||||
msgstr "Insgesamt Abgeschlossene Dokumente"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:267
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:268
|
||||
msgid "Total Customers"
|
||||
msgstr "Insgesamt Kunden"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/funding-raised.tsx:29
|
||||
msgid "Total Funding Raised"
|
||||
msgstr "Insgesamt Finanzierungsvolumen"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/monthly-total-users-chart.tsx:30
|
||||
#: apps/marketing/src/app/(marketing)/open/monthly-total-users-chart.tsx:43
|
||||
#: apps/marketing/src/app/(marketing)/open/monthly-total-users-chart.tsx:52
|
||||
msgid "Total Users"
|
||||
msgstr "Gesamtanzahl der Benutzer"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:31
|
||||
msgid "Truly your own."
|
||||
msgstr "Wirklich Ihr Eigenes."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/callout.tsx:27
|
||||
#: apps/marketing/src/components/(marketing)/hero.tsx:123
|
||||
msgid "Try our Free Plan"
|
||||
msgstr "Probieren Sie unseren Gratisplan aus"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/typefully.tsx:20
|
||||
msgid "Twitter Stats"
|
||||
msgstr "Twitter-Statistiken"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:142
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:186
|
||||
msgid "Unlimited Documents per Month"
|
||||
msgstr "Unbegrenzte Dokumente pro Monat"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:103
|
||||
msgid "Up to 10 recipients per document"
|
||||
msgstr "Bis zu 10 Empfänger pro Dokument"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/singleplayer/client.tsx:52
|
||||
msgid "Upload a document and add fields."
|
||||
@@ -572,48 +558,52 @@ msgstr "Laden Sie ein Dokument hoch und fügen Sie Felder hinzu."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:123
|
||||
msgid "Using our hosted version is the easiest way to get started, you can simply subscribe and start signing your documents. We take care of the infrastructure, so you can focus on your business. Additionally, when using our hosted version you benefit from our trusted signing certificates which helps you to build trust with your customers."
|
||||
msgstr "Die Nutzung unserer gehosteten Version ist der einfachste Weg, um zu starten. Sie können einfach abonnieren und mit der Unterzeichnung Ihrer Dokumente beginnen. Wir kümmern uns um die Infrastruktur, damit Sie sich auf Ihr Geschäft konzentrieren können. Zudem profitieren Sie bei der Nutzung unserer gehosteten Version von unseren vertrauenswürdigen Signaturzertifikaten, die Ihnen helfen, Vertrauen bei Ihren Kunden aufzubauen."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/typefully.tsx:33
|
||||
msgid "View all stats"
|
||||
msgstr "Alle Statistiken anzeigen"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:195
|
||||
msgid "We are happy to assist you at <0>support@documenso.com</0> or <1>in our Discord-Support-Channel</1> please message either Lucas or Timur to get added to the channel if you are not already a member."
|
||||
msgstr "Wir helfen Ihnen gerne unter <0>support@documenso.com</0> oder <1>in unserem Discord-Support-Kanal</1>. Bitte senden Sie Lucas oder Timur eine Nachricht, um dem Kanal beizutreten, falls Sie noch kein Mitglied sind."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:89
|
||||
msgid "What is the difference between the plans?"
|
||||
msgstr "Was ist der Unterschied zwischen den Plänen?"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:47
|
||||
msgid "When it comes to sending or receiving a contract, you can count on lightning-fast speeds."
|
||||
msgstr "Wenn es um das Senden oder Empfangen eines Vertrags geht, können Sie auf blitzschnelle Geschwindigkeiten zählen."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:191
|
||||
msgid "Where can I get support?"
|
||||
msgstr "Wo kann ich Unterstützung bekommen?"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:177
|
||||
msgid "Why should I prefer Documenso over DocuSign or some other signing tool?"
|
||||
msgstr "Warum sollte ich Documenso gegenüber DocuSign oder einem anderen Signatur-Tool bevorzugen?"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:119
|
||||
msgid "Why should I use your hosting service?"
|
||||
msgstr "Warum sollte ich Ihren Hosting-Service nutzen?"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:60
|
||||
msgid "Yearly"
|
||||
msgstr "Jährlich"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:167
|
||||
msgid "Yes! Documenso is offered under the GNU AGPL V3 open source license. This means you can use it for free and even modify it to fit your needs, as long as you publish your changes under the same license."
|
||||
msgstr "Ja! Documenso wird unter der GNU AGPL V3 Open-Source-Lizenz angeboten. Das bedeutet, dass Sie es kostenlos nutzen und sogar an Ihre Bedürfnisse anpassen können, solange Sie Ihre Änderungen unter derselben Lizenz veröffentlichen."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:93
|
||||
msgid "You can self-host Documenso for free or use our ready-to-use hosted version. The hosted version comes with additional support, painless scalability and more. Early adopters will get access to all features we build this year, for no additional cost! Forever! Yes, that includes multiple users per account later. If you want Documenso for your enterprise, we are happy to talk about your needs."
|
||||
msgstr "Sie können Documenso kostenlos selbst hosten oder unsere sofort einsatzbereite gehostete Version nutzen. Die gehostete Version bietet zusätzlichen Support, schmerzfreie Skalierbarkeit und mehr. Frühzeitige Anwender erhalten in diesem Jahr Zugriff auf alle Funktionen, die wir entwickeln, ohne zusätzliche Kosten! Für immer! Ja, das beinhaltet später mehrere Benutzer pro Konto. Wenn Sie Documenso für Ihr Unternehmen möchten, sprechen wir gerne über Ihre Bedürfnisse."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:265
|
||||
msgid "Your browser does not support the video tag."
|
||||
msgstr "Ihr Browser unterstützt das Video-Tag nicht."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:265
|
||||
#~ msgid "Your browser does not support the video tag.<<<<<<< HEAD"
|
||||
#~ msgstr "Ihr Browser unterstützt das Video-Tag nicht.>>>>>>> main"
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -55,12 +55,12 @@ msgstr "<0>Require passkey</0> - The recipient must have an account and passkey
|
||||
msgid "Add a document"
|
||||
msgstr "Add a document"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:336
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:305
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:339
|
||||
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:248
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:217
|
||||
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."
|
||||
|
||||
@@ -77,19 +77,19 @@ msgstr "Add another option"
|
||||
msgid "Add another value"
|
||||
msgstr "Add another value"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:653
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:459
|
||||
msgid "Add myself"
|
||||
msgstr "Add myself"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:637
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:450
|
||||
msgid "Add Myself"
|
||||
msgstr "Add Myself"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:623
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:436
|
||||
msgid "Add Placeholder Recipient"
|
||||
msgstr "Add Placeholder Recipient"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:642
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:448
|
||||
msgid "Add Signer"
|
||||
msgstr "Add Signer"
|
||||
|
||||
@@ -105,12 +105,12 @@ msgstr "Add text to the field"
|
||||
msgid "Admin"
|
||||
msgstr "Admin"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:230
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:199
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:238
|
||||
msgid "Advanced Options"
|
||||
msgstr "Advanced Options"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:522
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:510
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:402
|
||||
msgid "Advanced settings"
|
||||
msgstr "Advanced settings"
|
||||
@@ -148,9 +148,9 @@ msgstr "Blue"
|
||||
msgid "Cancel"
|
||||
msgstr "Cancel"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:194
|
||||
msgid "Cannot remove signer"
|
||||
msgstr "Cannot remove signer"
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:164
|
||||
#~ msgid "Cannot remove signer"
|
||||
#~ msgstr "Cannot remove signer"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:17
|
||||
msgid "Cc"
|
||||
@@ -169,7 +169,7 @@ msgstr "CC'd"
|
||||
msgid "Character Limit"
|
||||
msgstr "Character Limit"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:944
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:932
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:788
|
||||
msgid "Checkbox"
|
||||
msgstr "Checkbox"
|
||||
@@ -198,7 +198,7 @@ msgstr "Close"
|
||||
msgid "Configure Direct Recipient"
|
||||
msgstr "Configure Direct Recipient"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:523
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:511
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:403
|
||||
msgid "Configure the {0} field"
|
||||
msgstr "Configure the {0} field"
|
||||
@@ -215,22 +215,22 @@ msgstr "Copied to clipboard"
|
||||
msgid "Custom Text"
|
||||
msgstr "Custom Text"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:840
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:828
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:684
|
||||
msgid "Date"
|
||||
msgstr "Date"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:271
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:240
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:279
|
||||
msgid "Date Format"
|
||||
msgstr "Date Format"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:570
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:393
|
||||
msgid "Direct link receiver"
|
||||
msgstr "Direct link receiver"
|
||||
|
||||
#: packages/ui/components/document/document-global-auth-access-select.tsx:62
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:174
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:166
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:151
|
||||
msgid "Document access"
|
||||
msgstr "Document access"
|
||||
@@ -247,7 +247,7 @@ msgstr "Download"
|
||||
msgid "Drag & drop your PDF here."
|
||||
msgstr "Drag & drop your PDF here."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:970
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:958
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:814
|
||||
msgid "Dropdown"
|
||||
msgstr "Dropdown"
|
||||
@@ -256,12 +256,13 @@ msgstr "Dropdown"
|
||||
msgid "Dropdown options"
|
||||
msgstr "Dropdown options"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:788
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:776
|
||||
#: packages/ui/primitives/document-flow/add-signature.tsx:272
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:491
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:330
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:337
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:632
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:463
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:470
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:296
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:303
|
||||
msgid "Email"
|
||||
msgstr "Email"
|
||||
|
||||
@@ -273,11 +274,6 @@ msgstr "Email Options"
|
||||
msgid "Enable Direct Link Signing"
|
||||
msgstr "Enable Direct Link Signing"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:392
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:362
|
||||
msgid "Enable signing order"
|
||||
msgstr "Enable signing order"
|
||||
|
||||
#: packages/ui/primitives/document-password-dialog.tsx:84
|
||||
msgid "Enter password"
|
||||
msgstr "Enter password"
|
||||
@@ -286,7 +282,7 @@ msgstr "Enter password"
|
||||
msgid "Error"
|
||||
msgstr "Error"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:241
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:210
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:249
|
||||
msgid "External ID"
|
||||
msgstr "External ID"
|
||||
@@ -375,13 +371,12 @@ msgstr "Message <0>(Optional)</0>"
|
||||
msgid "Min"
|
||||
msgstr "Min"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:814
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:802
|
||||
#: packages/ui/primitives/document-flow/add-signature.tsx:298
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:526
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:532
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:364
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:658
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:498
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:504
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:327
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:333
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
@@ -397,12 +392,12 @@ msgstr "Needs to sign"
|
||||
msgid "Needs to view"
|
||||
msgstr "Needs to view"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:625
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:613
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:497
|
||||
msgid "No recipient matching this description was found."
|
||||
msgstr "No recipient matching this description was found."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:641
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:629
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:513
|
||||
msgid "No recipients with this role"
|
||||
msgstr "No recipients with this role"
|
||||
@@ -427,7 +422,7 @@ msgstr "No signature field found"
|
||||
msgid "No value found."
|
||||
msgstr "No value found."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:892
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:880
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:736
|
||||
msgid "Number"
|
||||
msgstr "Number"
|
||||
@@ -462,7 +457,7 @@ msgstr "Pick a number"
|
||||
msgid "Placeholder"
|
||||
msgstr "Placeholder"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:918
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:906
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:762
|
||||
msgid "Radio"
|
||||
msgstr "Radio"
|
||||
@@ -484,7 +479,7 @@ msgid "Receives copy"
|
||||
msgstr "Receives copy"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:39
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:215
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:184
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:169
|
||||
msgid "Recipient action authentication"
|
||||
msgstr "Recipient action authentication"
|
||||
@@ -493,12 +488,12 @@ msgstr "Recipient action authentication"
|
||||
msgid "Red"
|
||||
msgstr "Red"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:329
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:298
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:332
|
||||
msgid "Redirect URL"
|
||||
msgstr "Redirect URL"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:1008
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:996
|
||||
msgid "Remove"
|
||||
msgstr "Remove"
|
||||
|
||||
@@ -560,8 +555,8 @@ msgstr "Share Signature Card"
|
||||
msgid "Share the Link"
|
||||
msgstr "Share the Link"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:671
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:655
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:477
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:468
|
||||
msgid "Show advanced settings"
|
||||
msgstr "Show advanced settings"
|
||||
|
||||
@@ -569,7 +564,7 @@ msgstr "Show advanced settings"
|
||||
msgid "Sign"
|
||||
msgstr "Sign"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:736
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:724
|
||||
#: packages/ui/primitives/document-flow/add-signature.tsx:323
|
||||
#: packages/ui/primitives/document-flow/field-icon.tsx:52
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:580
|
||||
@@ -617,7 +612,7 @@ msgstr "Submit"
|
||||
msgid "Template title"
|
||||
msgstr "Template title"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:866
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:854
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:710
|
||||
msgid "Text"
|
||||
msgstr "Text"
|
||||
@@ -678,7 +673,7 @@ msgstr "The signer's name"
|
||||
msgid "This can be overriden by setting the authentication requirements directly on each recipient in the next step."
|
||||
msgstr "This can be overriden by setting the authentication requirements directly on each recipient in the next step."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:697
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:685
|
||||
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."
|
||||
|
||||
@@ -686,28 +681,28 @@ msgstr "This document has already been sent to this recipient. You can no longer
|
||||
msgid "This document is password protected. Please enter the password to view the document."
|
||||
msgstr "This document is password protected. Please enter the password to view the document."
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:573
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:396
|
||||
msgid "This field cannot be modified or deleted. When you share this template's direct link or add it to your public profile, anyone who accesses it can input their name and email, and fill in the fields assigned to them."
|
||||
msgstr "This field cannot be modified or deleted. When you share this template's direct link or add it to your public profile, anyone who accesses it can input their name and email, and fill in the fields assigned to them."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:195
|
||||
msgid "This signer has already received the document."
|
||||
msgstr "This signer has already received the document."
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:165
|
||||
#~ msgid "This signer has already received the document."
|
||||
#~ msgstr "This signer has already received the document."
|
||||
|
||||
#: 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:305
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:274
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:309
|
||||
msgid "Time Zone"
|
||||
msgstr "Time Zone"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:153
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:145
|
||||
msgid "Title"
|
||||
msgstr "Title"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:983
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:971
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:828
|
||||
msgid "To proceed further, please set at least one value for the {0} field."
|
||||
msgstr "To proceed further, please set at least one value for the {0} field."
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -21,15 +21,15 @@ msgstr "\"{0}\" will appear on the document as it has a timezone of \"{timezone}
|
||||
msgid "\"{documentTitle}\" has been successfully deleted"
|
||||
msgstr "\"{documentTitle}\" has been successfully deleted"
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:78
|
||||
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:76
|
||||
msgid "({0}) has invited you to approve this document"
|
||||
msgstr "({0}) has invited you to approve this document"
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:75
|
||||
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:73
|
||||
msgid "({0}) has invited you to sign this document"
|
||||
msgstr "({0}) has invited you to sign this document"
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:72
|
||||
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:70
|
||||
msgid "({0}) has invited you to view this document"
|
||||
msgstr "({0}) has invited you to view this document"
|
||||
|
||||
@@ -55,11 +55,11 @@ msgstr "{0, plural, one {# Seat} other {# Seats}}"
|
||||
msgid "{0, plural, one {<0>You have <1>1</1> pending team invitation</0>} other {<2>You have <3>#</3> pending team invitations</2>}}"
|
||||
msgstr "{0, plural, one {<0>You have <1>1</1> pending team invitation</0>} other {<2>You have <3>#</3> pending team invitations</2>}}"
|
||||
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx:129
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx:102
|
||||
msgid "{0, plural, one {1 Recipient} other {# Recipients}}"
|
||||
msgstr "{0, plural, one {1 Recipient} other {# Recipients}}"
|
||||
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view.tsx:230
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view.tsx:204
|
||||
msgid "{0, plural, one {Waiting on 1 recipient} other {Waiting on # recipients}}"
|
||||
msgstr "{0, plural, one {Waiting on 1 recipient} other {Waiting on # recipients}}"
|
||||
|
||||
@@ -79,7 +79,7 @@ msgstr "{0} document"
|
||||
msgid "{0} of {1} documents remaining this month."
|
||||
msgstr "{0} of {1} documents remaining this month."
|
||||
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view.tsx:165
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view.tsx:139
|
||||
msgid "{0} Recipient(s)"
|
||||
msgstr "{0} Recipient(s)"
|
||||
|
||||
@@ -215,7 +215,7 @@ msgstr "Active Subscriptions"
|
||||
msgid "Add"
|
||||
msgstr "Add"
|
||||
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:157
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:143
|
||||
#: apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx:87
|
||||
msgid "Add all relevant fields for each recipient."
|
||||
msgstr "Add all relevant fields for each recipient."
|
||||
@@ -236,7 +236,7 @@ msgstr "Add an authenticator to serve as a secondary authentication method when
|
||||
msgid "Add email"
|
||||
msgstr "Add email"
|
||||
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:156
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:142
|
||||
#: apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx:86
|
||||
msgid "Add Fields"
|
||||
msgstr "Add Fields"
|
||||
@@ -246,8 +246,8 @@ msgid "Add more"
|
||||
msgstr "Add more"
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/number-field.tsx:270
|
||||
#~ msgid "Add number"
|
||||
#~ msgstr "Add number"
|
||||
msgid "Add number"
|
||||
msgstr "Add number"
|
||||
|
||||
#: apps/web/src/app/(dashboard)/settings/security/passkeys/create-passkey-dialog.tsx:146
|
||||
#: apps/web/src/app/(dashboard)/settings/security/passkeys/create-passkey-dialog.tsx:154
|
||||
@@ -258,11 +258,11 @@ msgstr "Add passkey"
|
||||
msgid "Add Placeholders"
|
||||
msgstr "Add Placeholders"
|
||||
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:151
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:137
|
||||
msgid "Add Signers"
|
||||
msgstr "Add Signers"
|
||||
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:161
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:147
|
||||
msgid "Add Subject"
|
||||
msgstr "Add Subject"
|
||||
|
||||
@@ -271,14 +271,14 @@ msgid "Add team email"
|
||||
msgstr "Add team email"
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/text-field.tsx:256
|
||||
#~ msgid "Add text"
|
||||
#~ msgstr "Add text"
|
||||
msgid "Add text"
|
||||
msgstr "Add text"
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/text-field.tsx:272
|
||||
#~ msgid "Add Text"
|
||||
#~ msgstr "Add Text"
|
||||
msgid "Add Text"
|
||||
msgstr "Add Text"
|
||||
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:152
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:138
|
||||
msgid "Add the people who will sign the document."
|
||||
msgstr "Add the people who will sign the document."
|
||||
|
||||
@@ -286,7 +286,7 @@ msgstr "Add the people who will sign the document."
|
||||
msgid "Add the recipients to create the document with"
|
||||
msgstr "Add the recipients to create the document with"
|
||||
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:162
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:148
|
||||
msgid "Add the subject and message you wish to send to signers."
|
||||
msgstr "Add the subject and message you wish to send to signers."
|
||||
|
||||
@@ -365,13 +365,13 @@ msgstr "An email requesting the transfer of this team has been sent."
|
||||
msgid "An error occurred"
|
||||
msgstr "An error occurred"
|
||||
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:248
|
||||
#: apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx:197
|
||||
#: apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx:231
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:226
|
||||
#: apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx:176
|
||||
#: apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx:210
|
||||
msgid "An error occurred while adding signers."
|
||||
msgstr "An error occurred while adding signers."
|
||||
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:278
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:256
|
||||
msgid "An error occurred while adding the fields."
|
||||
msgstr "An error occurred while adding the fields."
|
||||
|
||||
@@ -429,7 +429,7 @@ msgstr "An error occurred while removing the signature."
|
||||
msgid "An error occurred while removing the text."
|
||||
msgstr "An error occurred while removing the text."
|
||||
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:309
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:287
|
||||
msgid "An error occurred while sending the document."
|
||||
msgstr "An error occurred while sending the document."
|
||||
|
||||
@@ -453,8 +453,8 @@ msgstr "An error occurred while signing the document."
|
||||
msgid "An error occurred while trying to create a checkout session."
|
||||
msgstr "An error occurred while trying to create a checkout session."
|
||||
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:214
|
||||
#: apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx:166
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:199
|
||||
#: apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx:153
|
||||
msgid "An error occurred while updating the document settings."
|
||||
msgstr "An error occurred while updating the document settings."
|
||||
|
||||
@@ -534,7 +534,7 @@ msgstr "App Version"
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-button.tsx:89
|
||||
#: apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx:120
|
||||
#: apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx:144
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:125
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:122
|
||||
msgid "Approve"
|
||||
msgstr "Approve"
|
||||
|
||||
@@ -660,10 +660,10 @@ msgstr "By enabling 2FA, you will be required to enter a code from your authenti
|
||||
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-account.tsx:74
|
||||
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-passkey.tsx:164
|
||||
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-passkey.tsx:189
|
||||
#: apps/web/src/app/(signing)/sign/[token]/form.tsx:150
|
||||
#: apps/web/src/app/(signing)/sign/[token]/form.tsx:143
|
||||
#: apps/web/src/app/(signing)/sign/[token]/name-field.tsx:215
|
||||
#: apps/web/src/app/(signing)/sign/[token]/number-field.tsx:327
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:113
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:110
|
||||
#: apps/web/src/app/(signing)/sign/[token]/signature-field.tsx:250
|
||||
#: apps/web/src/app/(signing)/sign/[token]/text-field.tsx:333
|
||||
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/team-transfer-status.tsx:121
|
||||
@@ -747,7 +747,7 @@ msgid "Click to copy signing link for sending to recipient"
|
||||
msgstr "Click to copy signing link for sending to recipient"
|
||||
|
||||
#: apps/web/src/app/(recipient)/d/[token]/sign-direct-template.tsx:175
|
||||
#: apps/web/src/app/(signing)/sign/[token]/form.tsx:114
|
||||
#: apps/web/src/app/(signing)/sign/[token]/form.tsx:107
|
||||
#: apps/web/src/app/embed/direct/[[...url]]/client.tsx:435
|
||||
#: apps/web/src/app/embed/sign/[[...url]]/client.tsx:314
|
||||
msgid "Click to insert field"
|
||||
@@ -765,22 +765,22 @@ msgstr "Click to insert field"
|
||||
msgid "Close"
|
||||
msgstr "Close"
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:61
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:58
|
||||
#: apps/web/src/app/embed/direct/[[...url]]/client.tsx:425
|
||||
#: apps/web/src/app/embed/sign/[[...url]]/client.tsx:304
|
||||
#: apps/web/src/components/forms/v2/signup.tsx:522
|
||||
msgid "Complete"
|
||||
msgstr "Complete"
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:70
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:67
|
||||
msgid "Complete Approval"
|
||||
msgstr "Complete Approval"
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:69
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:66
|
||||
msgid "Complete Signing"
|
||||
msgstr "Complete Signing"
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:68
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:65
|
||||
msgid "Complete Viewing"
|
||||
msgstr "Complete Viewing"
|
||||
|
||||
@@ -797,7 +797,7 @@ msgstr "Completed documents"
|
||||
msgid "Completed Documents"
|
||||
msgstr "Completed Documents"
|
||||
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:147
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:133
|
||||
msgid "Configure general settings for the document."
|
||||
msgstr "Configure general settings for the document."
|
||||
|
||||
@@ -1215,7 +1215,7 @@ msgid "Document created"
|
||||
msgstr "Document created"
|
||||
|
||||
#: apps/web/src/app/(dashboard)/admin/documents/[id]/super-delete-document-dialog.tsx:51
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view.tsx:173
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view.tsx:147
|
||||
#: apps/web/src/app/(dashboard)/documents/delete-document-dialog.tsx:59
|
||||
msgid "Document deleted"
|
||||
msgstr "Document deleted"
|
||||
@@ -1228,7 +1228,7 @@ msgstr "Document draft"
|
||||
msgid "Document Duplicated"
|
||||
msgstr "Document Duplicated"
|
||||
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view.tsx:184
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view.tsx:158
|
||||
#: apps/web/src/components/document/document-history-sheet.tsx:104
|
||||
msgid "Document history"
|
||||
msgstr "Document history"
|
||||
@@ -1269,7 +1269,7 @@ msgstr "Document re-sent"
|
||||
msgid "Document resealed"
|
||||
msgstr "Document resealed"
|
||||
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:298
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:276
|
||||
msgid "Document sent"
|
||||
msgstr "Document sent"
|
||||
|
||||
@@ -1307,11 +1307,11 @@ msgstr "Document will be permanently deleted"
|
||||
|
||||
#: apps/web/src/app/(dashboard)/admin/nav.tsx:65
|
||||
#: apps/web/src/app/(dashboard)/admin/users/data-table-users.tsx:92
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view.tsx:139
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx:109
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view.tsx:113
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx:82
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/loading.tsx:16
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/sent/page.tsx:15
|
||||
#: apps/web/src/app/(dashboard)/documents/documents-page-view.tsx:114
|
||||
#: apps/web/src/app/(dashboard)/documents/documents-page-view.tsx:110
|
||||
#: apps/web/src/app/(profile)/p/[url]/page.tsx:166
|
||||
#: apps/web/src/app/not-found.tsx:21
|
||||
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:205
|
||||
@@ -1486,16 +1486,16 @@ msgstr "Enter your text here"
|
||||
|
||||
#: apps/web/src/app/(dashboard)/admin/documents/[id]/admin-actions.tsx:41
|
||||
#: apps/web/src/app/(dashboard)/admin/users/[id]/page.tsx:78
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:213
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:247
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:277
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:308
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:198
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:225
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:255
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:286
|
||||
#: apps/web/src/app/(dashboard)/documents/move-document-dialog.tsx:57
|
||||
#: apps/web/src/app/(dashboard)/documents/upload-document.tsx:106
|
||||
#: apps/web/src/app/(dashboard)/documents/upload-document.tsx:112
|
||||
#: apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx:165
|
||||
#: apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx:196
|
||||
#: apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx:230
|
||||
#: apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx:152
|
||||
#: apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx:175
|
||||
#: apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx:209
|
||||
#: apps/web/src/app/(dashboard)/templates/duplicate-template-dialog.tsx:51
|
||||
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:56
|
||||
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:152
|
||||
@@ -1592,7 +1592,7 @@ msgstr "Forgot your password?"
|
||||
msgid "Full Name"
|
||||
msgstr "Full Name"
|
||||
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:146
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:132
|
||||
#: apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx:76
|
||||
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:60
|
||||
#: apps/web/src/components/(teams)/settings/layout/desktop-nav.tsx:43
|
||||
@@ -1773,10 +1773,6 @@ msgstr "It seems that there is no token provided, if you are trying to verify yo
|
||||
msgid "It seems that there is no token provided. Please check your email and try again."
|
||||
msgstr "It seems that there is no token provided. Please check your email and try again."
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/waiting/page.tsx:74
|
||||
msgid "It's currently not your turn to sign. You will receive an email with instructions once it's your turn to sign the document."
|
||||
msgstr "It's currently not your turn to sign. You will receive an email with instructions once it's your turn to sign the document."
|
||||
|
||||
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:286
|
||||
msgid "Language"
|
||||
msgstr "Language"
|
||||
@@ -1932,7 +1928,7 @@ msgstr "Manage your passkeys."
|
||||
msgid "Manage your site settings here"
|
||||
msgstr "Manage your site settings here"
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:123
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:120
|
||||
msgid "Mark as Viewed"
|
||||
msgstr "Mark as Viewed"
|
||||
|
||||
@@ -2038,7 +2034,7 @@ msgstr "New Template"
|
||||
msgid "Next"
|
||||
msgstr "Next"
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:61
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:58
|
||||
msgid "Next field"
|
||||
msgstr "Next field"
|
||||
|
||||
@@ -2096,10 +2092,6 @@ msgstr "Not supported"
|
||||
msgid "Nothing to do"
|
||||
msgstr "Nothing to do"
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/number-field.tsx:270
|
||||
msgid "Number"
|
||||
msgstr "Number"
|
||||
|
||||
#: apps/web/src/components/(dashboard)/settings/webhooks/create-webhook-dialog.tsx:128
|
||||
msgid "On this page, you can create a new webhook."
|
||||
msgstr "On this page, you can create a new webhook."
|
||||
@@ -2273,10 +2265,6 @@ msgstr "Pick any of the following agreements below and start signing to get star
|
||||
msgid "Please check the CSV file and make sure it is according to our format"
|
||||
msgstr "Please check the CSV file and make sure it is according to our format"
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/waiting/page.tsx:81
|
||||
msgid "Please check your email for updates."
|
||||
msgstr "Please check your email for updates."
|
||||
|
||||
#: apps/web/src/app/(unauthenticated)/reset-password/[token]/page.tsx:34
|
||||
msgid "Please choose your new password"
|
||||
msgstr "Please choose your new password"
|
||||
@@ -2289,7 +2277,7 @@ msgstr "Please contact support if you would like to revert this action."
|
||||
msgid "Please enter a meaningful name for your token. This will help you identify it later."
|
||||
msgstr "Please enter a meaningful name for your token. This will help you identify it later."
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/form.tsx:134
|
||||
#: apps/web/src/app/(signing)/sign/[token]/form.tsx:127
|
||||
msgid "Please mark as viewed to complete"
|
||||
msgstr "Please mark as viewed to complete"
|
||||
|
||||
@@ -2620,7 +2608,7 @@ msgstr "Security"
|
||||
msgid "Security activity"
|
||||
msgstr "Security activity"
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/dropdown-field.tsx:194
|
||||
#: apps/web/src/app/(signing)/sign/[token]/dropdown-field.tsx:192
|
||||
msgid "Select"
|
||||
msgstr "Select"
|
||||
|
||||
@@ -2724,7 +2712,7 @@ msgstr "Show templates in your team public profile for your audience to sign and
|
||||
#: apps/web/src/app/(profile)/p/[url]/page.tsx:192
|
||||
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-2fa.tsx:182
|
||||
#: apps/web/src/app/(signing)/sign/[token]/name-field.tsx:224
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:124
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:121
|
||||
#: apps/web/src/app/(signing)/sign/[token]/signature-field.tsx:259
|
||||
#: apps/web/src/components/ui/user-profile-skeleton.tsx:75
|
||||
#: apps/web/src/components/ui/user-profile-timur.tsx:81
|
||||
@@ -3124,7 +3112,7 @@ msgstr "Template has been updated."
|
||||
msgid "Template moved"
|
||||
msgstr "Template moved"
|
||||
|
||||
#: apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx:219
|
||||
#: apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx:198
|
||||
msgid "Template saved"
|
||||
msgstr "Template saved"
|
||||
|
||||
@@ -3140,11 +3128,6 @@ msgstr "Templates"
|
||||
msgid "Templates allow you to quickly generate documents with pre-filled recipients and fields."
|
||||
msgstr "Templates allow you to quickly generate documents with pre-filled recipients and fields."
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/text-field.tsx:256
|
||||
#: apps/web/src/app/(signing)/sign/[token]/text-field.tsx:272
|
||||
msgid "Text"
|
||||
msgstr "Text"
|
||||
|
||||
#: apps/web/src/app/(dashboard)/admin/site-settings/banner-form.tsx:166
|
||||
msgid "Text Color"
|
||||
msgstr "Text Color"
|
||||
@@ -3322,11 +3305,11 @@ msgstr "This document has been cancelled by the owner and is no longer available
|
||||
msgid "This document has been cancelled by the owner."
|
||||
msgstr "This document has been cancelled by the owner."
|
||||
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view.tsx:219
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view.tsx:193
|
||||
msgid "This document has been signed by all recipients"
|
||||
msgstr "This document has been signed by all recipients"
|
||||
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view.tsx:222
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view.tsx:196
|
||||
msgid "This document is currently a draft and has not been sent"
|
||||
msgstr "This document is currently a draft and has not been sent"
|
||||
|
||||
@@ -3851,10 +3834,6 @@ msgstr "Waiting"
|
||||
msgid "Waiting for others to sign"
|
||||
msgstr "Waiting for others to sign"
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/waiting/page.tsx:70
|
||||
msgid "Waiting for Your Turn"
|
||||
msgstr "Waiting for Your Turn"
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/no-longer-available.tsx:61
|
||||
msgid "Want to send slick signing links like this one? <0>Check out Documenso.</0>"
|
||||
msgstr "Want to send slick signing links like this one? <0>Check out Documenso.</0>"
|
||||
@@ -4099,10 +4078,6 @@ msgstr "Weekly"
|
||||
msgid "Welcome back, we are lucky to have you."
|
||||
msgstr "Welcome back, we are lucky to have you."
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/waiting/page.tsx:88
|
||||
msgid "Were you trying to edit this document instead?"
|
||||
msgstr "Were you trying to edit this document instead?"
|
||||
|
||||
#: apps/web/src/app/(dashboard)/settings/security/passkeys/create-passkey-dialog.tsx:189
|
||||
msgid "When you click continue, you will be prompted to add the first available authenticator on your system."
|
||||
msgstr "When you click continue, you will be prompted to add the first available authenticator on your system."
|
||||
@@ -4131,15 +4106,15 @@ msgstr "Yearly"
|
||||
msgid "You"
|
||||
msgstr "You"
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:93
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:90
|
||||
msgid "You are about to complete approving \"{truncatedTitle}\".<0/> Are you sure?"
|
||||
msgstr "You are about to complete approving \"{truncatedTitle}\".<0/> Are you sure?"
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:85
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:82
|
||||
msgid "You are about to complete signing \"{truncatedTitle}\".<0/> Are you sure?"
|
||||
msgstr "You are about to complete signing \"{truncatedTitle}\".<0/> Are you sure?"
|
||||
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:77
|
||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:74
|
||||
msgid "You are about to complete viewing \"{truncatedTitle}\".<0/> Are you sure?"
|
||||
msgstr "You are about to complete viewing \"{truncatedTitle}\".<0/> Are you sure?"
|
||||
|
||||
@@ -4377,7 +4352,7 @@ msgstr "Your document has been created from the template successfully."
|
||||
msgid "Your document has been re-sent successfully."
|
||||
msgstr "Your document has been re-sent successfully."
|
||||
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:299
|
||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx:277
|
||||
msgid "Your document has been sent successfully."
|
||||
msgstr "Your document has been sent successfully."
|
||||
|
||||
@@ -4468,7 +4443,7 @@ msgstr "Your template has been successfully deleted."
|
||||
msgid "Your template will be duplicated."
|
||||
msgstr "Your template will be duplicated."
|
||||
|
||||
#: apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx:220
|
||||
#: apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx:199
|
||||
msgid "Your templates has been saved successfully."
|
||||
msgstr "Your templates has been saved successfully."
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ export const ZDocumentAuditLogTypeSchema = z.enum([
|
||||
'DOCUMENT_DELETED', // When the document is soft deleted.
|
||||
'DOCUMENT_FIELD_INSERTED', // When a field is inserted (signed/approved/etc) by a recipient.
|
||||
'DOCUMENT_FIELD_UNINSERTED', // When a field is uninserted by a recipient.
|
||||
'DOCUMENT_VISIBILITY_UPDATED', // When the document visibility scope is updated
|
||||
'DOCUMENT_GLOBAL_AUTH_ACCESS_UPDATED', // When the global access authentication is updated.
|
||||
'DOCUMENT_GLOBAL_AUTH_ACTION_UPDATED', // When the global action authentication is updated.
|
||||
'DOCUMENT_META_UPDATED', // When the document meta data is updated.
|
||||
@@ -312,11 +311,6 @@ export const ZDocumentAuditLogEventDocumentFieldUninsertedSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
export const ZDocumentAuditLogEventDocumentVisibilitySchema = z.object({
|
||||
type: z.literal(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_VISIBILITY_UPDATED),
|
||||
data: ZGenericFromToSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* Event: Document global authentication access updated.
|
||||
*/
|
||||
@@ -481,7 +475,6 @@ export const ZDocumentAuditLogSchema = ZDocumentAuditLogBaseSchema.and(
|
||||
ZDocumentAuditLogEventDocumentMovedToTeamSchema,
|
||||
ZDocumentAuditLogEventDocumentFieldInsertedSchema,
|
||||
ZDocumentAuditLogEventDocumentFieldUninsertedSchema,
|
||||
ZDocumentAuditLogEventDocumentVisibilitySchema,
|
||||
ZDocumentAuditLogEventDocumentGlobalAuthAccessUpdatedSchema,
|
||||
ZDocumentAuditLogEventDocumentGlobalAuthActionUpdatedSchema,
|
||||
ZDocumentAuditLogEventDocumentMetaUpdatedSchema,
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { DocumentVisibility as DocumentVisibilityEnum } from '@documenso/prisma/client';
|
||||
|
||||
export const ZDocumentVisibilitySchema = z.nativeEnum(DocumentVisibilityEnum);
|
||||
export const DocumentVisibility = ZDocumentVisibilitySchema.enum;
|
||||
export type TDocumentVisibility = z.infer<typeof ZDocumentVisibilitySchema>;
|
||||
@@ -313,10 +313,6 @@ export const formatDocumentAuditLogAction = (auditLog: TDocumentAuditLog, userId
|
||||
anonymous: 'Field unsigned',
|
||||
identified: 'unsigned a field',
|
||||
}))
|
||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_VISIBILITY_UPDATED }, () => ({
|
||||
anonymous: 'Document visibility updated',
|
||||
identified: 'updated the document visibility',
|
||||
}))
|
||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_GLOBAL_AUTH_ACCESS_UPDATED }, () => ({
|
||||
anonymous: 'Document access auth updated',
|
||||
identified: 'updated the document access auth requirements',
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Document" ADD COLUMN "visibility" TEXT;
|
||||
@@ -1,5 +0,0 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "DocumentSigningOrder" AS ENUM ('PARALLEL', 'SEQUENTIAL');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "DocumentMeta" ADD COLUMN "signingOrder" "DocumentSigningOrder" NOT NULL DEFAULT 'PARALLEL';
|
||||
@@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Recipient" ADD COLUMN "signingOrder" INTEGER;
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- The `visibility` column on the `Document` table would be dropped and recreated. This will lead to data loss if there is data in the column.
|
||||
|
||||
*/
|
||||
-- CreateEnum
|
||||
CREATE TYPE "DocumentVisibility" AS ENUM ('EVERYONE', 'MANAGER_AND_ABOVE', 'ADMIN');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Document" DROP COLUMN "visibility",
|
||||
ADD COLUMN "visibility" "DocumentVisibility" NOT NULL DEFAULT 'EVERYONE';
|
||||
@@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "TemplateMeta" ADD COLUMN "signingOrder" "DocumentSigningOrder" DEFAULT 'PARALLEL';
|
||||
@@ -282,12 +282,6 @@ enum DocumentSource {
|
||||
TEMPLATE_DIRECT_LINK
|
||||
}
|
||||
|
||||
enum DocumentVisibility {
|
||||
EVERYONE
|
||||
MANAGER_AND_ABOVE
|
||||
ADMIN
|
||||
}
|
||||
|
||||
model Document {
|
||||
id Int @id @default(autoincrement())
|
||||
externalId String?
|
||||
@@ -295,7 +289,6 @@ model Document {
|
||||
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
authOptions Json?
|
||||
formValues Json?
|
||||
visibility DocumentVisibility @default(EVERYONE)
|
||||
title String
|
||||
status DocumentStatus @default(DRAFT)
|
||||
Recipient Recipient[]
|
||||
@@ -344,11 +337,6 @@ enum DocumentDataType {
|
||||
BYTES_64
|
||||
}
|
||||
|
||||
enum DocumentSigningOrder {
|
||||
PARALLEL
|
||||
SEQUENTIAL
|
||||
}
|
||||
|
||||
model DocumentData {
|
||||
id String @id @default(cuid())
|
||||
type DocumentDataType
|
||||
@@ -359,16 +347,15 @@ model DocumentData {
|
||||
}
|
||||
|
||||
model DocumentMeta {
|
||||
id String @id @default(cuid())
|
||||
subject String?
|
||||
message String?
|
||||
timezone String? @default("Etc/UTC") @db.Text
|
||||
password String?
|
||||
dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text
|
||||
documentId Int @unique
|
||||
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||||
redirectUrl String?
|
||||
signingOrder DocumentSigningOrder @default(PARALLEL)
|
||||
id String @id @default(cuid())
|
||||
subject String?
|
||||
message String?
|
||||
timezone String? @default("Etc/UTC") @db.Text
|
||||
password String?
|
||||
dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text
|
||||
documentId Int @unique
|
||||
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||||
redirectUrl String?
|
||||
}
|
||||
|
||||
enum ReadStatus {
|
||||
@@ -404,7 +391,6 @@ model Recipient {
|
||||
expired DateTime?
|
||||
signedAt DateTime?
|
||||
authOptions Json?
|
||||
signingOrder Int?
|
||||
role RecipientRole @default(SIGNER)
|
||||
readStatus ReadStatus @default(NOT_OPENED)
|
||||
signingStatus SigningStatus @default(NOT_SIGNED)
|
||||
@@ -601,16 +587,15 @@ enum TemplateType {
|
||||
}
|
||||
|
||||
model TemplateMeta {
|
||||
id String @id @default(cuid())
|
||||
subject String?
|
||||
message String?
|
||||
timezone String? @default("Etc/UTC") @db.Text
|
||||
password String?
|
||||
dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text
|
||||
signingOrder DocumentSigningOrder? @default(PARALLEL)
|
||||
templateId Int @unique
|
||||
template Template @relation(fields: [templateId], references: [id], onDelete: Cascade)
|
||||
redirectUrl String?
|
||||
id String @id @default(cuid())
|
||||
subject String?
|
||||
message String?
|
||||
timezone String? @default("Etc/UTC") @db.Text
|
||||
password String?
|
||||
dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text
|
||||
templateId Int @unique
|
||||
template Template @relation(fields: [templateId], references: [id], onDelete: Cascade)
|
||||
redirectUrl String?
|
||||
}
|
||||
|
||||
model Template {
|
||||
|
||||
@@ -102,44 +102,6 @@ export const unseedTeam = async (teamUrl: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
type SeedTeamMemberOptions = {
|
||||
teamId: number;
|
||||
role?: TeamMemberRole;
|
||||
};
|
||||
|
||||
export const seedTeamMember = async ({
|
||||
teamId,
|
||||
role = TeamMemberRole.ADMIN,
|
||||
}: SeedTeamMemberOptions) => {
|
||||
const user = await seedUser();
|
||||
|
||||
await prisma.teamMember.create({
|
||||
data: {
|
||||
teamId,
|
||||
role,
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
type UnseedTeamMemberOptions = {
|
||||
teamId: number;
|
||||
userId: number;
|
||||
};
|
||||
|
||||
export const unseedTeamMember = async ({ teamId, userId }: UnseedTeamMemberOptions) => {
|
||||
await prisma.teamMember.delete({
|
||||
where: {
|
||||
userId_teamId: {
|
||||
userId,
|
||||
teamId,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const seedTeamTransfer = async (options: { newOwnerUserId: number; teamId: number }) => {
|
||||
return await prisma.teamTransferVerification.create({
|
||||
data: {
|
||||
|
||||
@@ -58,7 +58,7 @@ module.exports = {
|
||||
},
|
||||
widget: {
|
||||
DEFAULT: 'hsl(var(--widget))',
|
||||
foreground: 'hsl(var(--widget-foreground))',
|
||||
// foreground: 'hsl(var(--widget-foreground))',
|
||||
},
|
||||
documenso: {
|
||||
DEFAULT: '#A2E771',
|
||||
|
||||
@@ -40,7 +40,6 @@ import {
|
||||
ZSendDocumentMutationSchema,
|
||||
ZSetPasswordForDocumentMutationSchema,
|
||||
ZSetSettingsForDocumentMutationSchema,
|
||||
ZSetSigningOrderForDocumentMutationSchema,
|
||||
ZSetTitleForDocumentMutationSchema,
|
||||
} from './schema';
|
||||
|
||||
@@ -309,29 +308,6 @@ export const documentRouter = router({
|
||||
}
|
||||
}),
|
||||
|
||||
setSigningOrderForDocument: authenticatedProcedure
|
||||
.input(ZSetSigningOrderForDocumentMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { documentId, signingOrder } = input;
|
||||
|
||||
return await upsertDocumentMeta({
|
||||
documentId,
|
||||
signingOrder,
|
||||
userId: ctx.user.id,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message:
|
||||
'We were unable to update the settings for this document. Please try again later.',
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
sendDocument: authenticatedProcedure
|
||||
.input(ZSendDocumentMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
|
||||
import { DocumentSigningOrder, FieldType, RecipientRole } from '@documenso/prisma/client';
|
||||
import { FieldType, RecipientRole } from '@documenso/prisma/client';
|
||||
|
||||
export const ZFindDocumentAuditLogsQuerySchema = ZBaseTableSearchParamsSchema.extend({
|
||||
documentId: z.number().min(1),
|
||||
@@ -56,7 +56,6 @@ export const ZSetSettingsForDocumentMutationSchema = z.object({
|
||||
data: z.object({
|
||||
title: z.string().min(1).optional(),
|
||||
externalId: z.string().nullish(),
|
||||
visibility: z.string().optional(),
|
||||
globalAccessAuth: ZDocumentAccessAuthTypesSchema.nullable().optional(),
|
||||
globalActionAuth: ZDocumentActionAuthTypesSchema.nullable().optional(),
|
||||
}),
|
||||
@@ -149,15 +148,6 @@ export type TSetPasswordForDocumentMutationSchema = z.infer<
|
||||
typeof ZSetPasswordForDocumentMutationSchema
|
||||
>;
|
||||
|
||||
export const ZSetSigningOrderForDocumentMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder),
|
||||
});
|
||||
|
||||
export type TSetSigningOrderForDocumentMutationSchema = z.infer<
|
||||
typeof ZSetSigningOrderForDocumentMutationSchema
|
||||
>;
|
||||
|
||||
export const ZResendDocumentMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
recipients: z.array(z.number()).min(1),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
import { completeDocumentWithToken } from '@documenso/lib/server-only/document/complete-document-with-token';
|
||||
import { deleteRecipient } from '@documenso/lib/server-only/recipient/delete-recipient';
|
||||
import { deleteRecipientFromTemplate } from '@documenso/lib/server-only/recipient/delete-recipient-from-template';
|
||||
import { setRecipientsForDocument } from '@documenso/lib/server-only/recipient/set-recipients-for-document';
|
||||
import { setRecipientsForTemplate } from '@documenso/lib/server-only/recipient/set-recipients-for-template';
|
||||
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
@@ -10,6 +12,8 @@ import {
|
||||
ZAddSignersMutationSchema,
|
||||
ZAddTemplateSignersMutationSchema,
|
||||
ZCompleteDocumentWithTokenMutationSchema,
|
||||
ZRemoveSignerMutationSchema,
|
||||
ZRemoveTemplateSignerMutationSchema,
|
||||
} from './schema';
|
||||
|
||||
export const recipientRouter = router({
|
||||
@@ -28,7 +32,6 @@ export const recipientRouter = router({
|
||||
email: signer.email,
|
||||
name: signer.name,
|
||||
role: signer.role,
|
||||
signingOrder: signer.signingOrder,
|
||||
actionAuth: signer.actionAuth,
|
||||
})),
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
@@ -58,7 +61,6 @@ export const recipientRouter = router({
|
||||
email: signer.email,
|
||||
name: signer.name,
|
||||
role: signer.role,
|
||||
signingOrder: signer.signingOrder,
|
||||
actionAuth: signer.actionAuth,
|
||||
})),
|
||||
});
|
||||
@@ -72,6 +74,51 @@ export const recipientRouter = router({
|
||||
}
|
||||
}),
|
||||
|
||||
removeTemplateSigner: authenticatedProcedure
|
||||
.input(ZRemoveTemplateSignerMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { templateId, recipientId, teamId } = input;
|
||||
const userId = ctx.user.id;
|
||||
|
||||
return await deleteRecipientFromTemplate({
|
||||
userId,
|
||||
templateId,
|
||||
teamId,
|
||||
recipientId,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to remove the recipient. Please try again later.',
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
removeSigner: authenticatedProcedure
|
||||
.input(ZRemoveSignerMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { documentId, teamId, recipientId } = input;
|
||||
|
||||
return await deleteRecipient({
|
||||
userId: ctx.user.id,
|
||||
documentId,
|
||||
teamId,
|
||||
recipientId,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to set this field. Please try again later.',
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
completeDocumentWithToken: procedure
|
||||
.input(ZCompleteDocumentWithTokenMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
|
||||
@@ -16,7 +16,6 @@ export const ZAddSignersMutationSchema = z
|
||||
email: z.string().email().min(1),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
actionAuth: ZRecipientActionAuthTypesSchema.optional().nullable(),
|
||||
}),
|
||||
),
|
||||
@@ -43,7 +42,6 @@ export const ZAddTemplateSignersMutationSchema = z
|
||||
email: z.string().email().min(1),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
actionAuth: ZRecipientActionAuthTypesSchema.optional().nullable(),
|
||||
}),
|
||||
),
|
||||
@@ -60,6 +58,14 @@ export const ZAddTemplateSignersMutationSchema = z
|
||||
|
||||
export type TAddTemplateSignersMutationSchema = z.infer<typeof ZAddTemplateSignersMutationSchema>;
|
||||
|
||||
export const ZRemoveSignerMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
teamId: z.number().optional(),
|
||||
recipientId: z.number(),
|
||||
});
|
||||
|
||||
export type TRemoveSignerMutationSchema = z.infer<typeof ZRemoveSignerMutationSchema>;
|
||||
|
||||
export const ZCompleteDocumentWithTokenMutationSchema = z.object({
|
||||
token: z.string(),
|
||||
documentId: z.number(),
|
||||
@@ -69,3 +75,13 @@ export const ZCompleteDocumentWithTokenMutationSchema = z.object({
|
||||
export type TCompleteDocumentWithTokenMutationSchema = z.infer<
|
||||
typeof ZCompleteDocumentWithTokenMutationSchema
|
||||
>;
|
||||
|
||||
export const ZRemoveTemplateSignerMutationSchema = z.object({
|
||||
templateId: z.number(),
|
||||
teamId: z.number().optional(),
|
||||
recipientId: z.number(),
|
||||
});
|
||||
|
||||
export type TRemoveTemplateSignerMutationSchema = z.infer<
|
||||
typeof ZRemoveTemplateSignerMutationSchema
|
||||
>;
|
||||
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
ZFindTemplatesQuerySchema,
|
||||
ZGetTemplateWithDetailsByIdQuerySchema,
|
||||
ZMoveTemplatesToTeamSchema,
|
||||
ZSetSigningOrderForTemplateMutationSchema,
|
||||
ZToggleTemplateDirectLinkMutationSchema,
|
||||
ZUpdateTemplateSettingsMutationSchema,
|
||||
} from './schema';
|
||||
@@ -228,31 +227,6 @@ export const templateRouter = router({
|
||||
}
|
||||
}),
|
||||
|
||||
setSigningOrderForTemplate: authenticatedProcedure
|
||||
.input(ZSetSigningOrderForTemplateMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { templateId, teamId, signingOrder } = input;
|
||||
|
||||
return await updateTemplateSettings({
|
||||
templateId,
|
||||
teamId,
|
||||
data: {},
|
||||
meta: { signingOrder },
|
||||
userId: ctx.user.id,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message:
|
||||
'We were unable to update the settings for this document. Please try again later.',
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
findTemplates: authenticatedProcedure
|
||||
.input(ZFindTemplatesQuerySchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
|
||||
import { DocumentSigningOrder, TemplateType } from '@documenso/prisma/client';
|
||||
import { TemplateType } from '@documenso/prisma/client';
|
||||
|
||||
import { ZSignFieldWithTokenMutationSchema } from '../field-router/schema';
|
||||
|
||||
@@ -105,12 +105,6 @@ export const ZUpdateTemplateSettingsMutationSchema = z.object({
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const ZSetSigningOrderForTemplateMutationSchema = z.object({
|
||||
templateId: z.number(),
|
||||
teamId: z.number().optional(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder),
|
||||
});
|
||||
|
||||
export const ZFindTemplatesQuerySchema = ZBaseTableSearchParamsSchema.extend({
|
||||
teamId: z.number().optional(),
|
||||
type: z.nativeEnum(TemplateType).optional(),
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
|
||||
import type { SelectProps } from '@radix-ui/react-select';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
|
||||
import { DOCUMENT_VISIBILITY } from '@documenso/lib/constants/document-visibility';
|
||||
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
|
||||
export type DocumentVisibilitySelectType = SelectProps & {
|
||||
currentMemberRole?: string;
|
||||
};
|
||||
|
||||
export const DocumentVisibilitySelect = forwardRef<HTMLButtonElement, DocumentVisibilitySelectType>(
|
||||
({ currentMemberRole, ...props }, ref) => {
|
||||
const canUpdateVisibility = currentMemberRole === 'ADMIN' || currentMemberRole === 'MANAGER';
|
||||
|
||||
return (
|
||||
<Select {...props} disabled={!canUpdateVisibility}>
|
||||
<SelectTrigger ref={ref} className="bg-background text-muted-foreground">
|
||||
<SelectValue data-testid="documentVisibilitySelectValue" placeholder="Everyone" />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent position="popper">
|
||||
<SelectItem value={DocumentVisibility.EVERYONE}>
|
||||
{DOCUMENT_VISIBILITY.EVERYONE.value}
|
||||
</SelectItem>
|
||||
|
||||
{(currentMemberRole === 'ADMIN' || currentMemberRole === 'MANAGER') && (
|
||||
<SelectItem value={DocumentVisibility.MANAGER_AND_ABOVE}>
|
||||
{DOCUMENT_VISIBILITY.MANAGER_AND_ABOVE.value}
|
||||
</SelectItem>
|
||||
)}
|
||||
|
||||
{currentMemberRole === 'ADMIN' && (
|
||||
<SelectItem value={DocumentVisibility.ADMIN}>
|
||||
{DOCUMENT_VISIBILITY.ADMIN.value}
|
||||
</SelectItem>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
DocumentVisibilitySelect.displayName = 'DocumentVisibilitySelect';
|
||||
|
||||
export const DocumentVisibilityTooltip = () => {
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-foreground max-w-md space-y-2 p-4">
|
||||
<h2>
|
||||
<strong>Document visibility</strong>
|
||||
</h2>
|
||||
|
||||
<p>The visibility of the document to the recipient.</p>
|
||||
|
||||
<ul className="ml-3.5 list-outside list-disc space-y-0.5 py-2">
|
||||
<li>
|
||||
<strong>Everyone</strong> - Everyone can access and view the document
|
||||
</li>
|
||||
<li>
|
||||
<strong>Managers and above</strong> - Only managers and above can access and view the
|
||||
document
|
||||
</li>
|
||||
<li>
|
||||
<strong>Admins only</strong> - Only admins can access and view the document
|
||||
</li>
|
||||
</ul>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -18,7 +18,7 @@ export type RecipientRoleSelectProps = SelectProps & {
|
||||
export const RecipientRoleSelect = forwardRef<HTMLButtonElement, RecipientRoleSelectProps>(
|
||||
({ hideCCRecipients, ...props }, ref) => (
|
||||
<Select {...props}>
|
||||
<SelectTrigger ref={ref} className="bg-background w-[50px] p-2">
|
||||
<SelectTrigger ref={ref} className="bg-background w-[60px]">
|
||||
{/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */}
|
||||
{ROLE_ICONS[props.value as RecipientRole]}
|
||||
</SelectTrigger>
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@documenso/lib": "*",
|
||||
"@hello-pangea/dnd": "^16.6.0",
|
||||
"@hookform/resolvers": "^3.3.0",
|
||||
"@lingui/macro": "^4.11.3",
|
||||
"@lingui/react": "^4.11.3",
|
||||
@@ -74,7 +73,6 @@
|
||||
"react-hook-form": "^7.45.4",
|
||||
"react-pdf": "7.7.3",
|
||||
"react-rnd": "^10.4.1",
|
||||
"remeda": "^1.27.1",
|
||||
"tailwind-merge": "^1.12.0",
|
||||
"tailwindcss-animate": "^1.0.5",
|
||||
"ts-pattern": "^5.0.5",
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { prop, sortBy } from 'remeda';
|
||||
|
||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||
import { useDocumentElement } from '@documenso/lib/client-only/hooks/use-document-element';
|
||||
@@ -64,8 +63,8 @@ const fontCaveat = Caveat({
|
||||
variable: '--font-caveat',
|
||||
});
|
||||
|
||||
const MIN_HEIGHT_PX = 20;
|
||||
const MIN_WIDTH_PX = 80;
|
||||
const MIN_HEIGHT_PX = 40;
|
||||
const MIN_WIDTH_PX = 140;
|
||||
|
||||
export type FieldFormType = {
|
||||
nativeId?: number;
|
||||
@@ -479,20 +478,9 @@ export const AddFieldsFormPartial = ({
|
||||
|
||||
const recipientsByRoleToDisplay = useMemo(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return (Object.entries(recipientsByRole) as [RecipientRole, Recipient[]][])
|
||||
.filter(([role]) => role !== RecipientRole.CC && role !== RecipientRole.VIEWER)
|
||||
.map(
|
||||
([role, roleRecipients]) =>
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
[
|
||||
role,
|
||||
sortBy(
|
||||
roleRecipients,
|
||||
[(r) => r.signingOrder || Number.MAX_SAFE_INTEGER, 'asc'],
|
||||
[prop('id'), 'asc'],
|
||||
),
|
||||
] as [RecipientRole, Recipient[]],
|
||||
);
|
||||
return (Object.entries(recipientsByRole) as [RecipientRole, Recipient[]][]).filter(
|
||||
([role]) => role !== RecipientRole.CC && role !== RecipientRole.VIEWER,
|
||||
);
|
||||
}, [recipientsByRole]);
|
||||
|
||||
const handleAdvancedSettings = () => {
|
||||
|
||||
@@ -10,7 +10,6 @@ import { useForm } from 'react-hook-form';
|
||||
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||
import type { TeamMemberRole } from '@documenso/prisma/client';
|
||||
import { DocumentStatus, type Field, type Recipient, SendStatus } from '@documenso/prisma/client';
|
||||
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
|
||||
import {
|
||||
@@ -21,10 +20,6 @@ import {
|
||||
DocumentGlobalAuthActionSelect,
|
||||
DocumentGlobalAuthActionTooltip,
|
||||
} from '@documenso/ui/components/document/document-global-auth-action-select';
|
||||
import {
|
||||
DocumentVisibilitySelect,
|
||||
DocumentVisibilityTooltip,
|
||||
} from '@documenso/ui/components/document/document-visibility-select';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
@@ -64,7 +59,6 @@ export type AddSettingsFormProps = {
|
||||
isDocumentEnterprise: boolean;
|
||||
isDocumentPdfLoaded: boolean;
|
||||
document: DocumentWithData;
|
||||
currentTeamMemberRole?: TeamMemberRole;
|
||||
onSubmit: (_data: TAddSettingsFormSchema) => void;
|
||||
};
|
||||
|
||||
@@ -75,7 +69,6 @@ export const AddSettingsFormPartial = ({
|
||||
isDocumentEnterprise,
|
||||
isDocumentPdfLoaded,
|
||||
document,
|
||||
currentTeamMemberRole,
|
||||
onSubmit,
|
||||
}: AddSettingsFormProps) => {
|
||||
const { documentAuthOption } = extractDocumentAuthMethods({
|
||||
@@ -87,7 +80,6 @@ export const AddSettingsFormPartial = ({
|
||||
defaultValues: {
|
||||
title: document.title,
|
||||
externalId: document.externalId || '',
|
||||
visibility: document.visibility || '',
|
||||
globalAccessAuth: documentAuthOption?.globalAccessAuth || undefined,
|
||||
globalActionAuth: documentAuthOption?.globalActionAuth || undefined,
|
||||
meta: {
|
||||
@@ -182,29 +174,6 @@ export const AddSettingsFormPartial = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
{currentTeamMemberRole && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="visibility"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
Document visibility
|
||||
<DocumentVisibilityTooltip />
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<DocumentVisibilitySelect
|
||||
currentMemberRole={currentTeamMemberRole}
|
||||
{...field}
|
||||
onValueChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isDocumentEnterprise && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -22,7 +22,6 @@ export const ZMapNegativeOneToUndefinedSchema = z
|
||||
export const ZAddSettingsFormSchema = z.object({
|
||||
title: z.string().trim().min(1, { message: "Title can't be empty" }),
|
||||
externalId: z.string().optional(),
|
||||
visibility: z.string().optional(),
|
||||
globalAccessAuth: ZMapNegativeOneToUndefinedSchema.pipe(
|
||||
ZDocumentAccessAuthTypesSchema.optional(),
|
||||
),
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
'use client';
|
||||
|
||||
import React, { useCallback, useId, useMemo, useRef, useState } from 'react';
|
||||
import React, { useId, useMemo, useState } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import type { DropResult, SensorAPI } from '@hello-pangea/dnd';
|
||||
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { GripVerticalIcon, Plus, Trash } from 'lucide-react';
|
||||
import { Plus, Trash } from 'lucide-react';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { prop, sortBy } from 'remeda';
|
||||
|
||||
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
import { ZRecipientAuthOptionsSchema } from '@documenso/lib/types/document-auth';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import type { Field, Recipient } from '@documenso/prisma/client';
|
||||
import { DocumentSigningOrder, RecipientRole, SendStatus } from '@documenso/prisma/client';
|
||||
import { RecipientRole, SendStatus } from '@documenso/prisma/client';
|
||||
import type { DocumentWithDetails } from '@documenso/prisma/types/document';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';
|
||||
import { RecipientActionAuthSelect } from '@documenso/ui/components/recipient/recipient-action-auth-select';
|
||||
import { RecipientRoleSelect } from '@documenso/ui/components/recipient/recipient-role-select';
|
||||
@@ -44,67 +46,93 @@ import type { DocumentFlowStep } from './types';
|
||||
|
||||
export type AddSignersFormProps = {
|
||||
documentFlow: DocumentFlowStep;
|
||||
document: DocumentWithDetails;
|
||||
recipients: Recipient[];
|
||||
fields: Field[];
|
||||
signingOrder?: DocumentSigningOrder | null;
|
||||
isDocumentEnterprise: boolean;
|
||||
onSubmit: (_data: TAddSignersFormSchema) => void;
|
||||
isDocumentPdfLoaded: boolean;
|
||||
teamId?: number;
|
||||
};
|
||||
|
||||
export const AddSignersFormPartial = ({
|
||||
documentFlow,
|
||||
document,
|
||||
recipients,
|
||||
fields,
|
||||
signingOrder,
|
||||
isDocumentEnterprise,
|
||||
onSubmit,
|
||||
isDocumentPdfLoaded,
|
||||
teamId,
|
||||
}: AddSignersFormProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const { remaining } = useLimits();
|
||||
const { data: session } = useSession();
|
||||
const router = useRouter();
|
||||
|
||||
const user = session?.user;
|
||||
|
||||
const initialId = useId();
|
||||
const $sensorApi = useRef<SensorAPI | null>(null);
|
||||
const utils = trpc.useUtils();
|
||||
|
||||
const { currentStep, totalSteps, previousStep } = useStep();
|
||||
|
||||
const defaultRecipients = [
|
||||
{
|
||||
formId: initialId,
|
||||
name: '',
|
||||
email: '',
|
||||
role: RecipientRole.SIGNER,
|
||||
signingOrder: 1,
|
||||
actionAuth: undefined,
|
||||
const { mutateAsync: addSigners } = trpc.recipient.addSigners.useMutation({
|
||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
onSuccess: (newRecipients) => {
|
||||
utils.document.getDocumentWithDetailsById.setData(
|
||||
{
|
||||
id: document.id,
|
||||
teamId,
|
||||
},
|
||||
(oldData) => ({ ...(oldData || document), Recipient: newRecipients }),
|
||||
);
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const { mutateAsync: deleteSigner } = trpc.recipient.removeSigner.useMutation({
|
||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
onSuccess: (deletedRecipient) => {
|
||||
utils.document.getDocumentWithDetailsById.setData(
|
||||
{
|
||||
id: document.id,
|
||||
teamId,
|
||||
},
|
||||
(oldData) => {
|
||||
if (!oldData) return document;
|
||||
return {
|
||||
...oldData,
|
||||
Recipient: oldData.Recipient.filter((r) => r.id !== deletedRecipient.id),
|
||||
};
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const form = useForm<TAddSignersFormSchema>({
|
||||
resolver: zodResolver(ZAddSignersFormSchema),
|
||||
defaultValues: {
|
||||
signers:
|
||||
recipients.length > 0
|
||||
? sortBy(
|
||||
recipients.map((recipient, index) => ({
|
||||
nativeId: recipient.id,
|
||||
formId: String(recipient.id),
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder ?? index + 1,
|
||||
actionAuth:
|
||||
ZRecipientAuthOptionsSchema.parse(recipient.authOptions)?.actionAuth ?? undefined,
|
||||
})),
|
||||
[prop('signingOrder'), 'asc'],
|
||||
[prop('nativeId'), 'asc'],
|
||||
)
|
||||
: defaultRecipients,
|
||||
signingOrder: signingOrder || DocumentSigningOrder.PARALLEL,
|
||||
? recipients.map((recipient) => ({
|
||||
nativeId: recipient.id,
|
||||
formId: String(recipient.id),
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
role: recipient.role,
|
||||
actionAuth:
|
||||
ZRecipientAuthOptionsSchema.parse(recipient.authOptions)?.actionAuth ?? undefined,
|
||||
}))
|
||||
: [
|
||||
{
|
||||
formId: initialId,
|
||||
name: '',
|
||||
email: '',
|
||||
role: RecipientRole.SIGNER,
|
||||
actionAuth: undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -131,13 +159,6 @@ export const AddSignersFormPartial = ({
|
||||
} = form;
|
||||
|
||||
const watchedSigners = watch('signers');
|
||||
const isSigningOrderSequential = watch('signingOrder') === DocumentSigningOrder.SEQUENTIAL;
|
||||
|
||||
const normalizeSigningOrders = (signers: typeof watchedSigners) => {
|
||||
return signers
|
||||
.sort((a, b) => (a.signingOrder ?? 0) - (b.signingOrder ?? 0))
|
||||
.map((signer, index) => ({ ...signer, signingOrder: index + 1 }));
|
||||
};
|
||||
|
||||
const onFormSubmit = form.handleSubmit(onSubmit);
|
||||
|
||||
@@ -155,25 +176,18 @@ export const AddSignersFormPartial = ({
|
||||
(signer) => signer.email.toLowerCase() === user?.email?.toLowerCase(),
|
||||
);
|
||||
|
||||
const hasDocumentBeenSent = recipients.some(
|
||||
(recipient) => recipient.sendStatus === SendStatus.SENT,
|
||||
);
|
||||
const hasBeenSentToRecipientId = (id?: number) => {
|
||||
if (!id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasBeenSentToRecipientId = useCallback(
|
||||
(id?: number) => {
|
||||
if (!id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return recipients.some(
|
||||
(recipient) =>
|
||||
recipient.id === id &&
|
||||
recipient.sendStatus === SendStatus.SENT &&
|
||||
recipient.role !== RecipientRole.CC,
|
||||
);
|
||||
},
|
||||
[recipients],
|
||||
);
|
||||
return recipients.some(
|
||||
(recipient) =>
|
||||
recipient.id === id &&
|
||||
recipient.sendStatus === SendStatus.SENT &&
|
||||
recipient.role !== RecipientRole.CC,
|
||||
);
|
||||
};
|
||||
|
||||
const onAddSigner = () => {
|
||||
appendSigner({
|
||||
@@ -182,29 +196,14 @@ export const AddSignersFormPartial = ({
|
||||
email: '',
|
||||
role: RecipientRole.SIGNER,
|
||||
actionAuth: undefined,
|
||||
signingOrder: signers.length > 0 ? (signers[signers.length - 1]?.signingOrder ?? 0) + 1 : 1,
|
||||
});
|
||||
};
|
||||
|
||||
const onRemoveSigner = (index: number) => {
|
||||
const signer = signers[index];
|
||||
|
||||
if (hasBeenSentToRecipientId(signer.nativeId)) {
|
||||
toast({
|
||||
title: _(msg`Cannot remove signer`),
|
||||
description: _(msg`This signer has already received the document.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
removeSigner(index);
|
||||
|
||||
const updatedSigners = signers.filter((_, idx) => idx !== index);
|
||||
form.setValue('signers', normalizeSigningOrders(updatedSigners));
|
||||
};
|
||||
|
||||
/*
|
||||
The self-signer is automatically saved on blur
|
||||
since the email input is focused after adding the self-signer.
|
||||
When the user clicks outside the input, the self-signer is saved in the db.
|
||||
*/
|
||||
const onAddSelfSigner = () => {
|
||||
if (emptySignerIndex !== -1) {
|
||||
setValue(`signers.${emptySignerIndex}.name`, user?.name ?? '');
|
||||
@@ -216,7 +215,6 @@ export const AddSignersFormPartial = ({
|
||||
email: user?.email ?? '',
|
||||
role: RecipientRole.SIGNER,
|
||||
actionAuth: undefined,
|
||||
signingOrder: signers.length > 0 ? (signers[signers.length - 1]?.signingOrder ?? 0) + 1 : 1,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -227,129 +225,71 @@ export const AddSignersFormPartial = ({
|
||||
}
|
||||
};
|
||||
|
||||
const onDragEnd = useCallback(
|
||||
async (result: DropResult) => {
|
||||
if (!result.destination) return;
|
||||
const handleOnBlur = async (index: number) => {
|
||||
try {
|
||||
const currentSigner = form.getValues(`signers.${index}`);
|
||||
|
||||
const items = Array.from(watchedSigners);
|
||||
const [reorderedSigner] = items.splice(result.source.index, 1);
|
||||
|
||||
let insertIndex = result.destination.index;
|
||||
while (insertIndex < items.length && hasBeenSentToRecipientId(items[insertIndex].nativeId)) {
|
||||
insertIndex++;
|
||||
if (!currentSigner.email) {
|
||||
return;
|
||||
}
|
||||
|
||||
items.splice(insertIndex, 0, reorderedSigner);
|
||||
|
||||
const updatedSigners = items.map((item, index) => ({
|
||||
...item,
|
||||
signingOrder: hasBeenSentToRecipientId(item.nativeId) ? item.signingOrder : index + 1,
|
||||
}));
|
||||
|
||||
updatedSigners.forEach((item, index) => {
|
||||
const keys: (keyof typeof item)[] = [
|
||||
'formId',
|
||||
'nativeId',
|
||||
'email',
|
||||
'name',
|
||||
'role',
|
||||
'signingOrder',
|
||||
'actionAuth',
|
||||
];
|
||||
keys.forEach((key) => {
|
||||
form.setValue(`signers.${index}.${key}` as const, item[key]);
|
||||
});
|
||||
await addSigners({
|
||||
documentId: document.id,
|
||||
teamId: teamId,
|
||||
signers: form.getValues('signers').map((signer) => ({
|
||||
...signer,
|
||||
actionAuth: signer.actionAuth || null,
|
||||
})),
|
||||
});
|
||||
|
||||
const currentLength = form.getValues('signers').length;
|
||||
if (currentLength > updatedSigners.length) {
|
||||
for (let i = updatedSigners.length; i < currentLength; i++) {
|
||||
form.unregister(`signers.${i}`);
|
||||
}
|
||||
}
|
||||
const isNewSigner = !currentSigner.nativeId;
|
||||
|
||||
await form.trigger('signers');
|
||||
},
|
||||
[form, hasBeenSentToRecipientId, watchedSigners],
|
||||
);
|
||||
|
||||
const triggerDragAndDrop = useCallback(
|
||||
(fromIndex: number, toIndex: number) => {
|
||||
if (!$sensorApi.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const draggableId = signers[fromIndex].id;
|
||||
|
||||
const preDrag = $sensorApi.current.tryGetLock(draggableId);
|
||||
|
||||
if (!preDrag) {
|
||||
return;
|
||||
}
|
||||
|
||||
const drag = preDrag.snapLift();
|
||||
|
||||
setTimeout(() => {
|
||||
// Move directly to the target index
|
||||
if (fromIndex < toIndex) {
|
||||
for (let i = fromIndex; i < toIndex; i++) {
|
||||
drag.moveDown();
|
||||
}
|
||||
} else {
|
||||
for (let i = fromIndex; i > toIndex; i--) {
|
||||
drag.moveUp();
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
drag.drop();
|
||||
}, 500);
|
||||
}, 0);
|
||||
},
|
||||
[signers],
|
||||
);
|
||||
|
||||
const updateSigningOrders = useCallback(
|
||||
(newIndex: number, oldIndex: number) => {
|
||||
const updatedSigners = form.getValues('signers').map((signer, index) => {
|
||||
if (index === oldIndex) {
|
||||
return { ...signer, signingOrder: newIndex + 1 };
|
||||
} else if (index >= newIndex && index < oldIndex) {
|
||||
return { ...signer, signingOrder: (signer.signingOrder ?? index + 1) + 1 };
|
||||
} else if (index <= newIndex && index > oldIndex) {
|
||||
return { ...signer, signingOrder: Math.max(1, (signer.signingOrder ?? index + 1) - 1) };
|
||||
}
|
||||
return signer;
|
||||
toast({
|
||||
title: isNewSigner ? 'Signer added' : 'Signer updated',
|
||||
description: isNewSigner
|
||||
? 'The signer has been added to the document.'
|
||||
: 'The signer information has been updated.',
|
||||
});
|
||||
|
||||
updatedSigners.forEach((signer, index) => {
|
||||
form.setValue(`signers.${index}.signingOrder`, signer.signingOrder);
|
||||
router.refresh();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while updating the document recipient.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
},
|
||||
[form],
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSigningOrderChange = useCallback(
|
||||
(index: number, newOrderString: string) => {
|
||||
const newOrder = parseInt(newOrderString, 10);
|
||||
const handleRemoveSigner = async (index: number) => {
|
||||
const signer = signers[index];
|
||||
|
||||
if (!newOrderString.trim()) {
|
||||
return;
|
||||
}
|
||||
if (hasBeenSentToRecipientId(signer.nativeId)) {
|
||||
toast({
|
||||
title: 'Cannot remove signer',
|
||||
description: 'This signer has already received the document.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
|
||||
if (Number.isNaN(newOrder)) {
|
||||
form.setValue(`signers.${index}.signingOrder`, index + 1);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const newIndex = newOrder - 1;
|
||||
if (index !== newIndex) {
|
||||
updateSigningOrders(newIndex, index);
|
||||
triggerDragAndDrop(index, newIndex);
|
||||
}
|
||||
},
|
||||
[form, triggerDragAndDrop, updateSigningOrders],
|
||||
);
|
||||
removeSigner(index);
|
||||
|
||||
if (signer.nativeId) {
|
||||
await deleteSigner({
|
||||
documentId: document.id,
|
||||
teamId: teamId,
|
||||
recipientId: signer.nativeId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: 'Signer removed',
|
||||
description: 'The signer has been removed from the document.',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -365,261 +305,127 @@ export const AddSignersFormPartial = ({
|
||||
|
||||
<AnimateGenericFadeInOut motionKey={showAdvancedSettings ? 'Show' : 'Hide'}>
|
||||
<Form {...form}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="signingOrder"
|
||||
render={({ field }) => (
|
||||
<FormItem className="mb-6 flex flex-row items-center space-x-2 space-y-0">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
{...field}
|
||||
id="signingOrder"
|
||||
checkClassName="text-white"
|
||||
checked={field.value === DocumentSigningOrder.SEQUENTIAL}
|
||||
onCheckedChange={(checked) =>
|
||||
field.onChange(
|
||||
checked ? DocumentSigningOrder.SEQUENTIAL : DocumentSigningOrder.PARALLEL,
|
||||
)
|
||||
}
|
||||
disabled={isSubmitting || hasDocumentBeenSent}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormLabel
|
||||
htmlFor="signingOrder"
|
||||
className="text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
<Trans>Enable signing order</Trans>
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<DragDropContext
|
||||
onDragEnd={onDragEnd}
|
||||
sensors={[
|
||||
(api: SensorAPI) => {
|
||||
$sensorApi.current = api;
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Droppable droppableId="signers">
|
||||
{(provided) => (
|
||||
<div
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
className="flex w-full flex-col gap-y-2"
|
||||
>
|
||||
{signers.map((signer, index) => (
|
||||
<Draggable
|
||||
key={`${signer.id}-${signer.signingOrder}`}
|
||||
draggableId={signer.id}
|
||||
index={index}
|
||||
isDragDisabled={
|
||||
!isSigningOrderSequential ||
|
||||
isSubmitting ||
|
||||
hasBeenSentToRecipientId(signer.nativeId) ||
|
||||
!signer.signingOrder
|
||||
}
|
||||
<div className="flex w-full flex-col gap-y-2">
|
||||
{signers.map((signer, index) => (
|
||||
<motion.fieldset
|
||||
key={signer.id}
|
||||
data-native-id={signer.nativeId}
|
||||
disabled={isSubmitting || hasBeenSentToRecipientId(signer.nativeId)}
|
||||
className={cn('grid grid-cols-8 gap-4 pb-4', {
|
||||
'border-b pt-2': showAdvancedSettings,
|
||||
})}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`signers.${index}.email`}
|
||||
render={({ field }) => (
|
||||
<FormItem
|
||||
className={cn('relative', {
|
||||
'col-span-3': !showAdvancedSettings,
|
||||
'col-span-4': showAdvancedSettings,
|
||||
})}
|
||||
>
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
className={cn('py-1', {
|
||||
'bg-widget-foreground pointer-events-none rounded-md pt-2':
|
||||
snapshot.isDragging,
|
||||
})}
|
||||
>
|
||||
<motion.fieldset
|
||||
data-native-id={signer.nativeId}
|
||||
disabled={isSubmitting || hasBeenSentToRecipientId(signer.nativeId)}
|
||||
className={cn('grid grid-cols-10 items-end gap-2 pb-2', {
|
||||
'border-b pt-2': showAdvancedSettings,
|
||||
'grid-cols-12 pr-3': isSigningOrderSequential,
|
||||
})}
|
||||
>
|
||||
{isSigningOrderSequential && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`signers.${index}.signingOrder`}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-2 mt-auto flex items-center gap-x-1 space-y-0">
|
||||
<GripVerticalIcon className="h-5 w-5 flex-shrink-0 opacity-40" />
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
max={signers.length}
|
||||
className={cn(
|
||||
'w-full text-center',
|
||||
'[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none',
|
||||
)}
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
field.onChange(e);
|
||||
handleSigningOrderChange(index, e.target.value);
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
field.onBlur();
|
||||
handleSigningOrderChange(index, e.target.value);
|
||||
}}
|
||||
disabled={
|
||||
snapshot.isDragging ||
|
||||
isSubmitting ||
|
||||
hasBeenSentToRecipientId(signer.nativeId)
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`signers.${index}.email`}
|
||||
render={({ field }) => (
|
||||
<FormItem
|
||||
className={cn('relative', {
|
||||
'col-span-4': !showAdvancedSettings,
|
||||
'col-span-5': showAdvancedSettings,
|
||||
})}
|
||||
>
|
||||
{!showAdvancedSettings && (
|
||||
<FormLabel required>
|
||||
<Trans>Email</Trans>
|
||||
</FormLabel>
|
||||
)}
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
{...field}
|
||||
disabled={
|
||||
snapshot.isDragging ||
|
||||
isSubmitting ||
|
||||
hasBeenSentToRecipientId(signer.nativeId)
|
||||
}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`signers.${index}.name`}
|
||||
render={({ field }) => (
|
||||
<FormItem
|
||||
className={cn({
|
||||
'col-span-4': !showAdvancedSettings,
|
||||
'col-span-5': showAdvancedSettings,
|
||||
})}
|
||||
>
|
||||
{!showAdvancedSettings && (
|
||||
<FormLabel>
|
||||
<Trans>Name</Trans>
|
||||
</FormLabel>
|
||||
)}
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={_(msg`Name`)}
|
||||
{...field}
|
||||
disabled={
|
||||
snapshot.isDragging ||
|
||||
isSubmitting ||
|
||||
hasBeenSentToRecipientId(signer.nativeId)
|
||||
}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{showAdvancedSettings && isDocumentEnterprise && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`signers.${index}.actionAuth`}
|
||||
render={({ field }) => (
|
||||
<FormItem
|
||||
className={cn('col-span-8', {
|
||||
'col-span-10': isSigningOrderSequential,
|
||||
})}
|
||||
>
|
||||
<FormControl>
|
||||
<RecipientActionAuthSelect
|
||||
{...field}
|
||||
onValueChange={field.onChange}
|
||||
disabled={
|
||||
snapshot.isDragging ||
|
||||
isSubmitting ||
|
||||
hasBeenSentToRecipientId(signer.nativeId)
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="col-span-2 flex gap-x-2">
|
||||
<FormField
|
||||
name={`signers.${index}.role`}
|
||||
render={({ field }) => (
|
||||
<FormItem className="mt-auto">
|
||||
<FormControl>
|
||||
<RecipientRoleSelect
|
||||
{...field}
|
||||
onValueChange={field.onChange}
|
||||
disabled={
|
||||
snapshot.isDragging ||
|
||||
isSubmitting ||
|
||||
hasBeenSentToRecipientId(signer.nativeId)
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="mt-auto inline-flex h-10 w-10 items-center justify-center hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
disabled={
|
||||
snapshot.isDragging ||
|
||||
isSubmitting ||
|
||||
hasBeenSentToRecipientId(signer.nativeId) ||
|
||||
signers.length === 1
|
||||
}
|
||||
onClick={() => onRemoveSigner(index)}
|
||||
>
|
||||
<Trash className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</motion.fieldset>
|
||||
</div>
|
||||
{!showAdvancedSettings && index === 0 && (
|
||||
<FormLabel required>
|
||||
<Trans>Email</Trans>
|
||||
</FormLabel>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder={_(msg`Email`)}
|
||||
{...field}
|
||||
disabled={isSubmitting || hasBeenSentToRecipientId(signer.nativeId)}
|
||||
onKeyDown={onKeyDown}
|
||||
onBlur={() => void handleOnBlur(index)}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`signers.${index}.name`}
|
||||
render={({ field }) => (
|
||||
<FormItem
|
||||
className={cn({
|
||||
'col-span-3': !showAdvancedSettings,
|
||||
'col-span-4': showAdvancedSettings,
|
||||
})}
|
||||
>
|
||||
{!showAdvancedSettings && index === 0 && <FormLabel>Name</FormLabel>}
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={_(msg`Name`)}
|
||||
{...field}
|
||||
disabled={isSubmitting || hasBeenSentToRecipientId(signer.nativeId)}
|
||||
onKeyDown={onKeyDown}
|
||||
onBlur={() => void handleOnBlur(index)}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{showAdvancedSettings && isDocumentEnterprise && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`signers.${index}.actionAuth`}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-6">
|
||||
<FormControl>
|
||||
<RecipientActionAuthSelect
|
||||
{...field}
|
||||
onValueChange={field.onChange}
|
||||
disabled={isSubmitting || hasBeenSentToRecipientId(signer.nativeId)}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
name={`signers.${index}.role`}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 mt-auto">
|
||||
<FormControl>
|
||||
<RecipientRoleSelect
|
||||
{...field}
|
||||
onValueChange={field.onChange}
|
||||
disabled={isSubmitting || hasBeenSentToRecipientId(signer.nativeId)}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="col-span-1 mt-auto inline-flex h-10 w-10 items-center justify-center text-slate-500 hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
disabled={
|
||||
isSubmitting ||
|
||||
hasBeenSentToRecipientId(signer.nativeId) ||
|
||||
signers.length === 1
|
||||
}
|
||||
onClick={() => void handleRemoveSigner(index)}
|
||||
>
|
||||
<Trash className="h-5 w-5" />
|
||||
</button>
|
||||
</motion.fieldset>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<FormErrorMessage
|
||||
className="mt-2"
|
||||
|
||||
@@ -3,7 +3,7 @@ import { z } from 'zod';
|
||||
import { ZRecipientActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
|
||||
|
||||
import { ZMapNegativeOneToUndefinedSchema } from './add-settings.types';
|
||||
import { DocumentSigningOrder, RecipientRole } from '.prisma/client';
|
||||
import { RecipientRole } from '.prisma/client';
|
||||
|
||||
export const ZAddSignersFormSchema = z
|
||||
.object({
|
||||
@@ -14,13 +14,11 @@ export const ZAddSignersFormSchema = z
|
||||
email: z.string().email().min(1),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
actionAuth: ZMapNegativeOneToUndefinedSchema.pipe(
|
||||
ZRecipientActionAuthTypesSchema.optional(),
|
||||
),
|
||||
}),
|
||||
),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder),
|
||||
})
|
||||
.refine(
|
||||
(schema) => {
|
||||
|
||||
@@ -26,21 +26,19 @@ export const CheckboxField = ({ field }: CheckboxFieldProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<div className="flex flex-col gap-y-2">
|
||||
{!parsedFieldMeta?.values ? (
|
||||
<FieldIcon fieldMeta={field.fieldMeta} type={field.type} signerEmail={field.signerEmail} />
|
||||
) : (
|
||||
parsedFieldMeta.values.map((item: { value: string; checked: boolean }, index: number) => (
|
||||
<div key={index} className="flex items-center gap-x-1.5">
|
||||
<Checkbox
|
||||
className="h-3 w-3"
|
||||
className="h-4 w-4"
|
||||
checkClassName="text-white"
|
||||
id={`checkbox-${index}`}
|
||||
checked={item.checked}
|
||||
/>
|
||||
<Label htmlFor={`checkbox-${index}`} className="text-xs">
|
||||
{item.value}
|
||||
</Label>
|
||||
<Label htmlFor={`checkbox-${index}`}>{item.value}</Label>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
|
||||
@@ -30,18 +30,16 @@ export const RadioField = ({ field }: RadioFieldProps) => {
|
||||
{!parsedFieldMeta?.values ? (
|
||||
<FieldIcon fieldMeta={field.fieldMeta} type={field.type} signerEmail={field.signerEmail} />
|
||||
) : (
|
||||
<RadioGroup className="gap-y-1">
|
||||
<RadioGroup>
|
||||
{parsedFieldMeta.values?.map((item, index) => (
|
||||
<div key={index} className="flex items-center gap-x-1.5">
|
||||
<RadioGroupItem
|
||||
className="pointer-events-none h-3 w-3"
|
||||
className="pointer-events-none"
|
||||
value={item.value}
|
||||
id={`option-${index}`}
|
||||
checked={item.checked}
|
||||
/>
|
||||
<Label htmlFor={`option-${index}`} className="text-xs">
|
||||
{item.value}
|
||||
</Label>
|
||||
<Label htmlFor={`option-${index}`}>{item.value}</Label>
|
||||
</div>
|
||||
))}
|
||||
</RadioGroup>
|
||||
|
||||
@@ -28,8 +28,8 @@ const fieldIcons = {
|
||||
[FieldType.EMAIL]: { icon: Mail, label: 'Email' },
|
||||
[FieldType.NAME]: { icon: User, label: 'Name' },
|
||||
[FieldType.DATE]: { icon: CalendarDays, label: 'Date' },
|
||||
[FieldType.TEXT]: { icon: Type, label: 'Text' },
|
||||
[FieldType.NUMBER]: { icon: Hash, label: 'Number' },
|
||||
[FieldType.TEXT]: { icon: Type, label: 'Add text' },
|
||||
[FieldType.NUMBER]: { icon: Hash, label: 'Add number' },
|
||||
[FieldType.RADIO]: { icon: Disc, label: 'Radio' },
|
||||
[FieldType.CHECKBOX]: { icon: CheckSquare, label: 'Checkbox' },
|
||||
[FieldType.DROPDOWN]: { icon: ChevronDown, label: 'Select' },
|
||||
@@ -45,7 +45,7 @@ export const FieldIcon = ({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'text-field-card-foreground flex items-center justify-center gap-x-1 text-[clamp(0.875rem,1.8cqw,1.2rem)]',
|
||||
'text-field-card-foreground flex items-center justify-center gap-x-1 text-xl',
|
||||
fontCaveatClassName,
|
||||
)}
|
||||
>
|
||||
@@ -71,7 +71,7 @@ export const FieldIcon = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="text-field-card-foreground flex items-center justify-center gap-x-1.5 text-[clamp(0.625rem,1cqw,0.825rem)]">
|
||||
<div className="text-field-card-foreground flex items-center justify-center gap-x-1.5 text-sm">
|
||||
<Icon className="h-4 w-4" /> {label}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -201,7 +201,6 @@ export const FieldItem = ({
|
||||
<div
|
||||
className={cn(
|
||||
'relative flex h-full w-full items-center justify-center bg-white',
|
||||
!fixedSize && '[container-type:size]',
|
||||
signerStyles.default.base,
|
||||
signerStyles.default.fieldItem,
|
||||
)}
|
||||
@@ -224,10 +223,10 @@ export const FieldItem = ({
|
||||
))}
|
||||
|
||||
{!hideRecipients && (
|
||||
<div className="absolute -right-5 top-0 z-20 hidden h-full w-5 items-center justify-center group-hover:flex">
|
||||
<div className="absolute -right-6 top-0 z-20 hidden h-full w-6 items-center justify-center group-hover:flex">
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-5 w-5 flex-col items-center justify-center rounded-r-md text-[0.5rem] font-bold text-white',
|
||||
'flex h-7 w-6 flex-col items-center justify-center rounded-r-lg text-[0.625rem] font-bold text-white',
|
||||
signerStyles.default.fieldItemInitials,
|
||||
{
|
||||
'!opacity-50': disabled || passive,
|
||||
|
||||
@@ -65,8 +65,8 @@ const fontCaveat = Caveat({
|
||||
variable: '--font-caveat',
|
||||
});
|
||||
|
||||
const MIN_HEIGHT_PX = 20;
|
||||
const MIN_WIDTH_PX = 80;
|
||||
const MIN_HEIGHT_PX = 40;
|
||||
const MIN_WIDTH_PX = 140;
|
||||
|
||||
export type AddTemplateFieldsFormProps = {
|
||||
documentFlow: DocumentFlowStep;
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
'use client';
|
||||
|
||||
import React, { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
|
||||
import React, { useEffect, useId, useMemo, useState } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import type { DropResult, SensorAPI } from '@hello-pangea/dnd';
|
||||
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { GripVerticalIcon, Link2Icon, Plus, Trash } from 'lucide-react';
|
||||
import { Link2Icon, Plus, Trash } from 'lucide-react';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
import { ZRecipientAuthOptionsSchema } from '@documenso/lib/types/document-auth';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { generateRecipientPlaceholder } from '@documenso/lib/utils/templates';
|
||||
import type { TemplateDirectLink } from '@documenso/prisma/client';
|
||||
import {
|
||||
DocumentSigningOrder,
|
||||
type Field,
|
||||
type Recipient,
|
||||
RecipientRole,
|
||||
} from '@documenso/prisma/client';
|
||||
import { type Field, type Recipient, RecipientRole } from '@documenso/prisma/client';
|
||||
import type { TemplateWithDetails } from '@documenso/prisma/types/template';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';
|
||||
import { RecipientActionAuthSelect } from '@documenso/ui/components/recipient/recipient-action-auth-select';
|
||||
import { RecipientRoleSelect } from '@documenso/ui/components/recipient/recipient-role-select';
|
||||
@@ -29,6 +27,7 @@ import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { Checkbox } from '../checkbox';
|
||||
import {
|
||||
@@ -50,10 +49,10 @@ export type AddTemplatePlaceholderRecipientsFormProps = {
|
||||
documentFlow: DocumentFlowStep;
|
||||
recipients: Recipient[];
|
||||
fields: Field[];
|
||||
signingOrder?: DocumentSigningOrder | null;
|
||||
templateDirectLink: TemplateDirectLink | null;
|
||||
isEnterprise: boolean;
|
||||
isDocumentPdfLoaded: boolean;
|
||||
template: TemplateWithDetails;
|
||||
onSubmit: (_data: TAddTemplatePlacholderRecipientsFormSchema) => void;
|
||||
};
|
||||
|
||||
@@ -63,12 +62,13 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
recipients,
|
||||
templateDirectLink,
|
||||
fields,
|
||||
signingOrder,
|
||||
isDocumentPdfLoaded,
|
||||
onSubmit,
|
||||
template,
|
||||
}: AddTemplatePlaceholderRecipientsFormProps) => {
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
const initialId = useId();
|
||||
const $sensorApi = useRef<SensorAPI | null>(null);
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { data: session } = useSession();
|
||||
@@ -81,6 +81,38 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
|
||||
const { currentStep, totalSteps, previousStep } = useStep();
|
||||
|
||||
const utils = trpc.useUtils();
|
||||
|
||||
const { mutateAsync: addTemplateSigners } = trpc.recipient.addTemplateSigners.useMutation({
|
||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
onSuccess: (newData) => {
|
||||
utils.template.getTemplateWithDetailsById.setData(
|
||||
{
|
||||
id: template.id,
|
||||
},
|
||||
(oldData) => ({ ...(oldData || template), ...newData }),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: removeTemplateSigner } = trpc.recipient.removeTemplateSigner.useMutation({
|
||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
onSuccess: (deletedRecipient) => {
|
||||
utils.template.getTemplateWithDetailsById.setData(
|
||||
{
|
||||
id: template.id,
|
||||
},
|
||||
(oldData) => {
|
||||
if (!oldData) return template;
|
||||
return {
|
||||
...oldData,
|
||||
recipients: oldData.Recipient.filter((r) => r.id !== deletedRecipient.id),
|
||||
};
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const generateDefaultFormSigners = () => {
|
||||
if (recipients.length === 0) {
|
||||
return [
|
||||
@@ -89,19 +121,17 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
role: RecipientRole.SIGNER,
|
||||
actionAuth: undefined,
|
||||
...generateRecipientPlaceholder(1),
|
||||
signingOrder: 1,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return recipients.map((recipient, index) => ({
|
||||
return recipients.map((recipient) => ({
|
||||
nativeId: recipient.id,
|
||||
formId: String(recipient.id),
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
role: recipient.role,
|
||||
actionAuth: ZRecipientAuthOptionsSchema.parse(recipient.authOptions)?.actionAuth ?? undefined,
|
||||
signingOrder: recipient.signingOrder ?? index + 1,
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -109,14 +139,12 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
resolver: zodResolver(ZAddTemplatePlacholderRecipientsFormSchema),
|
||||
defaultValues: {
|
||||
signers: generateDefaultFormSigners(),
|
||||
signingOrder: signingOrder || DocumentSigningOrder.PARALLEL,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
form.reset({
|
||||
signers: generateDefaultFormSigners(),
|
||||
signingOrder: signingOrder || DocumentSigningOrder.PARALLEL,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -140,18 +168,8 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
const {
|
||||
formState: { errors, isSubmitting },
|
||||
control,
|
||||
watch,
|
||||
} = form;
|
||||
|
||||
const watchedSigners = watch('signers');
|
||||
const isSigningOrderSequential = watch('signingOrder') === DocumentSigningOrder.SEQUENTIAL;
|
||||
|
||||
const normalizeSigningOrders = (signers: typeof watchedSigners) => {
|
||||
return signers
|
||||
.sort((a, b) => (a.signingOrder ?? 0) - (b.signingOrder ?? 0))
|
||||
.map((signer, index) => ({ ...signer, signingOrder: index + 1 }));
|
||||
};
|
||||
|
||||
const onFormSubmit = form.handleSubmit(onSubmit);
|
||||
|
||||
const {
|
||||
@@ -169,7 +187,6 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
name: user?.name ?? '',
|
||||
email: user?.email ?? '',
|
||||
role: RecipientRole.SIGNER,
|
||||
signingOrder: signers.length > 0 ? (signers[signers.length - 1]?.signingOrder ?? 0) + 1 : 1,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -178,18 +195,11 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
formId: nanoid(12),
|
||||
role: RecipientRole.SIGNER,
|
||||
...generateRecipientPlaceholder(placeholderRecipientCount),
|
||||
signingOrder: signers.length > 0 ? (signers[signers.length - 1]?.signingOrder ?? 0) + 1 : 1,
|
||||
});
|
||||
|
||||
setPlaceholderRecipientCount((count) => count + 1);
|
||||
};
|
||||
|
||||
const onRemoveSigner = (index: number) => {
|
||||
removeSigner(index);
|
||||
const updatedSigners = signers.filter((_, idx) => idx !== index);
|
||||
form.setValue('signers', normalizeSigningOrders(updatedSigners));
|
||||
};
|
||||
|
||||
const isSignerDirectRecipient = (
|
||||
signer: TAddTemplatePlacholderRecipientsFormSchema['signers'][number],
|
||||
): boolean => {
|
||||
@@ -199,126 +209,53 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
);
|
||||
};
|
||||
|
||||
const onDragEnd = useCallback(
|
||||
async (result: DropResult) => {
|
||||
if (!result.destination) return;
|
||||
const handleOnBlur = async (index: number) => {
|
||||
try {
|
||||
const currentSigner = form.getValues(`signers.${index}`);
|
||||
|
||||
const items = Array.from(watchedSigners);
|
||||
const [reorderedSigner] = items.splice(result.source.index, 1);
|
||||
if (!currentSigner.email) {
|
||||
return;
|
||||
}
|
||||
|
||||
const insertIndex = result.destination.index;
|
||||
|
||||
items.splice(insertIndex, 0, reorderedSigner);
|
||||
|
||||
const updatedSigners = items.map((item, index) => ({
|
||||
...item,
|
||||
signingOrder: index + 1,
|
||||
}));
|
||||
|
||||
updatedSigners.forEach((item, index) => {
|
||||
const keys: (keyof typeof item)[] = [
|
||||
'formId',
|
||||
'nativeId',
|
||||
'email',
|
||||
'name',
|
||||
'role',
|
||||
'signingOrder',
|
||||
'actionAuth',
|
||||
];
|
||||
keys.forEach((key) => {
|
||||
form.setValue(`signers.${index}.${key}` as const, item[key]);
|
||||
});
|
||||
await addTemplateSigners({
|
||||
templateId: template.id,
|
||||
teamId: template.teamId ?? undefined,
|
||||
signers: form.getValues('signers'),
|
||||
});
|
||||
|
||||
const currentLength = form.getValues('signers').length;
|
||||
if (currentLength > updatedSigners.length) {
|
||||
for (let i = updatedSigners.length; i < currentLength; i++) {
|
||||
form.unregister(`signers.${i}`);
|
||||
}
|
||||
}
|
||||
router.refresh();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while updating the template recipient.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
await form.trigger('signers');
|
||||
},
|
||||
[form, watchedSigners],
|
||||
);
|
||||
const handleRemoveSigner = async (index: number) => {
|
||||
const signer = signers[index];
|
||||
|
||||
const triggerDragAndDrop = useCallback(
|
||||
(fromIndex: number, toIndex: number) => {
|
||||
if (!$sensorApi.current) {
|
||||
return;
|
||||
}
|
||||
if (!signer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const draggableId = signers[fromIndex].id;
|
||||
removeSigner(index);
|
||||
|
||||
const preDrag = $sensorApi.current.tryGetLock(draggableId);
|
||||
|
||||
if (!preDrag) {
|
||||
return;
|
||||
}
|
||||
|
||||
const drag = preDrag.snapLift();
|
||||
|
||||
setTimeout(() => {
|
||||
// Move directly to the target index
|
||||
if (fromIndex < toIndex) {
|
||||
for (let i = fromIndex; i < toIndex; i++) {
|
||||
drag.moveDown();
|
||||
}
|
||||
} else {
|
||||
for (let i = fromIndex; i > toIndex; i--) {
|
||||
drag.moveUp();
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
drag.drop();
|
||||
}, 500);
|
||||
}, 0);
|
||||
},
|
||||
[signers],
|
||||
);
|
||||
|
||||
const updateSigningOrders = useCallback(
|
||||
(newIndex: number, oldIndex: number) => {
|
||||
const updatedSigners = form.getValues('signers').map((signer, index) => {
|
||||
if (index === oldIndex) {
|
||||
return { ...signer, signingOrder: newIndex + 1 };
|
||||
} else if (index >= newIndex && index < oldIndex) {
|
||||
return { ...signer, signingOrder: (signer.signingOrder ?? index + 1) + 1 };
|
||||
} else if (index <= newIndex && index > oldIndex) {
|
||||
return { ...signer, signingOrder: Math.max(1, (signer.signingOrder ?? index + 1) - 1) };
|
||||
}
|
||||
return signer;
|
||||
if (signer.nativeId) {
|
||||
await removeTemplateSigner({
|
||||
templateId: template.id,
|
||||
teamId: template.teamId ?? undefined,
|
||||
recipientId: signer.nativeId,
|
||||
});
|
||||
|
||||
updatedSigners.forEach((signer, index) => {
|
||||
form.setValue(`signers.${index}.signingOrder`, signer.signingOrder);
|
||||
toast({
|
||||
title: 'Signer removed',
|
||||
description: 'The signer has been removed from the document.',
|
||||
});
|
||||
},
|
||||
[form],
|
||||
);
|
||||
|
||||
const handleSigningOrderChange = useCallback(
|
||||
(index: number, newOrderString: string) => {
|
||||
const newOrder = parseInt(newOrderString, 10);
|
||||
|
||||
if (!newOrderString.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Number.isNaN(newOrder)) {
|
||||
form.setValue(`signers.${index}.signingOrder`, index + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
const newIndex = newOrder - 1;
|
||||
if (index !== newIndex) {
|
||||
updateSigningOrders(newIndex, index);
|
||||
triggerDragAndDrop(index, newIndex);
|
||||
}
|
||||
},
|
||||
[form, triggerDragAndDrop, updateSigningOrders],
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -334,273 +271,149 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
|
||||
<AnimateGenericFadeInOut motionKey={showAdvancedSettings ? 'Show' : 'Hide'}>
|
||||
<Form {...form}>
|
||||
{/* Enable sequential signing checkbox */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="signingOrder"
|
||||
render={({ field }) => (
|
||||
<FormItem className="mb-6 flex flex-row items-center space-x-2 space-y-0">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
{...field}
|
||||
id="signingOrder"
|
||||
checkClassName="text-white"
|
||||
checked={field.value === DocumentSigningOrder.SEQUENTIAL}
|
||||
onCheckedChange={(checked) =>
|
||||
field.onChange(
|
||||
checked ? DocumentSigningOrder.SEQUENTIAL : DocumentSigningOrder.PARALLEL,
|
||||
)
|
||||
}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormLabel
|
||||
htmlFor="signingOrder"
|
||||
className="text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
<Trans>Enable signing order</Trans>
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Drag and drop context */}
|
||||
<DragDropContext
|
||||
onDragEnd={onDragEnd}
|
||||
sensors={[
|
||||
(api: SensorAPI) => {
|
||||
$sensorApi.current = api;
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Droppable droppableId="signers">
|
||||
{(provided) => (
|
||||
<div
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
className="flex w-full flex-col gap-y-2"
|
||||
>
|
||||
{signers.map((signer, index) => (
|
||||
<Draggable
|
||||
key={`${signer.id}-${signer.signingOrder}`}
|
||||
draggableId={signer.id}
|
||||
index={index}
|
||||
isDragDisabled={
|
||||
!isSigningOrderSequential ||
|
||||
isSubmitting ||
|
||||
isSignerDirectRecipient(signer) ||
|
||||
!signer.signingOrder
|
||||
}
|
||||
<div className="flex w-full flex-col gap-y-2">
|
||||
{signers.map((signer, index) => (
|
||||
<motion.fieldset
|
||||
key={signer.id}
|
||||
data-native-id={signer.nativeId}
|
||||
disabled={isSubmitting}
|
||||
className={cn('grid grid-cols-8 gap-4 pb-4', {
|
||||
'border-b pt-2': showAdvancedSettings,
|
||||
})}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`signers.${index}.email`}
|
||||
render={({ field }) => (
|
||||
<FormItem
|
||||
className={cn('relative', {
|
||||
'col-span-3': !showAdvancedSettings,
|
||||
'col-span-4': showAdvancedSettings,
|
||||
})}
|
||||
>
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
className={cn('py-1', {
|
||||
'bg-widget-foreground pointer-events-none rounded-md pt-2':
|
||||
snapshot.isDragging,
|
||||
})}
|
||||
>
|
||||
<motion.fieldset
|
||||
data-native-id={signer.nativeId}
|
||||
disabled={isSubmitting || isSignerDirectRecipient(signer)}
|
||||
className={cn('grid grid-cols-10 items-end gap-2 pb-2', {
|
||||
'border-b pt-2': showAdvancedSettings,
|
||||
'grid-cols-12 pr-3': isSigningOrderSequential,
|
||||
})}
|
||||
>
|
||||
{isSigningOrderSequential && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`signers.${index}.signingOrder`}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-2 mt-auto flex items-center gap-x-1 space-y-0">
|
||||
<GripVerticalIcon className="h-5 w-5 flex-shrink-0 opacity-40" />
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
max={signers.length}
|
||||
className={cn(
|
||||
'w-full text-center',
|
||||
'[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none',
|
||||
)}
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
field.onChange(e);
|
||||
handleSigningOrderChange(index, e.target.value);
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
field.onBlur();
|
||||
handleSigningOrderChange(index, e.target.value);
|
||||
}}
|
||||
disabled={
|
||||
snapshot.isDragging ||
|
||||
isSubmitting ||
|
||||
isSignerDirectRecipient(signer)
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`signers.${index}.email`}
|
||||
render={({ field }) => (
|
||||
<FormItem
|
||||
className={cn('relative', {
|
||||
'col-span-4': !showAdvancedSettings,
|
||||
'col-span-5': showAdvancedSettings,
|
||||
})}
|
||||
>
|
||||
{!showAdvancedSettings && index === 0 && (
|
||||
<FormLabel required>
|
||||
<Trans>Email</Trans>
|
||||
</FormLabel>
|
||||
)}
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder={_(msg`Email`)}
|
||||
{...field}
|
||||
disabled={
|
||||
field.disabled ||
|
||||
isSubmitting ||
|
||||
signers[index].email === user?.email ||
|
||||
isSignerDirectRecipient(signer)
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`signers.${index}.name`}
|
||||
render={({ field }) => (
|
||||
<FormItem
|
||||
className={cn({
|
||||
'col-span-4': !showAdvancedSettings,
|
||||
'col-span-5': showAdvancedSettings,
|
||||
})}
|
||||
>
|
||||
{!showAdvancedSettings && index === 0 && (
|
||||
<FormLabel>
|
||||
<Trans>Name</Trans>
|
||||
</FormLabel>
|
||||
)}
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={_(msg`Name`)}
|
||||
{...field}
|
||||
disabled={
|
||||
field.disabled ||
|
||||
isSubmitting ||
|
||||
signers[index].email === user?.email ||
|
||||
isSignerDirectRecipient(signer)
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{showAdvancedSettings && isEnterprise && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`signers.${index}.actionAuth`}
|
||||
render={({ field }) => (
|
||||
<FormItem
|
||||
className={cn('col-span-8', {
|
||||
'col-span-10': isSigningOrderSequential,
|
||||
})}
|
||||
>
|
||||
<FormControl>
|
||||
<RecipientActionAuthSelect
|
||||
{...field}
|
||||
onValueChange={field.onChange}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="col-span-2 flex gap-x-2">
|
||||
<FormField
|
||||
name={`signers.${index}.role`}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 mt-auto">
|
||||
<FormControl>
|
||||
<RecipientRoleSelect
|
||||
{...field}
|
||||
onValueChange={field.onChange}
|
||||
disabled={isSubmitting}
|
||||
hideCCRecipients={isSignerDirectRecipient(signer)}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{isSignerDirectRecipient(signer) ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger className="col-span-1 mt-auto inline-flex h-10 w-10 items-center justify-center text-slate-500 hover:opacity-80">
|
||||
<Link2Icon className="h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="text-foreground z-9999 max-w-md p-4">
|
||||
<h3 className="text-foreground text-lg font-semibold">
|
||||
<Trans>Direct link receiver</Trans>
|
||||
</h3>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
<Trans>
|
||||
This field cannot be modified or deleted. When you share
|
||||
this template's direct link or add it to your public
|
||||
profile, anyone who accesses it can input their name and
|
||||
email, and fill in the fields assigned to them.
|
||||
</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="col-span-1 mt-auto inline-flex h-10 w-10 items-center justify-center text-slate-500 hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
disabled={isSubmitting || signers.length === 1}
|
||||
onClick={() => onRemoveSigner(index)}
|
||||
>
|
||||
<Trash className="h-5 w-5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</motion.fieldset>
|
||||
</div>
|
||||
{!showAdvancedSettings && index === 0 && (
|
||||
<FormLabel required>
|
||||
<Trans>Email</Trans>
|
||||
</FormLabel>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder={_(msg`Email`)}
|
||||
{...field}
|
||||
disabled={field.disabled || isSubmitting}
|
||||
onBlur={() => void handleOnBlur(index)}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`signers.${index}.name`}
|
||||
render={({ field }) => (
|
||||
<FormItem
|
||||
className={cn({
|
||||
'col-span-3': !showAdvancedSettings,
|
||||
'col-span-4': showAdvancedSettings,
|
||||
})}
|
||||
>
|
||||
{!showAdvancedSettings && index === 0 && (
|
||||
<FormLabel>
|
||||
<Trans>Name</Trans>
|
||||
</FormLabel>
|
||||
)}
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={_(msg`Name`)}
|
||||
{...field}
|
||||
disabled={field.disabled || isSubmitting}
|
||||
onBlur={() => void handleOnBlur(index)}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{showAdvancedSettings && isEnterprise && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`signers.${index}.actionAuth`}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-6">
|
||||
<FormControl>
|
||||
<RecipientActionAuthSelect
|
||||
{...field}
|
||||
onValueChange={field.onChange}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
name={`signers.${index}.role`}
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-1 mt-auto">
|
||||
<FormControl>
|
||||
<RecipientRoleSelect
|
||||
{...field}
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
void handleOnBlur(index);
|
||||
}}
|
||||
disabled={isSubmitting}
|
||||
hideCCRecipients={isSignerDirectRecipient(signer)}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{isSignerDirectRecipient(signer) ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger className="col-span-1 mt-auto inline-flex h-10 w-10 items-center justify-center text-slate-500 hover:opacity-80">
|
||||
<Link2Icon className="h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="text-foreground z-9999 max-w-md p-4">
|
||||
<h3 className="text-foreground text-lg font-semibold">
|
||||
<Trans>Direct link receiver</Trans>
|
||||
</h3>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
<Trans>
|
||||
This field cannot be modified or deleted. When you share this template's
|
||||
direct link or add it to your public profile, anyone who accesses it can
|
||||
input their name and email, and fill in the fields assigned to them.
|
||||
</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="col-span-1 mt-auto inline-flex h-10 w-10 items-center justify-center text-slate-500 hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
disabled={isSubmitting || signers.length === 1}
|
||||
onClick={() => void handleRemoveSigner(index)}
|
||||
>
|
||||
<Trash className="h-5 w-5" />
|
||||
</button>
|
||||
)}
|
||||
</motion.fieldset>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<FormErrorMessage
|
||||
className="mt-2"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZRecipientActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
|
||||
import { DocumentSigningOrder, RecipientRole } from '@documenso/prisma/client';
|
||||
|
||||
import { ZMapNegativeOneToUndefinedSchema } from '../document-flow/add-settings.types';
|
||||
import { RecipientRole } from '.prisma/client';
|
||||
|
||||
export const ZAddTemplatePlacholderRecipientsFormSchema = z
|
||||
.object({
|
||||
@@ -14,13 +14,11 @@ export const ZAddTemplatePlacholderRecipientsFormSchema = z
|
||||
email: z.string().min(1).email(),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
actionAuth: ZMapNegativeOneToUndefinedSchema.pipe(
|
||||
ZRecipientActionAuthTypesSchema.optional(),
|
||||
),
|
||||
}),
|
||||
),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder),
|
||||
})
|
||||
.refine(
|
||||
(schema) => {
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
--field-card-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--widget: 0 0% 97%;
|
||||
--widget-foreground: 0 0% 95%;
|
||||
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
@@ -136,6 +135,7 @@
|
||||
/* Surface */
|
||||
--new-surface-black: 0, 0%, 0%;
|
||||
--new-surface-white: 0, 0%, 91%;
|
||||
|
||||
}
|
||||
|
||||
.dark {
|
||||
@@ -154,7 +154,6 @@
|
||||
--card-foreground: 0 0% 95%;
|
||||
|
||||
--widget: 0 0% 14.9%;
|
||||
--widget-foreground: 0 0% 18%;
|
||||
|
||||
--border: 0 0% 27.9%;
|
||||
--input: 0 0% 27.9%;
|
||||
|
||||
Reference in New Issue
Block a user