mirror of
https://github.com/documenso/documenso.git
synced 2025-11-18 10:42:01 +10:00
chore: merged main
This commit is contained in:
@ -21,6 +21,7 @@ import {
|
||||
ZSendDocumentForSigningMutationSchema,
|
||||
ZSuccessfulDeleteTemplateResponseSchema,
|
||||
ZSuccessfulDocumentResponseSchema,
|
||||
ZSuccessfulFieldCreationResponseSchema,
|
||||
ZSuccessfulFieldResponseSchema,
|
||||
ZSuccessfulGetDocumentResponseSchema,
|
||||
ZSuccessfulGetTemplateResponseSchema,
|
||||
@ -236,7 +237,7 @@ export const ApiContractV1 = c.router(
|
||||
path: '/api/v1/documents/:id/fields',
|
||||
body: ZCreateFieldMutationSchema,
|
||||
responses: {
|
||||
200: ZSuccessfulFieldResponseSchema,
|
||||
200: ZSuccessfulFieldCreationResponseSchema,
|
||||
400: ZUnsuccessfulResponseSchema,
|
||||
401: ZUnsuccessfulResponseSchema,
|
||||
404: ZUnsuccessfulResponseSchema,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { createNextRoute } from '@ts-rest/next';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
@ -15,7 +16,7 @@ import { getDocumentById } from '@documenso/lib/server-only/document/get-documen
|
||||
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
|
||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||
import { updateDocument } from '@documenso/lib/server-only/document/update-document';
|
||||
import { createField } from '@documenso/lib/server-only/field/create-field';
|
||||
import { updateDocumentSettings } from '@documenso/lib/server-only/document/update-document-settings';
|
||||
import { deleteField } from '@documenso/lib/server-only/field/delete-field';
|
||||
import { getFieldById } from '@documenso/lib/server-only/field/get-field-by-id';
|
||||
import { updateField } from '@documenso/lib/server-only/field/update-field';
|
||||
@ -31,6 +32,14 @@ import { createDocumentFromTemplateLegacy } from '@documenso/lib/server-only/tem
|
||||
import { deleteTemplate } from '@documenso/lib/server-only/template/delete-template';
|
||||
import { findTemplates } from '@documenso/lib/server-only/template/find-templates';
|
||||
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import {
|
||||
ZCheckboxFieldMeta,
|
||||
ZDropdownFieldMeta,
|
||||
ZNumberFieldMeta,
|
||||
ZRadioFieldMeta,
|
||||
ZTextFieldMeta,
|
||||
} from '@documenso/lib/types/field-meta';
|
||||
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { getFile } from '@documenso/lib/universal/upload/get-file';
|
||||
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
|
||||
@ -38,6 +47,8 @@ import {
|
||||
getPresignGetUrl,
|
||||
getPresignPostUrl,
|
||||
} from '@documenso/lib/universal/upload/server-actions';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentDataType, DocumentStatus, SigningStatus } from '@documenso/prisma/client';
|
||||
|
||||
import { ApiContractV1 } from './contract';
|
||||
@ -285,6 +296,16 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
||||
});
|
||||
|
||||
if (body.authOptions) {
|
||||
await updateDocumentSettings({
|
||||
documentId: document.id,
|
||||
userId: user.id,
|
||||
teamId: team?.id,
|
||||
data: body.authOptions,
|
||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
||||
});
|
||||
}
|
||||
|
||||
const recipients = await setRecipientsForDocument({
|
||||
userId: user.id,
|
||||
teamId: team?.id,
|
||||
@ -455,6 +476,16 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
||||
});
|
||||
}
|
||||
|
||||
if (body.authOptions) {
|
||||
await updateDocumentSettings({
|
||||
documentId: document.id,
|
||||
userId: user.id,
|
||||
teamId: team?.id,
|
||||
data: body.authOptions,
|
||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
@ -537,6 +568,16 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
||||
});
|
||||
}
|
||||
|
||||
if (body.authOptions) {
|
||||
await updateDocumentSettings({
|
||||
documentId: document.id,
|
||||
userId: user.id,
|
||||
teamId: team?.id,
|
||||
data: body.authOptions,
|
||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
@ -672,7 +713,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
||||
|
||||
createRecipient: authenticatedMiddleware(async (args, user, team) => {
|
||||
const { id: documentId } = args.params;
|
||||
const { name, email, role } = args.body;
|
||||
const { name, email, role, authOptions } = args.body;
|
||||
|
||||
const document = await getDocumentById({
|
||||
id: Number(documentId),
|
||||
@ -726,6 +767,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
||||
email,
|
||||
name,
|
||||
role,
|
||||
actionAuth: authOptions?.actionAuth ?? null,
|
||||
},
|
||||
],
|
||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
||||
@ -757,7 +799,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
||||
|
||||
updateRecipient: authenticatedMiddleware(async (args, user, team) => {
|
||||
const { id: documentId, recipientId } = args.params;
|
||||
const { name, email, role } = args.body;
|
||||
const { name, email, role, authOptions } = args.body;
|
||||
|
||||
const document = await getDocumentById({
|
||||
id: Number(documentId),
|
||||
@ -791,6 +833,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
||||
email,
|
||||
name,
|
||||
role,
|
||||
actionAuth: authOptions?.actionAuth,
|
||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
||||
}).catch(() => null);
|
||||
|
||||
@ -869,95 +912,179 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
||||
|
||||
createField: authenticatedMiddleware(async (args, user, team) => {
|
||||
const { id: documentId } = args.params;
|
||||
const { recipientId, type, pageNumber, pageWidth, pageHeight, pageX, pageY } = args.body;
|
||||
const fields = Array.isArray(args.body) ? args.body : [args.body];
|
||||
|
||||
const document = await getDocumentById({
|
||||
id: Number(documentId),
|
||||
userId: user.id,
|
||||
teamId: team?.id,
|
||||
const document = await prisma.document.findFirst({
|
||||
select: { id: true, status: true },
|
||||
where: {
|
||||
id: Number(documentId),
|
||||
...(team?.id
|
||||
? {
|
||||
team: {
|
||||
id: team.id,
|
||||
members: { some: { userId: user.id } },
|
||||
},
|
||||
}
|
||||
: {
|
||||
userId: user.id,
|
||||
teamId: null,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
if (!document) {
|
||||
return {
|
||||
status: 404,
|
||||
body: {
|
||||
message: 'Document not found',
|
||||
},
|
||||
body: { message: 'Document not found' },
|
||||
};
|
||||
}
|
||||
|
||||
if (document.status === DocumentStatus.COMPLETED) {
|
||||
return {
|
||||
status: 400,
|
||||
body: {
|
||||
message: 'Document is already completed',
|
||||
},
|
||||
body: { message: 'Document is already completed' },
|
||||
};
|
||||
}
|
||||
|
||||
const recipient = await getRecipientById({
|
||||
id: Number(recipientId),
|
||||
documentId: Number(documentId),
|
||||
}).catch(() => null);
|
||||
try {
|
||||
const createdFields = await prisma.$transaction(async (tx) => {
|
||||
return Promise.all(
|
||||
fields.map(async (fieldData) => {
|
||||
const {
|
||||
recipientId,
|
||||
type,
|
||||
pageNumber,
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
pageX,
|
||||
pageY,
|
||||
fieldMeta,
|
||||
} = fieldData;
|
||||
|
||||
if (pageNumber <= 0) {
|
||||
throw new Error('Invalid page number');
|
||||
}
|
||||
|
||||
const recipient = await getRecipientById({
|
||||
id: Number(recipientId),
|
||||
documentId: Number(documentId),
|
||||
}).catch(() => null);
|
||||
|
||||
if (!recipient) {
|
||||
throw new Error('Recipient not found');
|
||||
}
|
||||
|
||||
if (recipient.signingStatus === SigningStatus.SIGNED) {
|
||||
throw new Error('Recipient has already signed the document');
|
||||
}
|
||||
|
||||
const advancedField = ['NUMBER', 'RADIO', 'CHECKBOX', 'DROPDOWN', 'TEXT'].includes(
|
||||
type,
|
||||
);
|
||||
|
||||
if (advancedField && !fieldMeta) {
|
||||
throw new Error(
|
||||
'Field meta is required for this type of field. Please provide the appropriate field meta object.',
|
||||
);
|
||||
}
|
||||
|
||||
if (fieldMeta && fieldMeta.type.toLowerCase() !== String(type).toLowerCase()) {
|
||||
throw new Error('Field meta type does not match the field type');
|
||||
}
|
||||
|
||||
const result = match(type)
|
||||
.with('RADIO', () => ZRadioFieldMeta.safeParse(fieldMeta))
|
||||
.with('CHECKBOX', () => ZCheckboxFieldMeta.safeParse(fieldMeta))
|
||||
.with('DROPDOWN', () => ZDropdownFieldMeta.safeParse(fieldMeta))
|
||||
.with('NUMBER', () => ZNumberFieldMeta.safeParse(fieldMeta))
|
||||
.with('TEXT', () => ZTextFieldMeta.safeParse(fieldMeta))
|
||||
.with('SIGNATURE', 'INITIALS', 'DATE', 'EMAIL', 'NAME', () => ({
|
||||
success: true,
|
||||
data: {},
|
||||
}))
|
||||
.with('FREE_SIGNATURE', () => ({
|
||||
success: false,
|
||||
error: 'FREE_SIGNATURE is not supported',
|
||||
data: {},
|
||||
}))
|
||||
.exhaustive();
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error('Field meta parsing failed');
|
||||
}
|
||||
|
||||
const field = await tx.field.create({
|
||||
data: {
|
||||
documentId: Number(documentId),
|
||||
recipientId: Number(recipientId),
|
||||
type,
|
||||
page: pageNumber,
|
||||
positionX: pageX,
|
||||
positionY: pageY,
|
||||
width: pageWidth,
|
||||
height: pageHeight,
|
||||
customText: '',
|
||||
inserted: false,
|
||||
fieldMeta: result.data,
|
||||
},
|
||||
include: {
|
||||
Recipient: true,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.documentAuditLog.create({
|
||||
data: createDocumentAuditLogData({
|
||||
type: 'FIELD_CREATED',
|
||||
documentId: Number(documentId),
|
||||
user: {
|
||||
id: team?.id ?? user.id,
|
||||
email: team?.name ?? user.email,
|
||||
name: team ? '' : user.name,
|
||||
},
|
||||
data: {
|
||||
fieldId: field.secondaryId,
|
||||
fieldRecipientEmail: field.Recipient?.email ?? '',
|
||||
fieldRecipientId: recipientId,
|
||||
fieldType: field.type,
|
||||
},
|
||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
||||
}),
|
||||
});
|
||||
|
||||
return {
|
||||
id: field.id,
|
||||
documentId: Number(field.documentId),
|
||||
recipientId: field.recipientId ?? -1,
|
||||
type: field.type,
|
||||
pageNumber: field.page,
|
||||
pageX: Number(field.positionX),
|
||||
pageY: Number(field.positionY),
|
||||
pageWidth: Number(field.width),
|
||||
pageHeight: Number(field.height),
|
||||
customText: field.customText,
|
||||
fieldMeta: ZFieldMetaSchema.parse(field.fieldMeta),
|
||||
inserted: field.inserted,
|
||||
};
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
if (!recipient) {
|
||||
return {
|
||||
status: 404,
|
||||
status: 200,
|
||||
body: {
|
||||
message: 'Recipient not found',
|
||||
fields: createdFields,
|
||||
documentId: Number(documentId),
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
return AppError.toRestAPIError(err);
|
||||
}
|
||||
|
||||
if (recipient.signingStatus === SigningStatus.SIGNED) {
|
||||
return {
|
||||
status: 400,
|
||||
body: {
|
||||
message: 'Recipient has already signed the document',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const field = await createField({
|
||||
documentId: Number(documentId),
|
||||
recipientId: Number(recipientId),
|
||||
userId: user.id,
|
||||
teamId: team?.id,
|
||||
type,
|
||||
pageNumber,
|
||||
pageX,
|
||||
pageY,
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
||||
});
|
||||
|
||||
const remappedField = {
|
||||
id: field.id,
|
||||
documentId: field.documentId,
|
||||
recipientId: field.recipientId ?? -1,
|
||||
type: field.type,
|
||||
pageNumber: field.page,
|
||||
pageX: Number(field.positionX),
|
||||
pageY: Number(field.positionY),
|
||||
pageWidth: Number(field.width),
|
||||
pageHeight: Number(field.height),
|
||||
customText: field.customText,
|
||||
inserted: field.inserted,
|
||||
};
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
...remappedField,
|
||||
documentId: Number(documentId),
|
||||
},
|
||||
};
|
||||
}),
|
||||
|
||||
updateField: authenticatedMiddleware(async (args, user, team) => {
|
||||
const { id: documentId, fieldId } = args.params;
|
||||
const { recipientId, type, pageNumber, pageWidth, pageHeight, pageX, pageY } = args.body;
|
||||
const { recipientId, type, pageNumber, pageWidth, pageHeight, pageX, pageY, fieldMeta } =
|
||||
args.body;
|
||||
|
||||
const document = await getDocumentById({
|
||||
id: Number(documentId),
|
||||
@ -1019,6 +1146,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
||||
fieldMeta: fieldMeta ? ZFieldMetaSchema.parse(fieldMeta) : undefined,
|
||||
});
|
||||
|
||||
const remappedField = {
|
||||
|
||||
@ -5,6 +5,12 @@ import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/const
|
||||
import '@documenso/lib/constants/time-zones';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
import { ZUrlSchema } from '@documenso/lib/schemas/common';
|
||||
import {
|
||||
ZDocumentAccessAuthTypesSchema,
|
||||
ZDocumentActionAuthTypesSchema,
|
||||
ZRecipientActionAuthTypesSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import {
|
||||
DocumentDataType,
|
||||
FieldType,
|
||||
@ -119,6 +125,12 @@ export const ZCreateDocumentMutationSchema = z.object({
|
||||
redirectUrl: z.string(),
|
||||
})
|
||||
.partial(),
|
||||
authOptions: z
|
||||
.object({
|
||||
globalAccessAuth: ZDocumentAccessAuthTypesSchema.optional(),
|
||||
globalActionAuth: ZDocumentActionAuthTypesSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
formValues: z.record(z.string(), z.union([z.string(), z.boolean(), z.number()])).optional(),
|
||||
});
|
||||
|
||||
@ -165,6 +177,12 @@ export const ZCreateDocumentFromTemplateMutationSchema = z.object({
|
||||
})
|
||||
.partial()
|
||||
.optional(),
|
||||
authOptions: z
|
||||
.object({
|
||||
globalAccessAuth: ZDocumentAccessAuthTypesSchema.optional(),
|
||||
globalActionAuth: ZDocumentActionAuthTypesSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
formValues: z.record(z.string(), z.union([z.string(), z.boolean(), z.number()])).optional(),
|
||||
});
|
||||
|
||||
@ -222,6 +240,12 @@ export const ZGenerateDocumentFromTemplateMutationSchema = z.object({
|
||||
})
|
||||
.partial()
|
||||
.optional(),
|
||||
authOptions: z
|
||||
.object({
|
||||
globalAccessAuth: ZDocumentAccessAuthTypesSchema.optional(),
|
||||
globalActionAuth: ZDocumentActionAuthTypesSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
formValues: z.record(z.string(), z.union([z.string(), z.boolean(), z.number()])).optional(),
|
||||
});
|
||||
|
||||
@ -253,6 +277,11 @@ export const ZCreateRecipientMutationSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
email: z.string().email().min(1),
|
||||
role: z.nativeEnum(RecipientRole).optional().default(RecipientRole.SIGNER),
|
||||
authOptions: z
|
||||
.object({
|
||||
actionAuth: ZRecipientActionAuthTypesSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
@ -292,7 +321,7 @@ export type TSuccessfulRecipientResponseSchema = z.infer<typeof ZSuccessfulRecip
|
||||
/**
|
||||
* Fields
|
||||
*/
|
||||
export const ZCreateFieldMutationSchema = z.object({
|
||||
const ZCreateFieldSchema = z.object({
|
||||
recipientId: z.number(),
|
||||
type: z.nativeEnum(FieldType),
|
||||
pageNumber: z.number(),
|
||||
@ -300,11 +329,17 @@ export const ZCreateFieldMutationSchema = z.object({
|
||||
pageY: z.number(),
|
||||
pageWidth: z.number(),
|
||||
pageHeight: z.number(),
|
||||
fieldMeta: ZFieldMetaSchema.openapi({}),
|
||||
});
|
||||
|
||||
export const ZCreateFieldMutationSchema = z.union([
|
||||
ZCreateFieldSchema,
|
||||
z.array(ZCreateFieldSchema).min(1),
|
||||
]);
|
||||
|
||||
export type TCreateFieldMutationSchema = z.infer<typeof ZCreateFieldMutationSchema>;
|
||||
|
||||
export const ZUpdateFieldMutationSchema = ZCreateFieldMutationSchema.partial();
|
||||
export const ZUpdateFieldMutationSchema = ZCreateFieldSchema.partial();
|
||||
|
||||
export type TUpdateFieldMutationSchema = z.infer<typeof ZUpdateFieldMutationSchema>;
|
||||
|
||||
@ -312,6 +347,26 @@ export const ZDeleteFieldMutationSchema = null;
|
||||
|
||||
export type TDeleteFieldMutationSchema = typeof ZDeleteFieldMutationSchema;
|
||||
|
||||
const ZSuccessfulFieldSchema = z.object({
|
||||
id: z.number(),
|
||||
documentId: z.number(),
|
||||
recipientId: z.number(),
|
||||
type: z.nativeEnum(FieldType),
|
||||
pageNumber: z.number(),
|
||||
pageX: z.number(),
|
||||
pageY: z.number(),
|
||||
pageWidth: z.number(),
|
||||
pageHeight: z.number(),
|
||||
customText: z.string(),
|
||||
fieldMeta: ZFieldMetaSchema,
|
||||
inserted: z.boolean(),
|
||||
});
|
||||
|
||||
export const ZSuccessfulFieldCreationResponseSchema = z.object({
|
||||
fields: z.union([ZSuccessfulFieldSchema, z.array(ZSuccessfulFieldSchema)]),
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZSuccessfulFieldResponseSchema = z.object({
|
||||
id: z.number(),
|
||||
documentId: z.number(),
|
||||
@ -323,6 +378,7 @@ export const ZSuccessfulFieldResponseSchema = z.object({
|
||||
pageWidth: z.number(),
|
||||
pageHeight: z.number(),
|
||||
customText: z.string(),
|
||||
fieldMeta: ZFieldMetaSchema,
|
||||
inserted: z.boolean(),
|
||||
});
|
||||
|
||||
|
||||
@ -32,8 +32,12 @@ test('[TEAMS]: update team member role', async ({ page }) => {
|
||||
await page.getByRole('combobox').click();
|
||||
await page.getByLabel('Manager').click();
|
||||
await page.getByRole('button', { name: 'Update' }).click();
|
||||
|
||||
// TODO: Remove me, but i don't care for now
|
||||
await page.reload();
|
||||
|
||||
await expect(
|
||||
page.getByRole('row').filter({ hasText: teamMemberToUpdate.user.email }),
|
||||
page.getByRole('row').filter({ hasText: teamMemberToUpdate.user.email }).first(),
|
||||
).toContainText('Manager');
|
||||
});
|
||||
|
||||
|
||||
@ -5,9 +5,8 @@ import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
||||
import {
|
||||
DIRECT_TEMPLATE_RECIPIENT_EMAIL,
|
||||
DIRECT_TEMPLATE_RECIPIENT_NAME,
|
||||
} from '@documenso/lib/constants/template';
|
||||
} from '@documenso/lib/constants/direct-templates';
|
||||
import { createDocumentAuthOptions } from '@documenso/lib/utils/document-auth';
|
||||
import { formatDocumentsPath, formatTemplatesPath } from '@documenso/lib/utils/teams';
|
||||
import { formatDirectTemplatePath } from '@documenso/lib/utils/templates';
|
||||
import { seedTeam } from '@documenso/prisma/seed/teams';
|
||||
import { seedDirectTemplate, seedTemplate } from '@documenso/prisma/seed/templates';
|
||||
@ -16,6 +15,12 @@ import { seedTestEmail, seedUser } from '@documenso/prisma/seed/users';
|
||||
import { apiSignin } from '../fixtures/authentication';
|
||||
import { checkDocumentTabCount } from '../fixtures/documents';
|
||||
|
||||
// Duped from `packages/lib/utils/teams.ts` due to errors when importing that file.
|
||||
const formatDocumentsPath = (teamUrl?: string) =>
|
||||
teamUrl ? `/t/${teamUrl}/documents` : '/documents';
|
||||
const formatTemplatesPath = (teamUrl?: string) =>
|
||||
teamUrl ? `/t/${teamUrl}/templates` : '/templates';
|
||||
|
||||
const nanoid = customAlphabet('1234567890abcdef', 10);
|
||||
|
||||
test.describe.configure({ mode: 'parallel' });
|
||||
|
||||
@ -41,7 +41,8 @@ test('[USER] can sign up with email and password', async ({ page }: { page: Page
|
||||
|
||||
await expect(page.getByRole('heading')).toContainText('Email Confirmed!');
|
||||
|
||||
await page.getByRole('link', { name: 'Go back home' }).click();
|
||||
// We now automatically redirect to the home page
|
||||
// await page.getByRole('link', { name: 'Go back home' }).click();
|
||||
|
||||
await page.waitForURL('/documents');
|
||||
|
||||
|
||||
@ -17,9 +17,9 @@
|
||||
"@documenso/prisma": "*",
|
||||
"luxon": "^3.4.0",
|
||||
"micro": "^10.0.1",
|
||||
"next": "14.0.3",
|
||||
"next": "14.2.6",
|
||||
"next-auth": "4.24.5",
|
||||
"react": "18.2.0",
|
||||
"react": "^18",
|
||||
"ts-pattern": "^5.0.5",
|
||||
"zod": "^3.22.4"
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import { RECIPIENT_ROLES_DESCRIPTION_ENG } from '@documenso/lib/constants/recipient-roles';
|
||||
import type { RecipientRole } from '@documenso/prisma/client';
|
||||
|
||||
import { Button, Section, Text } from '../components';
|
||||
@ -26,7 +26,7 @@ export const TemplateDocumentInvite = ({
|
||||
isTeamInvite,
|
||||
teamName,
|
||||
}: TemplateDocumentInviteProps) => {
|
||||
const { actionVerb, progressiveVerb } = RECIPIENT_ROLES_DESCRIPTION[role];
|
||||
const { actionVerb, progressiveVerb } = RECIPIENT_ROLES_DESCRIPTION_ENG[role];
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import { RECIPIENT_ROLES_DESCRIPTION_ENG } from '@documenso/lib/constants/recipient-roles';
|
||||
import config from '@documenso/tailwind-config';
|
||||
|
||||
import {
|
||||
@ -32,7 +32,7 @@ export const DocumentCreatedFromDirectTemplateEmailTemplate = ({
|
||||
documentName = 'Open Source Pledge.pdf',
|
||||
assetBaseUrl = 'http://localhost:3002',
|
||||
}: DocumentCompletedEmailTemplateProps) => {
|
||||
const action = RECIPIENT_ROLES_DESCRIPTION[recipientRole].actioned.toLowerCase();
|
||||
const action = RECIPIENT_ROLES_DESCRIPTION_ENG[recipientRole].actioned.toLowerCase();
|
||||
|
||||
const previewText = `Document created from direct template`;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import { RECIPIENT_ROLES_DESCRIPTION_ENG } from '@documenso/lib/constants/recipient-roles';
|
||||
import type { RecipientRole } from '@documenso/prisma/client';
|
||||
import config from '@documenso/tailwind-config';
|
||||
|
||||
@ -40,7 +40,7 @@ export const DocumentInviteEmailTemplate = ({
|
||||
isTeamInvite = false,
|
||||
teamName,
|
||||
}: DocumentInviteEmailTemplateProps) => {
|
||||
const action = RECIPIENT_ROLES_DESCRIPTION[role].actionVerb.toLowerCase();
|
||||
const action = RECIPIENT_ROLES_DESCRIPTION_ENG[role].actionVerb.toLowerCase();
|
||||
|
||||
const previewText = selfSigner
|
||||
? `Please ${action} your document ${documentName}`
|
||||
|
||||
60
packages/lib/client-only/hooks/use-throttle-fn.ts
Normal file
60
packages/lib/client-only/hooks/use-throttle-fn.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
|
||||
type ThrottleOptions = {
|
||||
leading?: boolean;
|
||||
trailing?: boolean;
|
||||
};
|
||||
|
||||
export function useThrottleFn<T extends (...args: unknown[]) => unknown>(
|
||||
fn: T,
|
||||
ms = 500,
|
||||
options: ThrottleOptions = {},
|
||||
): [(...args: Parameters<T>) => void, boolean, () => void] {
|
||||
const [isThrottling, setIsThrottling] = useState(false);
|
||||
const $isThrottling = useRef(false);
|
||||
|
||||
const $timeout = useRef<NodeJS.Timeout | null>(null);
|
||||
const $lastArgs = useRef<Parameters<T> | null>(null);
|
||||
|
||||
const { leading = true, trailing = true } = options;
|
||||
|
||||
const $setIsThrottling = useCallback((value: boolean) => {
|
||||
$isThrottling.current = value;
|
||||
setIsThrottling(value);
|
||||
}, []);
|
||||
|
||||
const throttledFn = useCallback(
|
||||
(...args: Parameters<T>) => {
|
||||
if (!$isThrottling.current) {
|
||||
$setIsThrottling(true);
|
||||
if (leading) {
|
||||
fn(...args);
|
||||
} else {
|
||||
$lastArgs.current = args;
|
||||
}
|
||||
|
||||
$timeout.current = setTimeout(() => {
|
||||
if (trailing && $lastArgs.current) {
|
||||
fn(...$lastArgs.current);
|
||||
$lastArgs.current = null;
|
||||
}
|
||||
$setIsThrottling(false);
|
||||
}, ms);
|
||||
} else {
|
||||
$lastArgs.current = args;
|
||||
}
|
||||
},
|
||||
[fn, ms, leading, trailing, $setIsThrottling],
|
||||
);
|
||||
|
||||
const cancel = useCallback(() => {
|
||||
if ($timeout.current) {
|
||||
clearTimeout($timeout.current);
|
||||
$timeout.current = null;
|
||||
$setIsThrottling(false);
|
||||
$lastArgs.current = null;
|
||||
}
|
||||
}, [$setIsThrottling]);
|
||||
|
||||
return [throttledFn, isThrottling, cancel];
|
||||
}
|
||||
@ -5,19 +5,24 @@ import { useState } from 'react';
|
||||
import { type Messages, setupI18n } from '@lingui/core';
|
||||
import { I18nProvider } from '@lingui/react';
|
||||
|
||||
import type { I18nLocaleData } from '../../constants/i18n';
|
||||
|
||||
export function I18nClientProvider({
|
||||
children,
|
||||
initialLocale,
|
||||
initialLocaleData,
|
||||
initialMessages,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
initialLocale: string;
|
||||
initialLocaleData: I18nLocaleData;
|
||||
initialMessages: Messages;
|
||||
}) {
|
||||
const { lang, locales } = initialLocaleData;
|
||||
|
||||
const [i18n] = useState(() => {
|
||||
return setupI18n({
|
||||
locale: initialLocale,
|
||||
messages: { [initialLocale]: initialMessages },
|
||||
locale: lang,
|
||||
locales: locales,
|
||||
messages: { [lang]: initialMessages },
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import 'server-only';
|
||||
|
||||
import { cookies } from 'next/headers';
|
||||
import { cookies, headers } from 'next/headers';
|
||||
|
||||
import type { I18n, Messages } from '@lingui/core';
|
||||
import { setupI18n } from '@lingui/core';
|
||||
@ -8,19 +8,19 @@ import { setI18n } from '@lingui/react/server';
|
||||
|
||||
import { IS_APP_WEB } from '../../constants/app';
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '../../constants/i18n';
|
||||
import { extractSupportedLanguage } from '../../utils/i18n';
|
||||
import { extractLocaleData } from '../../utils/i18n';
|
||||
|
||||
type SupportedLocales = (typeof SUPPORTED_LANGUAGE_CODES)[number];
|
||||
type SupportedLanguages = (typeof SUPPORTED_LANGUAGE_CODES)[number];
|
||||
|
||||
async function loadCatalog(locale: SupportedLocales): Promise<{
|
||||
async function loadCatalog(lang: SupportedLanguages): Promise<{
|
||||
[k: string]: Messages;
|
||||
}> {
|
||||
const { messages } = await import(
|
||||
`../../translations/${locale}/${IS_APP_WEB ? 'web' : 'marketing'}.js`
|
||||
`../../translations/${lang}/${IS_APP_WEB ? 'web' : 'marketing'}.js`
|
||||
);
|
||||
|
||||
return {
|
||||
[locale]: messages,
|
||||
[lang]: messages,
|
||||
};
|
||||
}
|
||||
|
||||
@ -31,18 +31,18 @@ export const allMessages = catalogs.reduce((acc, oneCatalog) => {
|
||||
return { ...acc, ...oneCatalog };
|
||||
}, {});
|
||||
|
||||
type AllI18nInstances = { [K in SupportedLocales]: I18n };
|
||||
type AllI18nInstances = { [K in SupportedLanguages]: I18n };
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
export const allI18nInstances = SUPPORTED_LANGUAGE_CODES.reduce((acc, locale) => {
|
||||
const messages = allMessages[locale] ?? {};
|
||||
export const allI18nInstances = SUPPORTED_LANGUAGE_CODES.reduce((acc, lang) => {
|
||||
const messages = allMessages[lang] ?? {};
|
||||
|
||||
const i18n = setupI18n({
|
||||
locale,
|
||||
messages: { [locale]: messages },
|
||||
locale: lang,
|
||||
messages: { [lang]: messages },
|
||||
});
|
||||
|
||||
return { ...acc, [locale]: i18n };
|
||||
return { ...acc, [lang]: i18n };
|
||||
}, {}) as AllI18nInstances;
|
||||
|
||||
/**
|
||||
@ -50,19 +50,23 @@ export const allI18nInstances = SUPPORTED_LANGUAGE_CODES.reduce((acc, locale) =>
|
||||
*
|
||||
* https://lingui.dev/tutorials/react-rsc#pages-layouts-and-lingui
|
||||
*/
|
||||
export const setupI18nSSR = (overrideLang?: SupportedLocales) => {
|
||||
const lang =
|
||||
overrideLang ||
|
||||
extractSupportedLanguage({
|
||||
cookies: cookies(),
|
||||
});
|
||||
export const setupI18nSSR = () => {
|
||||
const { lang, locales } = extractLocaleData({
|
||||
cookies: cookies(),
|
||||
headers: headers(),
|
||||
});
|
||||
|
||||
// Get and set a ready-made i18n instance for the given language.
|
||||
const i18n = allI18nInstances[lang];
|
||||
|
||||
// Reactivate the i18n instance with the locale for date and number formatting.
|
||||
i18n.activate(lang, locales);
|
||||
|
||||
setI18n(i18n);
|
||||
|
||||
return {
|
||||
lang,
|
||||
locales,
|
||||
i18n,
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,37 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
export type LocaleContextValue = {
|
||||
locale: string;
|
||||
};
|
||||
|
||||
export const LocaleContext = createContext<LocaleContextValue | null>(null);
|
||||
|
||||
export const useLocale = () => {
|
||||
const context = useContext(LocaleContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('useLocale must be used within a LocaleProvider');
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
|
||||
export function LocaleProvider({
|
||||
children,
|
||||
locale,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
locale: string;
|
||||
}) {
|
||||
return (
|
||||
<LocaleContext.Provider
|
||||
value={{
|
||||
locale: locale,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</LocaleContext.Provider>
|
||||
);
|
||||
}
|
||||
@ -5,10 +5,13 @@ export const APP_DOCUMENT_UPLOAD_SIZE_LIMIT =
|
||||
|
||||
export const NEXT_PUBLIC_WEBAPP_URL = () => env('NEXT_PUBLIC_WEBAPP_URL');
|
||||
export const NEXT_PUBLIC_MARKETING_URL = () => env('NEXT_PUBLIC_MARKETING_URL');
|
||||
export const NEXT_PRIVATE_INTERNAL_WEBAPP_URL =
|
||||
process.env.NEXT_PRIVATE_INTERNAL_WEBAPP_URL ?? NEXT_PUBLIC_WEBAPP_URL();
|
||||
|
||||
export const IS_APP_MARKETING = process.env.NEXT_PUBLIC_PROJECT === 'marketing';
|
||||
export const IS_APP_WEB = process.env.NEXT_PUBLIC_PROJECT === 'web';
|
||||
export const IS_BILLING_ENABLED = () => env('NEXT_PUBLIC_FEATURE_BILLING_ENABLED') === 'true';
|
||||
export const IS_APP_WEB_I18N_ENABLED = true;
|
||||
|
||||
export const APP_FOLDER = () => (IS_APP_MARKETING ? 'marketing' : 'web');
|
||||
|
||||
|
||||
3
packages/lib/constants/direct-templates.ts
Normal file
3
packages/lib/constants/direct-templates.ts
Normal file
@ -0,0 +1,3 @@
|
||||
// Put into a separate file due to Playwright not compiling due to the macro in the templates.ts file.
|
||||
export const DIRECT_TEMPLATE_RECIPIENT_EMAIL = 'direct.link@documenso.com';
|
||||
export const DIRECT_TEMPLATE_RECIPIENT_NAME = 'Direct link recipient';
|
||||
@ -6,9 +6,22 @@ export const ZSupportedLanguageCodeSchema = z.enum(SUPPORTED_LANGUAGE_CODES).cat
|
||||
|
||||
export type SupportedLanguageCodes = (typeof SUPPORTED_LANGUAGE_CODES)[number];
|
||||
|
||||
export type I18nLocaleData = {
|
||||
/**
|
||||
* The supported language extracted from the locale.
|
||||
*/
|
||||
lang: SupportedLanguageCodes;
|
||||
|
||||
/**
|
||||
* The preferred locales.
|
||||
*/
|
||||
locales: string[];
|
||||
};
|
||||
|
||||
export const APP_I18N_OPTIONS = {
|
||||
supportedLangs: SUPPORTED_LANGUAGE_CODES,
|
||||
sourceLang: 'en',
|
||||
defaultLocale: 'en-US',
|
||||
} as const;
|
||||
|
||||
type SupportedLanguage = {
|
||||
|
||||
@ -1,29 +1,64 @@
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { RecipientRole } from '@documenso/prisma/client';
|
||||
|
||||
export const RECIPIENT_ROLES_DESCRIPTION = {
|
||||
[RecipientRole.APPROVER]: {
|
||||
actionVerb: 'Approve',
|
||||
actioned: 'Approved',
|
||||
progressiveVerb: 'Approving',
|
||||
roleName: 'Approver',
|
||||
actionVerb: msg`Approve`,
|
||||
actioned: msg`Approved`,
|
||||
progressiveVerb: msg`Approving`,
|
||||
roleName: msg`Approver`,
|
||||
},
|
||||
[RecipientRole.CC]: {
|
||||
actionVerb: 'CC',
|
||||
actioned: `CC'd`,
|
||||
progressiveVerb: 'CC',
|
||||
roleName: 'Cc',
|
||||
actionVerb: msg`CC`,
|
||||
actioned: msg`CC'd`,
|
||||
progressiveVerb: msg`CC`,
|
||||
roleName: msg`Cc`,
|
||||
},
|
||||
[RecipientRole.SIGNER]: {
|
||||
actionVerb: 'Sign',
|
||||
actioned: 'Signed',
|
||||
progressiveVerb: 'Signing',
|
||||
roleName: 'Signer',
|
||||
actionVerb: msg`Sign`,
|
||||
actioned: msg`Signed`,
|
||||
progressiveVerb: msg`Signing`,
|
||||
roleName: msg`Signer`,
|
||||
},
|
||||
[RecipientRole.VIEWER]: {
|
||||
actionVerb: 'View',
|
||||
actioned: 'Viewed',
|
||||
progressiveVerb: 'Viewing',
|
||||
roleName: 'Viewer',
|
||||
actionVerb: msg`View`,
|
||||
actioned: msg`Viewed`,
|
||||
progressiveVerb: msg`Viewing`,
|
||||
roleName: msg`Viewer`,
|
||||
},
|
||||
} satisfies Record<keyof typeof RecipientRole, unknown>;
|
||||
|
||||
/**
|
||||
* Raw english descriptions for emails.
|
||||
*
|
||||
* Todo: Handle i18n for emails.
|
||||
*/
|
||||
export const RECIPIENT_ROLES_DESCRIPTION_ENG = {
|
||||
[RecipientRole.APPROVER]: {
|
||||
actionVerb: `Approve`,
|
||||
actioned: `Approved`,
|
||||
progressiveVerb: `Approving`,
|
||||
roleName: `Approver`,
|
||||
},
|
||||
[RecipientRole.CC]: {
|
||||
actionVerb: `CC`,
|
||||
actioned: `CC'd`,
|
||||
progressiveVerb: `CC`,
|
||||
roleName: `Cc`,
|
||||
},
|
||||
[RecipientRole.SIGNER]: {
|
||||
actionVerb: `Sign`,
|
||||
actioned: `Signed`,
|
||||
progressiveVerb: `Signing`,
|
||||
roleName: `Signer`,
|
||||
},
|
||||
[RecipientRole.VIEWER]: {
|
||||
actionVerb: `View`,
|
||||
actioned: `Viewed`,
|
||||
progressiveVerb: `Viewing`,
|
||||
roleName: `Viewer`,
|
||||
},
|
||||
} satisfies Record<keyof typeof RecipientRole, unknown>;
|
||||
|
||||
@ -34,8 +69,18 @@ export const RECIPIENT_ROLE_TO_EMAIL_TYPE = {
|
||||
} as const;
|
||||
|
||||
export const RECIPIENT_ROLE_SIGNING_REASONS = {
|
||||
[RecipientRole.SIGNER]: 'I am a signer of this document',
|
||||
[RecipientRole.APPROVER]: 'I am an approver of this document',
|
||||
[RecipientRole.CC]: 'I am required to recieve a copy of this document',
|
||||
[RecipientRole.VIEWER]: 'I am a viewer of this document',
|
||||
[RecipientRole.SIGNER]: msg`I am a signer of this document`,
|
||||
[RecipientRole.APPROVER]: msg`I am an approver of this document`,
|
||||
[RecipientRole.CC]: msg`I am required to receive a copy of this document`,
|
||||
[RecipientRole.VIEWER]: msg`I am a viewer of this document`,
|
||||
} satisfies Record<keyof typeof RecipientRole, MessageDescriptor>;
|
||||
|
||||
/**
|
||||
* Raw english descriptions for certificates.
|
||||
*/
|
||||
export const RECIPIENT_ROLE_SIGNING_REASONS_ENG = {
|
||||
[RecipientRole.SIGNER]: `I am a signer of this document`,
|
||||
[RecipientRole.APPROVER]: `I am an approver of this document`,
|
||||
[RecipientRole.CC]: `I am required to receive a copy of this document`,
|
||||
[RecipientRole.VIEWER]: `I am a viewer of this document`,
|
||||
} satisfies Record<keyof typeof RecipientRole, string>;
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { TeamMemberRole } from '@documenso/prisma/client';
|
||||
|
||||
export const TEAM_URL_ROOT_REGEX = new RegExp('^/t/[^/]+$');
|
||||
export const TEAM_URL_REGEX = new RegExp('^/t/[^/]+');
|
||||
|
||||
export const TEAM_MEMBER_ROLE_MAP: Record<keyof typeof TeamMemberRole, string> = {
|
||||
ADMIN: 'Admin',
|
||||
MANAGER: 'Manager',
|
||||
MEMBER: 'Member',
|
||||
export const TEAM_MEMBER_ROLE_MAP: Record<keyof typeof TeamMemberRole, MessageDescriptor> = {
|
||||
ADMIN: msg`Admin`,
|
||||
MANAGER: msg`Manager`,
|
||||
MEMBER: msg`Member`,
|
||||
};
|
||||
|
||||
export const TEAM_MEMBER_ROLE_PERMISSIONS_MAP = {
|
||||
|
||||
@ -1,28 +1,23 @@
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
export const TEMPLATE_RECIPIENT_EMAIL_PLACEHOLDER_REGEX = /recipient\.\d+@documenso\.com/i;
|
||||
export const TEMPLATE_RECIPIENT_NAME_PLACEHOLDER_REGEX = /Recipient \d+/i;
|
||||
|
||||
export const DIRECT_TEMPLATE_DOCUMENTATION = [
|
||||
{
|
||||
title: 'Enable Direct Link Signing',
|
||||
description:
|
||||
'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.',
|
||||
title: msg`Enable Direct Link Signing`,
|
||||
description: msg`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.`,
|
||||
},
|
||||
{
|
||||
title: 'Configure Direct Recipient',
|
||||
description:
|
||||
'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.',
|
||||
title: msg`Configure Direct Recipient`,
|
||||
description: msg`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.`,
|
||||
},
|
||||
{
|
||||
title: 'Share the Link',
|
||||
description:
|
||||
'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.',
|
||||
title: msg`Share the Link`,
|
||||
description: msg`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.`,
|
||||
},
|
||||
{
|
||||
title: 'Document Creation',
|
||||
description:
|
||||
'After submission, a document will be automatically generated and added to your documents page. You will also receive a notification via email.',
|
||||
title: msg`Document Creation`,
|
||||
description: msg`After submission, a document will be automatically generated and added to your documents page. You will also receive a notification via email.`,
|
||||
},
|
||||
];
|
||||
|
||||
export const DIRECT_TEMPLATE_RECIPIENT_EMAIL = 'direct.link@documenso.com';
|
||||
export const DIRECT_TEMPLATE_RECIPIENT_NAME = 'Direct link recipient';
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
import { Toast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
export const TOAST_DOCUMENT_SHARE_SUCCESS: Toast = {
|
||||
title: 'Copied to clipboard',
|
||||
description: 'The sharing link has been copied to your clipboard.',
|
||||
} as const;
|
||||
|
||||
export const TOAST_DOCUMENT_SHARE_ERROR: Toast = {
|
||||
variant: 'destructive',
|
||||
title: 'Something went wrong',
|
||||
description: 'The sharing link could not be created at this time. Please try again.',
|
||||
duration: 5000,
|
||||
};
|
||||
@ -6,7 +6,7 @@ import { json } from 'micro';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { BackgroundJobStatus, Prisma } from '@documenso/prisma/client';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { NEXT_PRIVATE_INTERNAL_WEBAPP_URL } from '../../constants/app';
|
||||
import { sign } from '../../server-only/crypto/sign';
|
||||
import { verify } from '../../server-only/crypto/verify';
|
||||
import {
|
||||
@ -229,7 +229,7 @@ export class LocalJobProvider extends BaseJobProvider {
|
||||
}) {
|
||||
const { jobId, jobDefinitionId, data, isRetry } = options;
|
||||
|
||||
const endpoint = `${NEXT_PUBLIC_WEBAPP_URL()}/api/jobs/${jobDefinitionId}/${jobId}`;
|
||||
const endpoint = `${NEXT_PRIVATE_INTERNAL_WEBAPP_URL}/api/jobs/${jobDefinitionId}/${jobId}`;
|
||||
const signature = sign(data);
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
|
||||
@ -16,7 +16,7 @@ import {
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
|
||||
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
|
||||
import {
|
||||
RECIPIENT_ROLES_DESCRIPTION,
|
||||
RECIPIENT_ROLES_DESCRIPTION_ENG,
|
||||
RECIPIENT_ROLE_TO_EMAIL_TYPE,
|
||||
} from '../../../constants/recipient-roles';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs';
|
||||
@ -87,8 +87,8 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
||||
|
||||
const { email, name } = recipient;
|
||||
const selfSigner = email === user.email;
|
||||
const { actionVerb } = RECIPIENT_ROLES_DESCRIPTION[recipient.role];
|
||||
const recipientActionVerb = actionVerb.toLowerCase();
|
||||
const recipientActionVerb =
|
||||
RECIPIENT_ROLES_DESCRIPTION_ENG[recipient.role].actionVerb.toLowerCase();
|
||||
|
||||
let emailMessage = customEmail?.message || '';
|
||||
let emailSubject = `Please ${recipientActionVerb} this document`;
|
||||
|
||||
@ -17,6 +17,7 @@ import { AppError, AppErrorCode } from '../errors/app-error';
|
||||
import { jobsClient } from '../jobs/client';
|
||||
import { isTwoFactorAuthenticationEnabled } from '../server-only/2fa/is-2fa-availble';
|
||||
import { validateTwoFactorAuthentication } from '../server-only/2fa/validate-2fa';
|
||||
import { decryptSecondaryData } from '../server-only/crypto/decrypt';
|
||||
import { getMostRecentVerificationTokenByUserId } from '../server-only/user/get-most-recent-verification-token-by-user-id';
|
||||
import { getUserByEmail } from '../server-only/user/get-user-by-email';
|
||||
import type { TAuthenticationResponseJSONSchema } from '../types/webauthn';
|
||||
@ -267,6 +268,55 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
id: Number(user.id),
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
emailVerified: user.emailVerified?.toISOString() ?? null,
|
||||
} satisfies User;
|
||||
},
|
||||
}),
|
||||
CredentialsProvider({
|
||||
id: 'manual',
|
||||
name: 'Manual',
|
||||
credentials: {
|
||||
credential: { label: 'Credential', type: 'credential' },
|
||||
},
|
||||
async authorize(credentials, req) {
|
||||
const credential = credentials?.credential;
|
||||
|
||||
if (typeof credential !== 'string' || credential.length === 0) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
const decryptedCredential = decryptSecondaryData(credential);
|
||||
|
||||
if (!decryptedCredential) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
const parsedCredential = JSON.parse(decryptedCredential);
|
||||
|
||||
if (typeof parsedCredential !== 'object' || parsedCredential === null) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
const { userId, email } = parsedCredential;
|
||||
|
||||
if (typeof userId !== 'number' || typeof email !== 'string') {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
return {
|
||||
id: Number(user.id),
|
||||
email: user.email,
|
||||
|
||||
@ -41,13 +41,13 @@
|
||||
"luxon": "^3.4.0",
|
||||
"micro": "^10.0.1",
|
||||
"nanoid": "^4.0.2",
|
||||
"next": "14.0.3",
|
||||
"next": "14.2.6",
|
||||
"next-auth": "4.24.5",
|
||||
"oslo": "^0.17.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pg": "^8.11.3",
|
||||
"playwright": "1.43.0",
|
||||
"react": "18.2.0",
|
||||
"react": "^18",
|
||||
"remeda": "^1.27.1",
|
||||
"sharp": "0.32.6",
|
||||
"stripe": "^12.7.0",
|
||||
|
||||
@ -2,25 +2,33 @@ import { prisma } from '@documenso/prisma';
|
||||
import type { User } from '@documenso/prisma/client';
|
||||
import { UserSecurityAuditLogType } from '@documenso/prisma/client';
|
||||
|
||||
import { AppError } from '../../errors/app-error';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { validateTwoFactorAuthentication } from './validate-2fa';
|
||||
|
||||
type DisableTwoFactorAuthenticationOptions = {
|
||||
user: User;
|
||||
token: string;
|
||||
totpCode?: string;
|
||||
backupCode?: string;
|
||||
requestMetadata?: RequestMetadata;
|
||||
};
|
||||
|
||||
export const disableTwoFactorAuthentication = async ({
|
||||
token,
|
||||
totpCode,
|
||||
backupCode,
|
||||
user,
|
||||
requestMetadata,
|
||||
}: DisableTwoFactorAuthenticationOptions) => {
|
||||
let isValid = await validateTwoFactorAuthentication({ totpCode: token, user });
|
||||
let isValid = false;
|
||||
|
||||
if (!isValid) {
|
||||
isValid = await validateTwoFactorAuthentication({ backupCode: token, user });
|
||||
if (!totpCode && !backupCode) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
if (totpCode) {
|
||||
isValid = await validateTwoFactorAuthentication({ totpCode, user });
|
||||
} else if (backupCode) {
|
||||
isValid = await validateTwoFactorAuthentication({ backupCode, user });
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
|
||||
@ -147,7 +147,7 @@ export const getDocumentAndRecipientByToken = async ({
|
||||
},
|
||||
});
|
||||
|
||||
const recipient = result.Recipient[0];
|
||||
const [recipient] = result.Recipient;
|
||||
|
||||
// Sanity check, should not be possible.
|
||||
if (!recipient) {
|
||||
|
||||
@ -5,7 +5,7 @@ import { render } from '@documenso/email/render';
|
||||
import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite';
|
||||
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
|
||||
import {
|
||||
RECIPIENT_ROLES_DESCRIPTION,
|
||||
RECIPIENT_ROLES_DESCRIPTION_ENG,
|
||||
RECIPIENT_ROLE_TO_EMAIL_TYPE,
|
||||
} from '@documenso/lib/constants/recipient-roles';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
@ -97,8 +97,8 @@ export const resendDocument = async ({
|
||||
const { email, name } = recipient;
|
||||
const selfSigner = email === user.email;
|
||||
|
||||
const { actionVerb } = RECIPIENT_ROLES_DESCRIPTION[recipient.role];
|
||||
const recipientActionVerb = actionVerb.toLowerCase();
|
||||
const recipientActionVerb =
|
||||
RECIPIENT_ROLES_DESCRIPTION_ENG[recipient.role].actionVerb.toLowerCase();
|
||||
|
||||
let emailMessage = customEmail?.message || '';
|
||||
let emailSubject = `Reminder: Please ${recipientActionVerb} this document`;
|
||||
|
||||
@ -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 } 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';
|
||||
|
||||
/**
|
||||
@ -46,6 +50,10 @@ export default async function handlerFeatureFlagAll(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);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
@ -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 } 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,6 +71,10 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
@ -1,6 +1,16 @@
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { FieldType, Team } from '@documenso/prisma/client';
|
||||
|
||||
import {
|
||||
ZCheckboxFieldMeta,
|
||||
ZDropdownFieldMeta,
|
||||
ZNumberFieldMeta,
|
||||
ZRadioFieldMeta,
|
||||
ZTextFieldMeta,
|
||||
} from '../../types/field-meta';
|
||||
import type { TFieldMetaSchema as FieldMeta } from '../../types/field-meta';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||
|
||||
@ -15,6 +25,7 @@ export type CreateFieldOptions = {
|
||||
pageY: number;
|
||||
pageWidth: number;
|
||||
pageHeight: number;
|
||||
fieldMeta?: FieldMeta;
|
||||
requestMetadata?: RequestMetadata;
|
||||
};
|
||||
|
||||
@ -29,6 +40,7 @@ export const createField = async ({
|
||||
pageY,
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
fieldMeta,
|
||||
requestMetadata,
|
||||
}: CreateFieldOptions) => {
|
||||
const document = await prisma.document.findFirst({
|
||||
@ -85,6 +97,39 @@ export const createField = async ({
|
||||
});
|
||||
}
|
||||
|
||||
const advancedField = ['NUMBER', 'RADIO', 'CHECKBOX', 'DROPDOWN', 'TEXT'].includes(type);
|
||||
|
||||
if (advancedField && !fieldMeta) {
|
||||
throw new Error(
|
||||
'Field meta is required for this type of field. Please provide the appropriate field meta object.',
|
||||
);
|
||||
}
|
||||
|
||||
if (fieldMeta && fieldMeta.type.toLowerCase() !== String(type).toLowerCase()) {
|
||||
throw new Error('Field meta type does not match the field type');
|
||||
}
|
||||
|
||||
const result = match(type)
|
||||
.with('RADIO', () => ZRadioFieldMeta.safeParse(fieldMeta))
|
||||
.with('CHECKBOX', () => ZCheckboxFieldMeta.safeParse(fieldMeta))
|
||||
.with('DROPDOWN', () => ZDropdownFieldMeta.safeParse(fieldMeta))
|
||||
.with('NUMBER', () => ZNumberFieldMeta.safeParse(fieldMeta))
|
||||
.with('TEXT', () => ZTextFieldMeta.safeParse(fieldMeta))
|
||||
.with('SIGNATURE', 'INITIALS', 'DATE', 'EMAIL', 'NAME', () => ({
|
||||
success: true,
|
||||
data: {},
|
||||
}))
|
||||
.with('FREE_SIGNATURE', () => ({
|
||||
success: false,
|
||||
error: 'FREE_SIGNATURE is not supported',
|
||||
data: {},
|
||||
}))
|
||||
.exhaustive();
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error('Field meta parsing failed');
|
||||
}
|
||||
|
||||
const field = await prisma.field.create({
|
||||
data: {
|
||||
documentId,
|
||||
@ -97,6 +142,7 @@ export const createField = async ({
|
||||
height: pageHeight,
|
||||
customText: '',
|
||||
inserted: false,
|
||||
fieldMeta: result.data,
|
||||
},
|
||||
include: {
|
||||
Recipient: true,
|
||||
|
||||
@ -107,7 +107,10 @@ export const setFieldsForTemplate = async ({
|
||||
}
|
||||
}
|
||||
|
||||
if (field.type === FieldType.CHECKBOX && field.fieldMeta) {
|
||||
if (field.type === FieldType.CHECKBOX) {
|
||||
if (!field.fieldMeta) {
|
||||
throw new Error('Checkbox field is missing required metadata');
|
||||
}
|
||||
const checkboxFieldParsedMeta = ZCheckboxFieldMeta.parse(field.fieldMeta);
|
||||
const errors = validateCheckboxField(
|
||||
checkboxFieldParsedMeta?.values?.map((item) => item.value) ?? [],
|
||||
@ -118,7 +121,10 @@ export const setFieldsForTemplate = async ({
|
||||
}
|
||||
}
|
||||
|
||||
if (field.type === FieldType.RADIO && field.fieldMeta) {
|
||||
if (field.type === FieldType.RADIO) {
|
||||
if (!field.fieldMeta) {
|
||||
throw new Error('Radio field is missing required metadata');
|
||||
}
|
||||
const radioFieldParsedMeta = ZRadioFieldMeta.parse(field.fieldMeta);
|
||||
const checkedRadioFieldValue = radioFieldParsedMeta.values?.find(
|
||||
(option) => option.checked,
|
||||
@ -129,7 +135,10 @@ export const setFieldsForTemplate = async ({
|
||||
}
|
||||
}
|
||||
|
||||
if (field.type === FieldType.DROPDOWN && field.fieldMeta) {
|
||||
if (field.type === FieldType.DROPDOWN) {
|
||||
if (!field.fieldMeta) {
|
||||
throw new Error('Dropdown field is missing required metadata');
|
||||
}
|
||||
const dropdownFieldParsedMeta = ZDropdownFieldMeta.parse(field.fieldMeta);
|
||||
const errors = validateDropdownField(undefined, dropdownFieldParsedMeta);
|
||||
if (errors.length > 0) {
|
||||
|
||||
@ -37,6 +37,10 @@ export const updateField = async ({
|
||||
requestMetadata,
|
||||
fieldMeta,
|
||||
}: UpdateFieldOptions) => {
|
||||
if (type === 'FREE_SIGNATURE') {
|
||||
throw new Error('Cannot update a FREE_SIGNATURE field');
|
||||
}
|
||||
|
||||
const oldField = await prisma.field.findFirstOrThrow({
|
||||
where: {
|
||||
id: fieldId,
|
||||
@ -61,6 +65,11 @@ export const updateField = async ({
|
||||
},
|
||||
});
|
||||
|
||||
const newFieldMeta = {
|
||||
...(oldField.fieldMeta as FieldMeta),
|
||||
...fieldMeta,
|
||||
};
|
||||
|
||||
const field = prisma.$transaction(async (tx) => {
|
||||
const updatedField = await tx.field.update({
|
||||
where: {
|
||||
@ -74,13 +83,39 @@ export const updateField = async ({
|
||||
positionY: pageY,
|
||||
width: pageWidth,
|
||||
height: pageHeight,
|
||||
fieldMeta,
|
||||
fieldMeta: newFieldMeta,
|
||||
},
|
||||
include: {
|
||||
Recipient: true,
|
||||
},
|
||||
});
|
||||
|
||||
const user = await prisma.user.findFirstOrThrow({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
});
|
||||
|
||||
let team: Team | null = null;
|
||||
|
||||
if (teamId) {
|
||||
team = await prisma.team.findFirst({
|
||||
where: {
|
||||
id: teamId,
|
||||
members: {
|
||||
some: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await tx.documentAuditLog.create({
|
||||
data: createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_UPDATED,
|
||||
@ -104,31 +139,5 @@ export const updateField = async ({
|
||||
return updatedField;
|
||||
});
|
||||
|
||||
const user = await prisma.user.findFirstOrThrow({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
});
|
||||
|
||||
let team: Team | null = null;
|
||||
|
||||
if (teamId) {
|
||||
team = await prisma.team.findFirst({
|
||||
where: {
|
||||
id: teamId,
|
||||
members: {
|
||||
some: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return field;
|
||||
};
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
import { headers } from 'next/headers';
|
||||
|
||||
export const getLocale = () => {
|
||||
const headerItems = headers();
|
||||
|
||||
const locales = headerItems.get('accept-language') ?? 'en-US';
|
||||
|
||||
const [locale] = locales.split(',');
|
||||
|
||||
return locale;
|
||||
};
|
||||
@ -4,5 +4,5 @@ import { cookies } from 'next/headers';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
export const switchI18NLanguage = async (lang: string) => {
|
||||
cookies().set('i18n', lang);
|
||||
cookies().set('language', lang);
|
||||
};
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||
import {
|
||||
DIRECT_TEMPLATE_RECIPIENT_EMAIL,
|
||||
DIRECT_TEMPLATE_RECIPIENT_NAME,
|
||||
} from '@documenso/lib/constants/direct-templates';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Recipient } from '@documenso/prisma/client';
|
||||
import { RecipientRole } from '@documenso/prisma/client';
|
||||
|
||||
import {
|
||||
DIRECT_TEMPLATE_RECIPIENT_EMAIL,
|
||||
DIRECT_TEMPLATE_RECIPIENT_NAME,
|
||||
} from '../../constants/template';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import {
|
||||
type TRecipientActionAuthTypes,
|
||||
|
||||
@ -1,9 +1,16 @@
|
||||
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { RecipientRole, Team } from '@documenso/prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||
import {
|
||||
type TRecipientActionAuthTypes,
|
||||
ZRecipientAuthOptionsSchema,
|
||||
} from '../../types/document-auth';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { createDocumentAuditLogData, diffRecipientChanges } from '../../utils/document-audit-logs';
|
||||
import { createRecipientAuthOptions } from '../../utils/document-auth';
|
||||
|
||||
export type UpdateRecipientOptions = {
|
||||
documentId: number;
|
||||
@ -11,6 +18,7 @@ export type UpdateRecipientOptions = {
|
||||
email?: string;
|
||||
name?: string;
|
||||
role?: RecipientRole;
|
||||
actionAuth?: TRecipientActionAuthTypes | null;
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
requestMetadata?: RequestMetadata;
|
||||
@ -22,6 +30,7 @@ export const updateRecipient = async ({
|
||||
email,
|
||||
name,
|
||||
role,
|
||||
actionAuth,
|
||||
userId,
|
||||
teamId,
|
||||
requestMetadata,
|
||||
@ -48,6 +57,9 @@ export const updateRecipient = async ({
|
||||
}),
|
||||
},
|
||||
},
|
||||
include: {
|
||||
Document: true,
|
||||
},
|
||||
});
|
||||
|
||||
let team: Team | null = null;
|
||||
@ -75,6 +87,22 @@ export const updateRecipient = async ({
|
||||
throw new Error('Recipient not found');
|
||||
}
|
||||
|
||||
if (actionAuth) {
|
||||
const isDocumentEnterprise = await isUserEnterprise({
|
||||
userId,
|
||||
teamId,
|
||||
});
|
||||
|
||||
if (!isDocumentEnterprise) {
|
||||
throw new AppError(
|
||||
AppErrorCode.UNAUTHORIZED,
|
||||
'You do not have permission to set the action auth',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const recipientAuthOptions = ZRecipientAuthOptionsSchema.parse(recipient.authOptions);
|
||||
|
||||
const updatedRecipient = await prisma.$transaction(async (tx) => {
|
||||
const persisted = await prisma.recipient.update({
|
||||
where: {
|
||||
@ -84,6 +112,10 @@ export const updateRecipient = async ({
|
||||
email: email?.toLowerCase() ?? recipient.email,
|
||||
name: name ?? recipient.name,
|
||||
role: role ?? recipient.role,
|
||||
authOptions: createRecipientAuthOptions({
|
||||
accessAuth: recipientAuthOptions.accessAuth,
|
||||
actionAuth: actionAuth ?? null,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
'use server';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { SubscriptionStatus } from '@documenso/prisma/client';
|
||||
|
||||
export type GetActiveSubscriptionsByUserIdOptions = {
|
||||
userId: number;
|
||||
};
|
||||
|
||||
export const getActiveSubscriptionsByUserId = async ({
|
||||
userId,
|
||||
}: GetActiveSubscriptionsByUserIdOptions) => {
|
||||
return await prisma.subscription.findMany({
|
||||
where: {
|
||||
userId,
|
||||
status: {
|
||||
not: SubscriptionStatus.INACTIVE,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -1,6 +1,7 @@
|
||||
import { updateSubscriptionItemQuantity } from '@documenso/ee/server-only/stripe/update-subscription-item-quantity';
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { TeamMemberInviteStatus } from '@documenso/prisma/client';
|
||||
|
||||
import { jobs } from '../../jobs/client';
|
||||
|
||||
@ -22,6 +23,9 @@ export const acceptTeamInvitation = async ({ userId, teamId }: AcceptTeamInvitat
|
||||
where: {
|
||||
teamId,
|
||||
email: user.email,
|
||||
status: {
|
||||
not: TeamMemberInviteStatus.DECLINED,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
team: {
|
||||
@ -37,6 +41,10 @@ export const acceptTeamInvitation = async ({ userId, teamId }: AcceptTeamInvitat
|
||||
},
|
||||
});
|
||||
|
||||
if (teamMemberInvite.status === TeamMemberInviteStatus.ACCEPTED) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { team } = teamMemberInvite;
|
||||
|
||||
const teamMember = await tx.teamMember.create({
|
||||
@ -47,10 +55,13 @@ export const acceptTeamInvitation = async ({ userId, teamId }: AcceptTeamInvitat
|
||||
},
|
||||
});
|
||||
|
||||
await tx.teamMemberInvite.delete({
|
||||
await tx.teamMemberInvite.update({
|
||||
where: {
|
||||
id: teamMemberInvite.id,
|
||||
},
|
||||
data: {
|
||||
status: TeamMemberInviteStatus.ACCEPTED,
|
||||
},
|
||||
});
|
||||
|
||||
if (IS_BILLING_ENABLED() && team.subscription) {
|
||||
|
||||
@ -28,11 +28,24 @@ export const transferTeamOwnership = async ({ token }: TransferTeamOwnershipOpti
|
||||
|
||||
const { team, userId: newOwnerUserId } = teamTransferVerification;
|
||||
|
||||
await tx.teamTransferVerification.delete({
|
||||
where: {
|
||||
teamId: team.id,
|
||||
},
|
||||
});
|
||||
await Promise.all([
|
||||
tx.teamTransferVerification.updateMany({
|
||||
where: {
|
||||
teamId: team.id,
|
||||
},
|
||||
data: {
|
||||
completed: true,
|
||||
},
|
||||
}),
|
||||
tx.teamTransferVerification.deleteMany({
|
||||
where: {
|
||||
teamId: team.id,
|
||||
expiresAt: {
|
||||
lt: new Date(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const newOwnerUser = await tx.user.findFirstOrThrow({
|
||||
where: {
|
||||
|
||||
@ -210,7 +210,7 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
|
||||
const initialRequestTime = new Date();
|
||||
|
||||
const { documentId, directRecipientToken } = await prisma.$transaction(async (tx) => {
|
||||
const { documentId, recipientId, token } = await prisma.$transaction(async (tx) => {
|
||||
const documentData = await tx.documentData.create({
|
||||
data: {
|
||||
type: template.templateDocumentData.type,
|
||||
@ -539,8 +539,9 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
});
|
||||
|
||||
return {
|
||||
token: createdDirectRecipient.token,
|
||||
documentId: document.id,
|
||||
directRecipientToken: createdDirectRecipient.token,
|
||||
recipientId: createdDirectRecipient.id,
|
||||
};
|
||||
});
|
||||
|
||||
@ -559,5 +560,9 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
// Log and reseal as required until we configure middleware.
|
||||
}
|
||||
|
||||
return directRecipientToken;
|
||||
return {
|
||||
token,
|
||||
documentId,
|
||||
recipientId,
|
||||
};
|
||||
};
|
||||
|
||||
@ -2,13 +2,13 @@
|
||||
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Recipient, TemplateDirectLink } from '@documenso/prisma/client';
|
||||
|
||||
import {
|
||||
DIRECT_TEMPLATE_RECIPIENT_EMAIL,
|
||||
DIRECT_TEMPLATE_RECIPIENT_NAME,
|
||||
} from '../../constants/template';
|
||||
} from '@documenso/lib/constants/direct-templates';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Recipient, TemplateDirectLink } from '@documenso/prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
|
||||
export type CreateTemplateDirectLinkOptions = {
|
||||
|
||||
@ -4,6 +4,13 @@ import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { jobsClient } from '../../jobs/client';
|
||||
|
||||
export const EMAIL_VERIFICATION_STATE = {
|
||||
NOT_FOUND: 'NOT_FOUND',
|
||||
VERIFIED: 'VERIFIED',
|
||||
EXPIRED: 'EXPIRED',
|
||||
ALREADY_VERIFIED: 'ALREADY_VERIFIED',
|
||||
} as const;
|
||||
|
||||
export type VerifyEmailProps = {
|
||||
token: string;
|
||||
};
|
||||
@ -19,7 +26,7 @@ export const verifyEmail = async ({ token }: VerifyEmailProps) => {
|
||||
});
|
||||
|
||||
if (!verificationToken) {
|
||||
return null;
|
||||
return EMAIL_VERIFICATION_STATE.NOT_FOUND;
|
||||
}
|
||||
|
||||
// check if the token is valid or expired
|
||||
@ -48,10 +55,14 @@ export const verifyEmail = async ({ token }: VerifyEmailProps) => {
|
||||
});
|
||||
}
|
||||
|
||||
return valid;
|
||||
return EMAIL_VERIFICATION_STATE.EXPIRED;
|
||||
}
|
||||
|
||||
const [updatedUser, deletedToken] = await prisma.$transaction([
|
||||
if (verificationToken.completed) {
|
||||
return EMAIL_VERIFICATION_STATE.ALREADY_VERIFIED;
|
||||
}
|
||||
|
||||
const [updatedUser] = await prisma.$transaction([
|
||||
prisma.user.update({
|
||||
where: {
|
||||
id: verificationToken.userId,
|
||||
@ -60,16 +71,28 @@ export const verifyEmail = async ({ token }: VerifyEmailProps) => {
|
||||
emailVerified: new Date(),
|
||||
},
|
||||
}),
|
||||
prisma.verificationToken.updateMany({
|
||||
where: {
|
||||
userId: verificationToken.userId,
|
||||
},
|
||||
data: {
|
||||
completed: true,
|
||||
},
|
||||
}),
|
||||
// Tidy up old expired tokens
|
||||
prisma.verificationToken.deleteMany({
|
||||
where: {
|
||||
userId: verificationToken.userId,
|
||||
expires: {
|
||||
lt: new Date(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
if (!updatedUser || !deletedToken) {
|
||||
if (!updatedUser) {
|
||||
throw new Error('Something went wrong while verifying your email. Please try again.');
|
||||
}
|
||||
|
||||
return !!updatedUser && !!deletedToken;
|
||||
return EMAIL_VERIFICATION_STATE.VERIFIED;
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { WebhookTriggerEvents } from '@documenso/prisma/client';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
|
||||
import { NEXT_PRIVATE_INTERNAL_WEBAPP_URL } from '../../../constants/app';
|
||||
import { sign } from '../../crypto/sign';
|
||||
import { getAllWebhooksByEventTrigger } from '../get-all-webhooks-by-event-trigger';
|
||||
|
||||
@ -29,7 +29,7 @@ export const triggerWebhook = async ({ event, data, userId, teamId }: TriggerWeb
|
||||
const signature = sign(body);
|
||||
|
||||
await Promise.race([
|
||||
fetch(`${NEXT_PUBLIC_WEBAPP_URL()}/api/webhook/trigger`, {
|
||||
fetch(`${NEXT_PRIVATE_INTERNAL_WEBAPP_URL}/api/webhook/trigger`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
|
||||
@ -8,7 +8,768 @@ msgstr ""
|
||||
"Language: de\n"
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"PO-Revision-Date: 2024-09-05 06:04\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Plural-Forms: \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: common.po\n"
|
||||
"X-Crowdin-File-ID: 4\n"
|
||||
|
||||
#: packages/ui/primitives/data-table-pagination.tsx:30
|
||||
msgid "{0} of {1} row(s) selected."
|
||||
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 "{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 "<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 "<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 "<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 "<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 "<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 "<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 "<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 "Dokument hinzufügen"
|
||||
|
||||
#: 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 "Fügen Sie eine URL hinzu, um den Benutzer nach der Unterzeichnung des Dokuments weiterzuleiten"
|
||||
|
||||
#: 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 "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 "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 "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 "Weiteren Wert hinzufügen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:455
|
||||
msgid "Add myself"
|
||||
msgstr "Mich selbst hinzufügen"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:452
|
||||
msgid "Add Myself"
|
||||
msgstr "Mich hinzufügen"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:438
|
||||
msgid "Add Placeholder Recipient"
|
||||
msgstr "Platzhalterempfänger hinzufügen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:444
|
||||
msgid "Add Signer"
|
||||
msgstr "Unterzeichner hinzufügen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:70
|
||||
msgid "Add text"
|
||||
msgstr "Text hinzufügen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:75
|
||||
msgid "Add text to the field"
|
||||
msgstr "Text zum Feld hinzufügen"
|
||||
|
||||
#: packages/lib/constants/teams.ts:10
|
||||
msgid "Admin"
|
||||
msgstr "Admin"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:199
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:238
|
||||
msgid "Advanced Options"
|
||||
msgstr "Erweiterte Optionen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:510
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:402
|
||||
msgid "Advanced settings"
|
||||
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 "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 "Genehmigen"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:9
|
||||
msgid "Approved"
|
||||
msgstr "Genehmigt"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:11
|
||||
msgid "Approver"
|
||||
msgstr "Genehmiger"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:10
|
||||
msgid "Approving"
|
||||
msgstr "Genehmigung"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:276
|
||||
msgid "Black"
|
||||
msgstr ""
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:290
|
||||
msgid "Blue"
|
||||
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 "Abbrechen"
|
||||
|
||||
#: 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 "Cc"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:14
|
||||
#: packages/lib/constants/recipient-roles.ts:16
|
||||
msgid "CC"
|
||||
msgstr "CC"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:15
|
||||
msgid "CC'd"
|
||||
msgstr "CC'd"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:83
|
||||
msgid "Character Limit"
|
||||
msgstr "Zeichenbeschränkung"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:932
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:788
|
||||
msgid "Checkbox"
|
||||
msgstr "Checkbox"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:195
|
||||
msgid "Checkbox values"
|
||||
msgstr "Checkbox-Werte"
|
||||
|
||||
#: packages/ui/primitives/data-table.tsx:156
|
||||
msgid "Clear filters"
|
||||
msgstr "Filter löschen"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:310
|
||||
msgid "Clear Signature"
|
||||
msgstr "Unterschrift löschen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signature.tsx:394
|
||||
msgid "Click to insert field"
|
||||
msgstr "Klicken, um das Feld einzufügen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx:44
|
||||
msgid "Close"
|
||||
msgstr "Schließen"
|
||||
|
||||
#: packages/lib/constants/template.ts:12
|
||||
msgid "Configure Direct Recipient"
|
||||
msgstr "Direkten Empfänger konfigurieren"
|
||||
|
||||
#: 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 "Konfigurieren Sie das Feld {0}"
|
||||
|
||||
#: packages/ui/primitives/document-flow/document-flow-root.tsx:141
|
||||
msgid "Continue"
|
||||
msgstr "Fortsetzen"
|
||||
|
||||
#: packages/ui/components/document/document-share-button.tsx:46
|
||||
msgid "Copied to clipboard"
|
||||
msgstr "In die Zwischenablage kopiert"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signature.tsx:360
|
||||
msgid "Custom Text"
|
||||
msgstr "Benutzerdefinierter Text"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:828
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:684
|
||||
msgid "Date"
|
||||
msgstr "Datum"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:240
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:279
|
||||
msgid "Date Format"
|
||||
msgstr "Datumsformat"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:395
|
||||
msgid "Direct link receiver"
|
||||
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:166
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:151
|
||||
msgid "Document access"
|
||||
msgstr "Dokumentenzugriff"
|
||||
|
||||
#: packages/lib/constants/template.ts:20
|
||||
msgid "Document Creation"
|
||||
msgstr "Dokumenterstellung"
|
||||
|
||||
#: packages/ui/components/document/document-download-button.tsx:68
|
||||
msgid "Download"
|
||||
msgstr "Herunterladen"
|
||||
|
||||
#: packages/ui/primitives/document-dropzone.tsx:162
|
||||
msgid "Drag & drop your PDF here."
|
||||
msgstr "Ziehen Sie Ihr PDF hierher."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:958
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:814
|
||||
msgid "Dropdown"
|
||||
msgstr "Dropdown"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:148
|
||||
msgid "Dropdown options"
|
||||
msgstr "Dropdown-Optionen"
|
||||
|
||||
#: 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:326
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:333
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:632
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:298
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:305
|
||||
msgid "Email"
|
||||
msgstr "E-Mail"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:184
|
||||
msgid "Email Options"
|
||||
msgstr "E-Mail-Optionen"
|
||||
|
||||
#: packages/lib/constants/template.ts:8
|
||||
msgid "Enable Direct Link Signing"
|
||||
msgstr "Direktlink-Signierung aktivieren"
|
||||
|
||||
#: packages/ui/primitives/document-password-dialog.tsx:84
|
||||
msgid "Enter password"
|
||||
msgstr "Passwort eingeben"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:216
|
||||
msgid "Error"
|
||||
msgstr "Fehler"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:210
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:249
|
||||
msgid "External ID"
|
||||
msgstr "Externe ID"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:217
|
||||
msgid "Failed to save settings."
|
||||
msgstr "Einstellungen konnten nicht gespeichert werden."
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:90
|
||||
msgid "Field character limit"
|
||||
msgstr "Zeichenbeschränkung des Feldes"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:107
|
||||
msgid "Field format"
|
||||
msgstr "Feldformat"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:50
|
||||
msgid "Field label"
|
||||
msgstr "Feldbeschriftung"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:62
|
||||
msgid "Field placeholder"
|
||||
msgstr "Feldplatzhalter"
|
||||
|
||||
#: packages/ui/components/document/document-global-auth-action-select.tsx:64
|
||||
msgid "Global recipient action authentication"
|
||||
msgstr "Globale Empfängerauthentifizierung"
|
||||
|
||||
#: packages/ui/primitives/document-flow/document-flow-root.tsx:142
|
||||
msgid "Go Back"
|
||||
msgstr "Zurück"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:297
|
||||
msgid "Green"
|
||||
msgstr ""
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:72
|
||||
msgid "I am a signer of this document"
|
||||
msgstr "Ich bin ein Unterzeichner dieses Dokuments"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:75
|
||||
msgid "I am a viewer of this document"
|
||||
msgstr "Ich bin ein Betrachter dieses Dokuments"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:73
|
||||
msgid "I am an approver of this document"
|
||||
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 "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 "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 "Beschriftung"
|
||||
|
||||
#: packages/lib/constants/teams.ts:11
|
||||
msgid "Manager"
|
||||
msgstr "Manager"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:168
|
||||
msgid "Max"
|
||||
msgstr "Max"
|
||||
|
||||
#: packages/lib/constants/teams.ts:12
|
||||
msgid "Member"
|
||||
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 "Nachricht <0>(Optional)</0>"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:156
|
||||
msgid "Min"
|
||||
msgstr "Min"
|
||||
|
||||
#: 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:360
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:658
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:329
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:335
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-role-select.tsx:52
|
||||
msgid "Needs to approve"
|
||||
msgstr "Muss genehmigen"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-role-select.tsx:31
|
||||
msgid "Needs to sign"
|
||||
msgstr "Muss unterzeichnen"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-role-select.tsx:73
|
||||
msgid "Needs to view"
|
||||
msgstr "Muss sehen"
|
||||
|
||||
#: 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 "Kein passender Empfänger mit dieser Beschreibung gefunden."
|
||||
|
||||
#: 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 "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 "Keine Einschränkungen"
|
||||
|
||||
#: packages/ui/primitives/data-table.tsx:148
|
||||
msgid "No results found"
|
||||
msgstr "Keine Ergebnisse gefunden"
|
||||
|
||||
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx:30
|
||||
msgid "No signature field found"
|
||||
msgstr "Kein Unterschriftsfeld gefunden"
|
||||
|
||||
#: packages/ui/primitives/combobox.tsx:60
|
||||
#: packages/ui/primitives/multi-select-combobox.tsx:153
|
||||
msgid "No value found."
|
||||
msgstr "Kein Wert gefunden."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:880
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:736
|
||||
msgid "Number"
|
||||
msgstr "Nummer"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:100
|
||||
msgid "Number format"
|
||||
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 "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 "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 "Seite {0} von {1}"
|
||||
|
||||
#: packages/ui/primitives/document-password-dialog.tsx:62
|
||||
msgid "Password Required"
|
||||
msgstr "Passwort erforderlich"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:154
|
||||
msgid "Pick a number"
|
||||
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 "Platzhalter"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:906
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:762
|
||||
msgid "Radio"
|
||||
msgstr "Radio"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/radio-field.tsx:133
|
||||
msgid "Radio values"
|
||||
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
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:136
|
||||
#: 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 "Nur lesen"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-role-select.tsx:95
|
||||
msgid "Receives copy"
|
||||
msgstr "Erhält Kopie"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:39
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:184
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:169
|
||||
msgid "Recipient action authentication"
|
||||
msgstr "Empfängeraktion Authentifizierung"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:283
|
||||
msgid "Red"
|
||||
msgstr ""
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:298
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:332
|
||||
msgid "Redirect URL"
|
||||
msgstr "Weiterleitungs-URL"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:996
|
||||
msgid "Remove"
|
||||
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
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:126
|
||||
#: 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 "Pflichtfeld"
|
||||
|
||||
#: packages/ui/primitives/data-table-pagination.tsx:55
|
||||
msgid "Rows per page"
|
||||
msgstr "Zeilen pro Seite"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:286
|
||||
msgid "Save"
|
||||
msgstr "Speichern"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:848
|
||||
msgid "Save Template"
|
||||
msgstr "Vorlage speichern"
|
||||
|
||||
#: packages/ui/components/common/language-switcher-dialog.tsx:34
|
||||
msgid "Search languages..."
|
||||
msgstr "Sprachen suchen..."
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:105
|
||||
msgid "Select"
|
||||
msgstr "Auswählen"
|
||||
|
||||
#: packages/ui/primitives/combobox.tsx:38
|
||||
msgid "Select an option"
|
||||
msgstr "Option auswählen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:137
|
||||
msgid "Select at least"
|
||||
msgstr "Wählen Sie mindestens"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:95
|
||||
msgid "Select default option"
|
||||
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 "Senden"
|
||||
|
||||
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:41
|
||||
msgid "Send Document"
|
||||
msgstr "Dokument senden"
|
||||
|
||||
#: packages/ui/components/document/document-share-button.tsx:135
|
||||
msgid "Share Signature Card"
|
||||
msgstr "Unterschriftenkarte teilen"
|
||||
|
||||
#: packages/lib/constants/template.ts:16
|
||||
msgid "Share the Link"
|
||||
msgstr "Link teilen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:473
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:470
|
||||
msgid "Show advanced settings"
|
||||
msgstr "Erweiterte Einstellungen anzeigen"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:20
|
||||
msgid "Sign"
|
||||
msgstr "Unterschreiben"
|
||||
|
||||
#: 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 "Unterschrift"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:21
|
||||
msgid "Signed"
|
||||
msgstr "Unterzeichnet"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:23
|
||||
msgid "Signer"
|
||||
msgstr "Unterzeichner"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:22
|
||||
msgid "Signing"
|
||||
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 "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 "Etwas ist schief gelaufen"
|
||||
|
||||
#: packages/ui/primitives/data-table.tsx:136
|
||||
msgid "Something went wrong."
|
||||
msgstr "Etwas ist schief gelaufen."
|
||||
|
||||
#: packages/ui/primitives/document-flow/document-flow-root.tsx:107
|
||||
msgid "Step <0>{step} of {maxStep}</0>"
|
||||
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 "Betreff <0>(Optional)</0>"
|
||||
|
||||
#: packages/ui/primitives/document-password-dialog.tsx:97
|
||||
msgid "Submit"
|
||||
msgstr "Einreichen"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:134
|
||||
msgid "Template title"
|
||||
msgstr "Vorlagentitel"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:854
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:710
|
||||
msgid "Text"
|
||||
msgstr "Text"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:44
|
||||
msgid "The authentication required for recipients to sign fields"
|
||||
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 "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 "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 "Der Name des Dokuments"
|
||||
|
||||
#: packages/ui/primitives/document-password-dialog.tsx:52
|
||||
msgid "The password you have entered is incorrect. Please try again."
|
||||
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 "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 "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 "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 "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 "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 "Der Freigabelink wurde in Ihre Zwischenablage kopiert."
|
||||
|
||||
#: packages/ui/components/document/document-send-email-message-helper.tsx:25
|
||||
msgid "The signer's email"
|
||||
msgstr "Die E-Mail des Unterzeichners"
|
||||
|
||||
#: packages/ui/components/document/document-send-email-message-helper.tsx:19
|
||||
msgid "The signer's name"
|
||||
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 "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:685
|
||||
msgid "This document has already been sent to this recipient. You can no longer edit this recipient."
|
||||
msgstr "Dieses Dokument wurde bereits an diesen Empfänger gesendet. Sie können diesen Empfänger nicht mehr bearbeiten."
|
||||
|
||||
#: packages/ui/primitives/document-password-dialog.tsx:66
|
||||
msgid "This document is password protected. Please enter the password to view the document."
|
||||
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:398
|
||||
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 "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: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 "Dies überschreibt alle globalen Einstellungen."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:274
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:309
|
||||
msgid "Time Zone"
|
||||
msgstr "Zeitzone"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx:145
|
||||
msgid "Title"
|
||||
msgstr "Titel"
|
||||
|
||||
#: 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 "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 "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 "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 "Upgrade"
|
||||
|
||||
#: packages/ui/primitives/document-dropzone.tsx:70
|
||||
msgid "Upload Template Document"
|
||||
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 "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 "Wert"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:26
|
||||
msgid "View"
|
||||
msgstr "View"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:27
|
||||
msgid "Viewed"
|
||||
msgstr "Viewed"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:29
|
||||
msgid "Viewer"
|
||||
msgstr "Viewer"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:28
|
||||
msgid "Viewing"
|
||||
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 "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 "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 "Sie können derzeit keine Dokumente hochladen."
|
||||
|
||||
#: packages/ui/primitives/document-dropzone.tsx:69
|
||||
msgid "You have reached your document limit."
|
||||
msgstr "Sie haben Ihr Dokumentenlimit erreicht."
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: de\n"
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"PO-Revision-Date: 2024-09-05 06:04\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Plural-Forms: \n"
|
||||
@ -29,6 +29,10 @@ msgstr ""
|
||||
msgid "A 10x better signing experience."
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/singleplayer/client.tsx:51
|
||||
msgid "Add document"
|
||||
msgstr "Dokument hinzufügen"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:201
|
||||
msgid "Add More Users for {0}"
|
||||
msgstr ""
|
||||
@ -164,6 +168,10 @@ msgstr ""
|
||||
msgid "Engagement"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/singleplayer/client.tsx:64
|
||||
msgid "Enter your details."
|
||||
msgstr "Geben Sie Ihre Details ein."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/enterprise.tsx:16
|
||||
msgid "Enterprise Compliance, License or Technical Needs?"
|
||||
msgstr ""
|
||||
@ -408,9 +416,8 @@ msgstr ""
|
||||
msgid "Save $60 or $120"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/i18n-switcher.tsx:47
|
||||
msgid "Search languages..."
|
||||
msgstr ""
|
||||
#~ msgid "Search languages..."
|
||||
#~ 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."
|
||||
@ -428,6 +435,10 @@ msgstr ""
|
||||
msgid "Shop"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/singleplayer/client.tsx:63
|
||||
msgid "Sign"
|
||||
msgstr "Signieren"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/header.tsx:72
|
||||
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:61
|
||||
msgid "Sign in"
|
||||
@ -541,6 +552,10 @@ msgstr ""
|
||||
msgid "Up to 10 recipients per document"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/singleplayer/client.tsx:52
|
||||
msgid "Upload a document and add fields."
|
||||
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 ""
|
||||
@ -588,3 +603,7 @@ msgstr ""
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:265
|
||||
msgid "Your browser does not support the video tag."
|
||||
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
@ -12,3 +12,759 @@ msgstr ""
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#: packages/ui/primitives/data-table-pagination.tsx:30
|
||||
msgid "{0} of {1} row(s) selected."
|
||||
msgstr "{0} of {1} row(s) selected."
|
||||
|
||||
#: packages/ui/primitives/data-table-pagination.tsx:41
|
||||
msgid "{visibleRows, plural, one {Showing # result.} other {Showing # results.}}"
|
||||
msgstr "{visibleRows, plural, one {Showing # result.} other {Showing # results.}}"
|
||||
|
||||
#: 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 "<0>Inherit authentication method</0> - Use the global action signing authentication method configured in the \"General Settings\" step"
|
||||
|
||||
#: packages/ui/components/document/document-global-auth-action-select.tsx:95
|
||||
msgid "<0>No restrictions</0> - No authentication required"
|
||||
msgstr "<0>No restrictions</0> - No authentication required"
|
||||
|
||||
#: 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 "<0>No restrictions</0> - The document can be accessed directly by the URL sent to the recipient"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:75
|
||||
msgid "<0>None</0> - No authentication required"
|
||||
msgstr "<0>None</0> - No authentication required"
|
||||
|
||||
#: 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 "<0>Require 2FA</0> - The recipient must have an account and 2FA enabled via their settings"
|
||||
|
||||
#: 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 "<0>Require account</0> - The recipient must be signed in to view the document"
|
||||
|
||||
#: 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 "<0>Require passkey</0> - The recipient must have an account and passkey configured via their settings"
|
||||
|
||||
#: packages/ui/primitives/document-dropzone.tsx:69
|
||||
msgid "Add a document"
|
||||
msgstr "Add a document"
|
||||
|
||||
#: 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: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."
|
||||
|
||||
#: 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 "Add an external ID to the template. This can be used to identify in external systems."
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:177
|
||||
msgid "Add another option"
|
||||
msgstr "Add another option"
|
||||
|
||||
#: 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 "Add another value"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:455
|
||||
msgid "Add myself"
|
||||
msgstr "Add myself"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:452
|
||||
msgid "Add Myself"
|
||||
msgstr "Add Myself"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:438
|
||||
msgid "Add Placeholder Recipient"
|
||||
msgstr "Add Placeholder Recipient"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:444
|
||||
msgid "Add Signer"
|
||||
msgstr "Add Signer"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:70
|
||||
msgid "Add text"
|
||||
msgstr "Add text"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:75
|
||||
msgid "Add text to the field"
|
||||
msgstr "Add text to the field"
|
||||
|
||||
#: packages/lib/constants/teams.ts:10
|
||||
msgid "Admin"
|
||||
msgstr "Admin"
|
||||
|
||||
#: 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:510
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:402
|
||||
msgid "Advanced settings"
|
||||
msgstr "Advanced settings"
|
||||
|
||||
#: 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 "After submission, a document will be automatically generated and added to your documents page. You will also receive a notification via email."
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:8
|
||||
msgid "Approve"
|
||||
msgstr "Approve"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:9
|
||||
msgid "Approved"
|
||||
msgstr "Approved"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:11
|
||||
msgid "Approver"
|
||||
msgstr "Approver"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:10
|
||||
msgid "Approving"
|
||||
msgstr "Approving"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:276
|
||||
msgid "Black"
|
||||
msgstr "Black"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:290
|
||||
msgid "Blue"
|
||||
msgstr "Blue"
|
||||
|
||||
#: 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 "Cancel"
|
||||
|
||||
#: 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"
|
||||
msgstr "Cc"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:14
|
||||
#: packages/lib/constants/recipient-roles.ts:16
|
||||
msgid "CC"
|
||||
msgstr "CC"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:15
|
||||
msgid "CC'd"
|
||||
msgstr "CC'd"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:83
|
||||
msgid "Character Limit"
|
||||
msgstr "Character Limit"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:932
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:788
|
||||
msgid "Checkbox"
|
||||
msgstr "Checkbox"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:195
|
||||
msgid "Checkbox values"
|
||||
msgstr "Checkbox values"
|
||||
|
||||
#: packages/ui/primitives/data-table.tsx:156
|
||||
msgid "Clear filters"
|
||||
msgstr "Clear filters"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:310
|
||||
msgid "Clear Signature"
|
||||
msgstr "Clear Signature"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signature.tsx:394
|
||||
msgid "Click to insert field"
|
||||
msgstr "Click to insert field"
|
||||
|
||||
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx:44
|
||||
msgid "Close"
|
||||
msgstr "Close"
|
||||
|
||||
#: packages/lib/constants/template.ts:12
|
||||
msgid "Configure Direct Recipient"
|
||||
msgstr "Configure Direct Recipient"
|
||||
|
||||
#: 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"
|
||||
|
||||
#: packages/ui/primitives/document-flow/document-flow-root.tsx:141
|
||||
msgid "Continue"
|
||||
msgstr "Continue"
|
||||
|
||||
#: packages/ui/components/document/document-share-button.tsx:46
|
||||
msgid "Copied to clipboard"
|
||||
msgstr "Copied to clipboard"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signature.tsx:360
|
||||
msgid "Custom Text"
|
||||
msgstr "Custom Text"
|
||||
|
||||
#: 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: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:395
|
||||
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:166
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:151
|
||||
msgid "Document access"
|
||||
msgstr "Document access"
|
||||
|
||||
#: packages/lib/constants/template.ts:20
|
||||
msgid "Document Creation"
|
||||
msgstr "Document Creation"
|
||||
|
||||
#: packages/ui/components/document/document-download-button.tsx:68
|
||||
msgid "Download"
|
||||
msgstr "Download"
|
||||
|
||||
#: packages/ui/primitives/document-dropzone.tsx:162
|
||||
msgid "Drag & drop your PDF here."
|
||||
msgstr "Drag & drop your PDF here."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:958
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:814
|
||||
msgid "Dropdown"
|
||||
msgstr "Dropdown"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:148
|
||||
msgid "Dropdown options"
|
||||
msgstr "Dropdown options"
|
||||
|
||||
#: 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:326
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:333
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:632
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:298
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:305
|
||||
msgid "Email"
|
||||
msgstr "Email"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:184
|
||||
msgid "Email Options"
|
||||
msgstr "Email Options"
|
||||
|
||||
#: packages/lib/constants/template.ts:8
|
||||
msgid "Enable Direct Link Signing"
|
||||
msgstr "Enable Direct Link Signing"
|
||||
|
||||
#: packages/ui/primitives/document-password-dialog.tsx:84
|
||||
msgid "Enter password"
|
||||
msgstr "Enter password"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:216
|
||||
msgid "Error"
|
||||
msgstr "Error"
|
||||
|
||||
#: 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"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:217
|
||||
msgid "Failed to save settings."
|
||||
msgstr "Failed to save settings."
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:90
|
||||
msgid "Field character limit"
|
||||
msgstr "Field character limit"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:107
|
||||
msgid "Field format"
|
||||
msgstr "Field format"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:50
|
||||
msgid "Field label"
|
||||
msgstr "Field label"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:62
|
||||
msgid "Field placeholder"
|
||||
msgstr "Field placeholder"
|
||||
|
||||
#: packages/ui/components/document/document-global-auth-action-select.tsx:64
|
||||
msgid "Global recipient action authentication"
|
||||
msgstr "Global recipient action authentication"
|
||||
|
||||
#: packages/ui/primitives/document-flow/document-flow-root.tsx:142
|
||||
msgid "Go Back"
|
||||
msgstr "Go Back"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:297
|
||||
msgid "Green"
|
||||
msgstr "Green"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:72
|
||||
msgid "I am a signer of this document"
|
||||
msgstr "I am a signer of this document"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:75
|
||||
msgid "I am a viewer of this document"
|
||||
msgstr "I am a viewer of this document"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:73
|
||||
msgid "I am an approver of this document"
|
||||
msgstr "I am an approver of this document"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:74
|
||||
msgid "I am required to receive a copy of this document"
|
||||
msgstr "I am required to receive a copy of this document"
|
||||
|
||||
#: 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 "Inherit authentication method"
|
||||
|
||||
#: 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 "Label"
|
||||
|
||||
#: packages/lib/constants/teams.ts:11
|
||||
msgid "Manager"
|
||||
msgstr "Manager"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:168
|
||||
msgid "Max"
|
||||
msgstr "Max"
|
||||
|
||||
#: packages/lib/constants/teams.ts:12
|
||||
msgid "Member"
|
||||
msgstr "Member"
|
||||
|
||||
#: 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 "Message <0>(Optional)</0>"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:156
|
||||
msgid "Min"
|
||||
msgstr "Min"
|
||||
|
||||
#: 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:360
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:658
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:329
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:335
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-role-select.tsx:52
|
||||
msgid "Needs to approve"
|
||||
msgstr "Needs to approve"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-role-select.tsx:31
|
||||
msgid "Needs to sign"
|
||||
msgstr "Needs to sign"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-role-select.tsx:73
|
||||
msgid "Needs to view"
|
||||
msgstr "Needs to view"
|
||||
|
||||
#: 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:629
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:513
|
||||
msgid "No recipients with this role"
|
||||
msgstr "No recipients with this role"
|
||||
|
||||
#: 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 "No restrictions"
|
||||
|
||||
#: packages/ui/primitives/data-table.tsx:148
|
||||
msgid "No results found"
|
||||
msgstr "No results found"
|
||||
|
||||
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx:30
|
||||
msgid "No signature field found"
|
||||
msgstr "No signature field found"
|
||||
|
||||
#: packages/ui/primitives/combobox.tsx:60
|
||||
#: packages/ui/primitives/multi-select-combobox.tsx:153
|
||||
msgid "No value found."
|
||||
msgstr "No value found."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:880
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:736
|
||||
msgid "Number"
|
||||
msgstr "Number"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:100
|
||||
msgid "Number format"
|
||||
msgstr "Number format"
|
||||
|
||||
#: 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 "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."
|
||||
|
||||
#: 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 "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."
|
||||
|
||||
#: packages/ui/primitives/data-table-pagination.tsx:77
|
||||
msgid "Page {0} of {1}"
|
||||
msgstr "Page {0} of {1}"
|
||||
|
||||
#: packages/ui/primitives/document-password-dialog.tsx:62
|
||||
msgid "Password Required"
|
||||
msgstr "Password Required"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:154
|
||||
msgid "Pick a number"
|
||||
msgstr "Pick a number"
|
||||
|
||||
#: 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 "Placeholder"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:906
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:762
|
||||
msgid "Radio"
|
||||
msgstr "Radio"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/radio-field.tsx:133
|
||||
msgid "Radio values"
|
||||
msgstr "Radio values"
|
||||
|
||||
#: 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
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:136
|
||||
#: 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 "Read only"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-role-select.tsx:95
|
||||
msgid "Receives copy"
|
||||
msgstr "Receives copy"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:39
|
||||
#: 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"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:283
|
||||
msgid "Red"
|
||||
msgstr "Red"
|
||||
|
||||
#: 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:996
|
||||
msgid "Remove"
|
||||
msgstr "Remove"
|
||||
|
||||
#: 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
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:126
|
||||
#: 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 "Required field"
|
||||
|
||||
#: packages/ui/primitives/data-table-pagination.tsx:55
|
||||
msgid "Rows per page"
|
||||
msgstr "Rows per page"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:286
|
||||
msgid "Save"
|
||||
msgstr "Save"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:848
|
||||
msgid "Save Template"
|
||||
msgstr "Save Template"
|
||||
|
||||
#: packages/ui/components/common/language-switcher-dialog.tsx:34
|
||||
msgid "Search languages..."
|
||||
msgstr "Search languages..."
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:105
|
||||
msgid "Select"
|
||||
msgstr "Select"
|
||||
|
||||
#: packages/ui/primitives/combobox.tsx:38
|
||||
msgid "Select an option"
|
||||
msgstr "Select an option"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:137
|
||||
msgid "Select at least"
|
||||
msgstr "Select at least"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:95
|
||||
msgid "Select default option"
|
||||
msgstr "Select default option"
|
||||
|
||||
#: 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 "Send"
|
||||
|
||||
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:41
|
||||
msgid "Send Document"
|
||||
msgstr "Send Document"
|
||||
|
||||
#: packages/ui/components/document/document-share-button.tsx:135
|
||||
msgid "Share Signature Card"
|
||||
msgstr "Share Signature Card"
|
||||
|
||||
#: packages/lib/constants/template.ts:16
|
||||
msgid "Share the Link"
|
||||
msgstr "Share the Link"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx:473
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:470
|
||||
msgid "Show advanced settings"
|
||||
msgstr "Show advanced settings"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:20
|
||||
msgid "Sign"
|
||||
msgstr "Sign"
|
||||
|
||||
#: 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 "Signature"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:21
|
||||
msgid "Signed"
|
||||
msgstr "Signed"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:23
|
||||
msgid "Signer"
|
||||
msgstr "Signer"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:22
|
||||
msgid "Signing"
|
||||
msgstr "Signing"
|
||||
|
||||
#: 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 "Some signers have not been assigned a signature field. Please assign at least 1 signature field to each signer before proceeding."
|
||||
|
||||
#: packages/ui/components/document/document-share-button.tsx:51
|
||||
msgid "Something went wrong"
|
||||
msgstr "Something went wrong"
|
||||
|
||||
#: packages/ui/primitives/data-table.tsx:136
|
||||
msgid "Something went wrong."
|
||||
msgstr "Something went wrong."
|
||||
|
||||
#: packages/ui/primitives/document-flow/document-flow-root.tsx:107
|
||||
msgid "Step <0>{step} of {maxStep}</0>"
|
||||
msgstr "Step <0>{step} of {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 "Subject <0>(Optional)</0>"
|
||||
|
||||
#: packages/ui/primitives/document-password-dialog.tsx:97
|
||||
msgid "Submit"
|
||||
msgstr "Submit"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx:134
|
||||
msgid "Template title"
|
||||
msgstr "Template title"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx:854
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:710
|
||||
msgid "Text"
|
||||
msgstr "Text"
|
||||
|
||||
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:44
|
||||
msgid "The authentication required for recipients to sign fields"
|
||||
msgstr "The authentication required for recipients to sign fields"
|
||||
|
||||
#: packages/ui/components/document/document-global-auth-action-select.tsx:68
|
||||
msgid "The authentication required for recipients to sign the signature field."
|
||||
msgstr "The authentication required for recipients to sign the signature field."
|
||||
|
||||
#: packages/ui/components/document/document-global-auth-access-select.tsx:67
|
||||
msgid "The authentication required for recipients to view the document."
|
||||
msgstr "The authentication required for recipients to view the document."
|
||||
|
||||
#: packages/ui/components/document/document-send-email-message-helper.tsx:31
|
||||
msgid "The document's name"
|
||||
msgstr "The document's name"
|
||||
|
||||
#: packages/ui/primitives/document-password-dialog.tsx:52
|
||||
msgid "The password you have entered is incorrect. Please try again."
|
||||
msgstr "The password you have entered is incorrect. Please try again."
|
||||
|
||||
#: 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 "The recipient is not required to take any action and receives a copy of the document after it is completed."
|
||||
|
||||
#: packages/ui/components/recipient/recipient-role-select.tsx:60
|
||||
msgid "The recipient is required to approve the document for it to be completed."
|
||||
msgstr "The recipient is required to approve the document for it to be completed."
|
||||
|
||||
#: packages/ui/components/recipient/recipient-role-select.tsx:39
|
||||
msgid "The recipient is required to sign the document for it to be completed."
|
||||
msgstr "The recipient is required to sign the document for it to be completed."
|
||||
|
||||
#: packages/ui/components/recipient/recipient-role-select.tsx:81
|
||||
msgid "The recipient is required to view the document for it to be completed."
|
||||
msgstr "The recipient is required to view the document for it to be completed."
|
||||
|
||||
#: packages/ui/components/document/document-share-button.tsx:52
|
||||
msgid "The sharing link could not be created at this time. Please try again."
|
||||
msgstr "The sharing link could not be created at this time. Please try again."
|
||||
|
||||
#: packages/ui/components/document/document-share-button.tsx:47
|
||||
msgid "The sharing link has been copied to your clipboard."
|
||||
msgstr "The sharing link has been copied to your clipboard."
|
||||
|
||||
#: packages/ui/components/document/document-send-email-message-helper.tsx:25
|
||||
msgid "The signer's email"
|
||||
msgstr "The signer's email"
|
||||
|
||||
#: packages/ui/components/document/document-send-email-message-helper.tsx:19
|
||||
msgid "The signer's name"
|
||||
msgstr "The signer's name"
|
||||
|
||||
#: 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 "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: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."
|
||||
|
||||
#: packages/ui/primitives/document-password-dialog.tsx:66
|
||||
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:398
|
||||
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: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: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:145
|
||||
msgid "Title"
|
||||
msgstr "Title"
|
||||
|
||||
#: 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."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx:124
|
||||
msgid "Update"
|
||||
msgstr "Update"
|
||||
|
||||
#: 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 "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."
|
||||
|
||||
#: packages/ui/primitives/document-dropzone.tsx:168
|
||||
msgid "Upgrade"
|
||||
msgstr "Upgrade"
|
||||
|
||||
#: packages/ui/primitives/document-dropzone.tsx:70
|
||||
msgid "Upload Template Document"
|
||||
msgstr "Upload Template Document"
|
||||
|
||||
#: 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 "Validation"
|
||||
|
||||
#: 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 "Value"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:26
|
||||
msgid "View"
|
||||
msgstr "View"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:27
|
||||
msgid "Viewed"
|
||||
msgstr "Viewed"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:29
|
||||
msgid "Viewer"
|
||||
msgstr "Viewer"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts:28
|
||||
msgid "Viewing"
|
||||
msgstr "Viewing"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad.tsx:280
|
||||
#~ msgid "White"
|
||||
#~ msgstr "White"
|
||||
|
||||
#: 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 "You are about to send this document to the recipients. Are you sure you want to continue?"
|
||||
|
||||
#: packages/ui/components/document/document-send-email-message-helper.tsx:11
|
||||
msgid "You can use the following variables in your message:"
|
||||
msgstr "You can use the following variables in your message:"
|
||||
|
||||
#: packages/ui/primitives/document-dropzone.tsx:43
|
||||
msgid "You cannot upload documents at this time."
|
||||
msgstr "You cannot upload documents at this time."
|
||||
|
||||
#: packages/ui/primitives/document-dropzone.tsx:69
|
||||
msgid "You have reached your document limit."
|
||||
msgstr "You have reached your document limit."
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -29,6 +29,10 @@ msgstr "5 Users Included"
|
||||
msgid "A 10x better signing experience."
|
||||
msgstr "A 10x better signing experience."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/singleplayer/client.tsx:51
|
||||
msgid "Add document"
|
||||
msgstr "Add document"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:201
|
||||
msgid "Add More Users for {0}"
|
||||
msgstr "Add More Users for {0}"
|
||||
@ -168,6 +172,10 @@ msgstr "Email and Discord Support"
|
||||
msgid "Engagement"
|
||||
msgstr "Engagement"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/singleplayer/client.tsx:64
|
||||
msgid "Enter your details."
|
||||
msgstr "Enter your details."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/enterprise.tsx:16
|
||||
msgid "Enterprise Compliance, License or Technical Needs?"
|
||||
msgstr "Enterprise Compliance, License or Technical Needs?"
|
||||
@ -417,8 +425,8 @@ msgid "Save $60 or $120"
|
||||
msgstr "Save $60 or $120"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/i18n-switcher.tsx:47
|
||||
msgid "Search languages..."
|
||||
msgstr "Search languages..."
|
||||
#~ msgid "Search languages..."
|
||||
#~ msgstr "Search languages..."
|
||||
|
||||
#: 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."
|
||||
@ -436,6 +444,10 @@ msgstr "Seniority"
|
||||
msgid "Shop"
|
||||
msgstr "Shop"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/singleplayer/client.tsx:63
|
||||
msgid "Sign"
|
||||
msgstr "Sign"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/header.tsx:72
|
||||
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:61
|
||||
msgid "Sign in"
|
||||
@ -549,6 +561,10 @@ msgstr "Unlimited Documents per Month"
|
||||
msgid "Up to 10 recipients per document"
|
||||
msgstr "Up to 10 recipients per document"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/singleplayer/client.tsx:52
|
||||
msgid "Upload a document and add fields."
|
||||
msgstr "Upload a document and add fields."
|
||||
|
||||
#: 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 "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."
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@ import type {
|
||||
RecipientRole,
|
||||
} from '@documenso/prisma/client';
|
||||
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '../constants/recipient-roles';
|
||||
import { RECIPIENT_ROLES_DESCRIPTION_ENG } from '../constants/recipient-roles';
|
||||
import type {
|
||||
TDocumentAuditLog,
|
||||
TDocumentAuditLogDocumentMetaDiffSchema,
|
||||
@ -268,6 +268,7 @@ export const formatDocumentAuditLogActionString = (
|
||||
*
|
||||
* Provide a userId to prefix the action with the user, example 'X did Y'.
|
||||
*/
|
||||
// Todo: Translations.
|
||||
export const formatDocumentAuditLogAction = (auditLog: TDocumentAuditLog, userId?: number) => {
|
||||
let prefix = userId === auditLog.userId ? 'You' : auditLog.name || auditLog.email || '';
|
||||
|
||||
@ -346,7 +347,7 @@ export const formatDocumentAuditLogAction = (auditLog: TDocumentAuditLog, userId
|
||||
}))
|
||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED }, ({ data }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const action = RECIPIENT_ROLES_DESCRIPTION[data.recipientRole as RecipientRole]?.actioned;
|
||||
const action = RECIPIENT_ROLES_DESCRIPTION_ENG[data.recipientRole as RecipientRole]?.actioned;
|
||||
|
||||
const value = action ? `${action.toLowerCase()} the document` : 'completed their task';
|
||||
|
||||
|
||||
@ -2,8 +2,8 @@ import type { ReadonlyRequestCookies } from 'next/dist/server/web/spec-extension
|
||||
|
||||
import type { I18n } from '@lingui/core';
|
||||
|
||||
import { IS_APP_WEB } from '../constants/app';
|
||||
import type { SupportedLanguageCodes } from '../constants/i18n';
|
||||
import { IS_APP_WEB, IS_APP_WEB_I18N_ENABLED } from '../constants/app';
|
||||
import type { I18nLocaleData, SupportedLanguageCodes } from '../constants/i18n';
|
||||
import { APP_I18N_OPTIONS } from '../constants/i18n';
|
||||
|
||||
export async function dynamicActivate(i18nInstance: I18n, locale: string) {
|
||||
@ -14,48 +14,58 @@ export async function dynamicActivate(i18nInstance: I18n, locale: string) {
|
||||
i18nInstance.loadAndActivate({ locale, messages });
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the language if supported from the cookies header.
|
||||
*
|
||||
* Returns `null` if not supported or not found.
|
||||
*/
|
||||
export const extractSupportedLanguageFromCookies = (
|
||||
cookies: ReadonlyRequestCookies,
|
||||
): SupportedLanguageCodes | null => {
|
||||
const preferredLanguage = cookies.get('i18n');
|
||||
|
||||
const foundSupportedLanguage = APP_I18N_OPTIONS.supportedLangs.find(
|
||||
(lang): lang is SupportedLanguageCodes => lang === preferredLanguage?.value,
|
||||
);
|
||||
|
||||
return foundSupportedLanguage || null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts the language from the `accept-language` header.
|
||||
*
|
||||
* Returns `null` if not supported or not found.
|
||||
*/
|
||||
export const extractSupportedLanguageFromHeaders = (
|
||||
headers: Headers,
|
||||
): SupportedLanguageCodes | null => {
|
||||
const locales = headers.get('accept-language') ?? '';
|
||||
|
||||
const [locale] = locales.split(',');
|
||||
|
||||
// Convert locale to language.
|
||||
const [language] = locale.split('-');
|
||||
const parseLanguageFromLocale = (locale: string): SupportedLanguageCodes | null => {
|
||||
const [language, _country] = locale.split('-');
|
||||
|
||||
const foundSupportedLanguage = APP_I18N_OPTIONS.supportedLangs.find(
|
||||
(lang): lang is SupportedLanguageCodes => lang === language,
|
||||
);
|
||||
|
||||
return foundSupportedLanguage || null;
|
||||
if (!foundSupportedLanguage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return foundSupportedLanguage;
|
||||
};
|
||||
|
||||
type ExtractSupportedLanguageOptions = {
|
||||
headers?: Headers;
|
||||
cookies?: ReadonlyRequestCookies;
|
||||
/**
|
||||
* Extract the language if supported from the cookies header.
|
||||
*
|
||||
* Returns `null` if not supported or not found.
|
||||
*/
|
||||
export const extractLocaleDataFromCookies = (
|
||||
cookies: ReadonlyRequestCookies,
|
||||
): SupportedLanguageCodes | null => {
|
||||
const preferredLocale = cookies.get('language')?.value || '';
|
||||
|
||||
const language = parseLanguageFromLocale(preferredLocale || '');
|
||||
|
||||
if (!language) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return language;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts the language from the `accept-language` header.
|
||||
*/
|
||||
export const extractLocaleDataFromHeaders = (
|
||||
headers: Headers,
|
||||
): { lang: SupportedLanguageCodes | null; locales: string[] } => {
|
||||
const headerLocales = (headers.get('accept-language') ?? '').split(',');
|
||||
|
||||
const language = parseLanguageFromLocale(headerLocales[0]);
|
||||
|
||||
return {
|
||||
lang: language,
|
||||
locales: [headerLocales[0]],
|
||||
};
|
||||
};
|
||||
|
||||
type ExtractLocaleDataOptions = {
|
||||
headers: Headers;
|
||||
cookies: ReadonlyRequestCookies;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -63,25 +73,25 @@ type ExtractSupportedLanguageOptions = {
|
||||
*
|
||||
* Will return the default fallback language if not found.
|
||||
*/
|
||||
export const extractSupportedLanguage = ({
|
||||
export const extractLocaleData = ({
|
||||
headers,
|
||||
cookies,
|
||||
}: ExtractSupportedLanguageOptions): SupportedLanguageCodes => {
|
||||
if (cookies) {
|
||||
const langCookie = extractSupportedLanguageFromCookies(cookies);
|
||||
}: ExtractLocaleDataOptions): I18nLocaleData => {
|
||||
let lang: SupportedLanguageCodes | null = extractLocaleDataFromCookies(cookies);
|
||||
|
||||
if (langCookie) {
|
||||
return langCookie;
|
||||
}
|
||||
const langHeader = extractLocaleDataFromHeaders(headers);
|
||||
|
||||
if (!lang && langHeader?.lang) {
|
||||
lang = langHeader.lang;
|
||||
}
|
||||
|
||||
if (headers) {
|
||||
const langHeader = extractSupportedLanguageFromHeaders(headers);
|
||||
|
||||
if (langHeader) {
|
||||
return langHeader;
|
||||
}
|
||||
// Override web app to be English.
|
||||
if (!IS_APP_WEB_I18N_ENABLED && IS_APP_WEB) {
|
||||
lang = 'en';
|
||||
}
|
||||
|
||||
return APP_I18N_OPTIONS.sourceLang;
|
||||
return {
|
||||
lang: lang || APP_I18N_OPTIONS.sourceLang,
|
||||
locales: langHeader.locales,
|
||||
};
|
||||
};
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "TeamEmailVerification" ADD COLUMN "completed" BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "TeamTransferVerification" ADD COLUMN "completed" BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "VerificationToken" ADD COLUMN "completed" BOOLEAN NOT NULL DEFAULT false;
|
||||
@ -149,6 +149,7 @@ model VerificationToken {
|
||||
secondaryId String @unique @default(cuid())
|
||||
identifier String
|
||||
token String @unique
|
||||
completed Boolean @default(false)
|
||||
expires DateTime
|
||||
createdAt DateTime @default(now())
|
||||
userId Int
|
||||
@ -546,6 +547,7 @@ model TeamEmailVerification {
|
||||
name String
|
||||
email String
|
||||
token String @unique
|
||||
completed Boolean @default(false)
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@ -558,6 +560,7 @@ model TeamTransferVerification {
|
||||
name String
|
||||
email String
|
||||
token String @unique
|
||||
completed Boolean @default(false)
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
clearPaymentMethods Boolean @default(false)
|
||||
|
||||
@ -4,7 +4,7 @@ import path from 'node:path';
|
||||
import {
|
||||
DIRECT_TEMPLATE_RECIPIENT_EMAIL,
|
||||
DIRECT_TEMPLATE_RECIPIENT_NAME,
|
||||
} from '@documenso/lib/constants/template';
|
||||
} from '@documenso/lib/constants/direct-templates';
|
||||
|
||||
import { prisma } from '..';
|
||||
import type { Prisma, User } from '../client';
|
||||
|
||||
@ -28,9 +28,18 @@ export const signWithLocalCert = async ({ pdf }: SignWithLocalCertOptions) => {
|
||||
}
|
||||
|
||||
if (!cert) {
|
||||
cert = Buffer.from(
|
||||
fs.readFileSync(process.env.NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH || './example/cert.p12'),
|
||||
);
|
||||
let certPath = process.env.NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH || '/opt/documenso/cert.p12';
|
||||
|
||||
// We don't want to make the development server suddenly crash when using the `dx` script
|
||||
// so we retain this when NODE_ENV isn't set to production which it should be in most production
|
||||
// deployments.
|
||||
//
|
||||
// Our docker image automatically sets this so it shouldn't be an issue for self-hosters.
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
certPath = process.env.NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH || './example/cert.p12';
|
||||
}
|
||||
|
||||
cert = Buffer.from(fs.readFileSync(certPath));
|
||||
}
|
||||
|
||||
const signature = signWithP12({
|
||||
|
||||
@ -65,7 +65,8 @@ export const twoFactorAuthenticationRouter = router({
|
||||
|
||||
return await disableTwoFactorAuthentication({
|
||||
user,
|
||||
token: input.token,
|
||||
totpCode: input.totpCode,
|
||||
backupCode: input.backupCode,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
@ -9,7 +9,8 @@ export type TEnableTwoFactorAuthenticationMutationSchema = z.infer<
|
||||
>;
|
||||
|
||||
export const ZDisableTwoFactorAuthenticationMutationSchema = z.object({
|
||||
token: z.string().trim().min(1),
|
||||
totpCode: z.string().trim().optional(),
|
||||
backupCode: z.string().trim().optional(),
|
||||
});
|
||||
|
||||
export type TDisableTwoFactorAuthenticationMutationSchema = z.infer<
|
||||
|
||||
35
packages/ui/components/call-to-action.tsx
Normal file
35
packages/ui/components/call-to-action.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import { Button } from '../primitives/button';
|
||||
import { Card, CardContent } from '../primitives/card';
|
||||
|
||||
type CallToActionProps = {
|
||||
className?: string;
|
||||
utmSource?: string;
|
||||
};
|
||||
|
||||
export const CallToAction = ({ className, utmSource = 'generic-cta' }: CallToActionProps) => {
|
||||
return (
|
||||
<Card spotlight className={className}>
|
||||
<CardContent className="flex flex-col items-center justify-center p-12">
|
||||
<h2 className="text-center text-2xl font-bold">Looking for the managed solution?</h2>
|
||||
|
||||
<p className="text-muted-foreground mt-4 max-w-[55ch] text-center leading-normal">
|
||||
You can get started with Documenso in minutes. We handle the infrastructure, so you can
|
||||
focus on signing documents.
|
||||
</p>
|
||||
|
||||
<Button
|
||||
className="focus-visible:ring-ring ring-offset-background bg-primary text-primary-foreground hover:bg-primary/90text-sm mt-8 inline-flex items-center justify-center rounded-full border font-medium no-underline transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
|
||||
variant="default"
|
||||
size="lg"
|
||||
asChild
|
||||
>
|
||||
<Link href={`https://app.documenso.com/signup?utm_source=${utmSource}`} target="_blank">
|
||||
Get started
|
||||
</Link>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
57
packages/ui/components/common/language-switcher-dialog.tsx
Normal file
57
packages/ui/components/common/language-switcher-dialog.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { CheckIcon } from 'lucide-react';
|
||||
|
||||
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
|
||||
import { switchI18NLanguage } from '@documenso/lib/server-only/i18n/switch-i18n-language';
|
||||
import { dynamicActivate } from '@documenso/lib/utils/i18n';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import {
|
||||
CommandDialog,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from '@documenso/ui/primitives/command';
|
||||
|
||||
type LanguageSwitcherDialogProps = {
|
||||
open: boolean;
|
||||
setOpen: (_open: boolean) => void;
|
||||
};
|
||||
|
||||
export const LanguageSwitcherDialog = ({ open, setOpen }: LanguageSwitcherDialogProps) => {
|
||||
const { i18n, _ } = useLingui();
|
||||
|
||||
const setLanguage = async (lang: string) => {
|
||||
setOpen(false);
|
||||
|
||||
await dynamicActivate(i18n, lang);
|
||||
await switchI18NLanguage(lang);
|
||||
};
|
||||
|
||||
return (
|
||||
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||
<CommandInput placeholder={_(msg`Search languages...`)} />
|
||||
|
||||
<CommandList>
|
||||
<CommandGroup>
|
||||
{Object.values(SUPPORTED_LANGUAGES).map((language) => (
|
||||
<CommandItem
|
||||
key={language.short}
|
||||
value={language.full}
|
||||
onSelect={async () => setLanguage(language.short)}
|
||||
>
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
'mr-2 h-4 w-4',
|
||||
i18n.locale === language.short ? 'opacity-100' : 'opacity-0',
|
||||
)}
|
||||
/>
|
||||
{SUPPORTED_LANGUAGES[language.short].full}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</CommandDialog>
|
||||
);
|
||||
};
|
||||
@ -3,6 +3,8 @@
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Download } from 'lucide-react';
|
||||
|
||||
import { downloadPDF } from '@documenso/lib/client-only/download-pdf';
|
||||
@ -24,9 +26,11 @@ export const DocumentDownloadButton = ({
|
||||
disabled,
|
||||
...props
|
||||
}: DownloadButtonProps) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const onDownloadClick = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
@ -43,8 +47,8 @@ export const DocumentDownloadButton = ({
|
||||
setIsLoading(false);
|
||||
|
||||
toast({
|
||||
title: 'Something went wrong',
|
||||
description: 'An error occurred while downloading your document.',
|
||||
title: _('Something went wrong'),
|
||||
description: _('An error occurred while downloading your document.'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -61,7 +65,7 @@ export const DocumentDownloadButton = ({
|
||||
{...props}
|
||||
>
|
||||
{!isLoading && <Download className="mr-2 h-5 w-5" />}
|
||||
Download
|
||||
<Trans>Download</Trans>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
import React, { forwardRef } from 'react';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import type { SelectProps } from '@radix-ui/react-select';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
|
||||
@ -17,24 +19,33 @@ import {
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
|
||||
export const DocumentGlobalAuthAccessSelect = forwardRef<HTMLButtonElement, SelectProps>(
|
||||
(props, ref) => (
|
||||
<Select {...props}>
|
||||
<SelectTrigger ref={ref} className="bg-background text-muted-foreground">
|
||||
<SelectValue data-testid="documentAccessSelectValue" placeholder="No restrictions" />
|
||||
</SelectTrigger>
|
||||
(props, ref) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
<SelectContent position="popper">
|
||||
{Object.values(DocumentAccessAuth).map((authType) => (
|
||||
<SelectItem key={authType} value={authType}>
|
||||
{DOCUMENT_AUTH_TYPES[authType].value}
|
||||
return (
|
||||
<Select {...props}>
|
||||
<SelectTrigger ref={ref} className="bg-background text-muted-foreground">
|
||||
<SelectValue
|
||||
data-testid="documentAccessSelectValue"
|
||||
placeholder={_(msg`No restrictions`)}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent position="popper">
|
||||
{Object.values(DocumentAccessAuth).map((authType) => (
|
||||
<SelectItem key={authType} value={authType}>
|
||||
{DOCUMENT_AUTH_TYPES[authType].value}
|
||||
</SelectItem>
|
||||
))}
|
||||
|
||||
{/* Note: -1 is remapped in the Zod schema to the required value. */}
|
||||
<SelectItem value={'-1'}>
|
||||
<Trans>No restrictions</Trans>
|
||||
</SelectItem>
|
||||
))}
|
||||
|
||||
{/* Note: -1 is remapped in the Zod schema to the required value. */}
|
||||
<SelectItem value={'-1'}>No restrictions</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
),
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
DocumentGlobalAuthAccessSelect.displayName = 'DocumentGlobalAuthAccessSelect';
|
||||
@ -47,18 +58,26 @@ export const DocumentGlobalAuthAccessTooltip = () => (
|
||||
|
||||
<TooltipContent className="text-foreground max-w-md space-y-2 p-4">
|
||||
<h2>
|
||||
<strong>Document access</strong>
|
||||
<strong>
|
||||
<Trans>Document access</Trans>
|
||||
</strong>
|
||||
</h2>
|
||||
|
||||
<p>The authentication required for recipients to view the document.</p>
|
||||
<p>
|
||||
<Trans>The authentication required for recipients to view the document.</Trans>
|
||||
</p>
|
||||
|
||||
<ul className="ml-3.5 list-outside list-disc space-y-0.5 py-2">
|
||||
<li>
|
||||
<strong>Require account</strong> - The recipient must be signed in to view the document
|
||||
<Trans>
|
||||
<strong>Require account</strong> - The recipient must be signed in to view the document
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<strong>No restrictions</strong> - The document can be accessed directly by the URL sent
|
||||
to the recipient
|
||||
<Trans>
|
||||
<strong>No restrictions</strong> - The document can be accessed directly by the URL sent
|
||||
to the recipient
|
||||
</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
</TooltipContent>
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
import React, { forwardRef } from 'react';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import type { SelectProps } from '@radix-ui/react-select';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
|
||||
@ -17,26 +19,36 @@ import {
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
|
||||
export const DocumentGlobalAuthActionSelect = forwardRef<HTMLButtonElement, SelectProps>(
|
||||
(props, ref) => (
|
||||
<Select {...props}>
|
||||
<SelectTrigger className="bg-background text-muted-foreground">
|
||||
<SelectValue ref={ref} data-testid="documentActionSelectValue" placeholder="No restrictions" />
|
||||
</SelectTrigger>
|
||||
(props, ref) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
<SelectContent position="popper">
|
||||
{Object.values(DocumentActionAuth)
|
||||
.filter((auth) => auth !== DocumentAuth.ACCOUNT)
|
||||
.map((authType) => (
|
||||
<SelectItem key={authType} value={authType}>
|
||||
{DOCUMENT_AUTH_TYPES[authType].value}
|
||||
</SelectItem>
|
||||
))}
|
||||
return (
|
||||
<Select {...props}>
|
||||
<SelectTrigger className="bg-background text-muted-foreground">
|
||||
<SelectValue
|
||||
ref={ref}
|
||||
data-testid="documentActionSelectValue"
|
||||
placeholder={_(msg`No restrictions`)}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
|
||||
{/* Note: -1 is remapped in the Zod schema to the required value. */}
|
||||
<SelectItem value={'-1'}>No restrictions</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
),
|
||||
<SelectContent position="popper">
|
||||
{Object.values(DocumentActionAuth)
|
||||
.filter((auth) => auth !== DocumentAuth.ACCOUNT)
|
||||
.map((authType) => (
|
||||
<SelectItem key={authType} value={authType}>
|
||||
{DOCUMENT_AUTH_TYPES[authType].value}
|
||||
</SelectItem>
|
||||
))}
|
||||
|
||||
{/* Note: -1 is remapped in the Zod schema to the required value. */}
|
||||
<SelectItem value={'-1'}>
|
||||
<Trans>No restrictions</Trans>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
DocumentGlobalAuthActionSelect.displayName = 'DocumentGlobalAuthActionSelect';
|
||||
@ -48,13 +60,19 @@ export const DocumentGlobalAuthActionTooltip = () => (
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-foreground max-w-md space-y-2 p-4">
|
||||
<h2>Global recipient action authentication</h2>
|
||||
|
||||
<p>The authentication required for recipients to sign the signature field.</p>
|
||||
<h2>
|
||||
<Trans>Global recipient action authentication</Trans>
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
This can be overriden by setting the authentication requirements directly on each recipient
|
||||
in the next step.
|
||||
<Trans>The authentication required for recipients to sign the signature field.</Trans>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<Trans>
|
||||
This can be overriden by setting the authentication requirements directly on each
|
||||
recipient in the next step.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<ul className="ml-3.5 list-outside list-disc space-y-0.5 py-2">
|
||||
@ -62,15 +80,21 @@ export const DocumentGlobalAuthActionTooltip = () => (
|
||||
<strong>Require account</strong> - The recipient must be signed in
|
||||
</li> */}
|
||||
<li>
|
||||
<strong>Require passkey</strong> - The recipient must have an account and passkey
|
||||
configured via their settings
|
||||
<Trans>
|
||||
<strong>Require passkey</strong> - The recipient must have an account and passkey
|
||||
configured via their settings
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Require 2FA</strong> - The recipient must have an account and 2FA enabled via
|
||||
their settings
|
||||
<Trans>
|
||||
<strong>Require 2FA</strong> - The recipient must have an account and 2FA enabled via
|
||||
their settings
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<strong>No restrictions</strong> - No authentication required
|
||||
<Trans>
|
||||
<strong>No restrictions</strong> - No authentication required
|
||||
</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
</TooltipContent>
|
||||
|
||||
@ -2,11 +2,13 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
export const DocumentSendEmailMessageHelper = () => {
|
||||
return (
|
||||
<div>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
You can use the following variables in your message:
|
||||
<Trans>You can use the following variables in your message:</Trans>
|
||||
</p>
|
||||
|
||||
<ul className="mt-2 flex list-inside list-disc flex-col gap-y-2 text-sm">
|
||||
@ -14,19 +16,19 @@ export const DocumentSendEmailMessageHelper = () => {
|
||||
<code className="text-muted-foreground bg-muted-foreground/20 rounded p-1 text-sm">
|
||||
{'{signer.name}'}
|
||||
</code>{' '}
|
||||
- The signer's name
|
||||
- <Trans>The signer's name</Trans>
|
||||
</li>
|
||||
<li className="text-muted-foreground">
|
||||
<code className="text-muted-foreground bg-muted-foreground/20 rounded p-1 text-sm">
|
||||
{'{signer.email}'}
|
||||
</code>{' '}
|
||||
- The signer's email
|
||||
- <Trans>The signer's email</Trans>
|
||||
</li>
|
||||
<li className="text-muted-foreground">
|
||||
<code className="text-muted-foreground bg-muted-foreground/20 rounded p-1 text-sm">
|
||||
{'{document.name}'}
|
||||
</code>{' '}
|
||||
- The document's name
|
||||
- <Trans>The document's name</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@ -3,15 +3,13 @@
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Copy, Sparkles } from 'lucide-react';
|
||||
import { FaXTwitter } from 'react-icons/fa6';
|
||||
|
||||
import { useCopyShareLink } from '@documenso/lib/client-only/hooks/use-copy-share-link';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import {
|
||||
TOAST_DOCUMENT_SHARE_ERROR,
|
||||
TOAST_DOCUMENT_SHARE_SUCCESS,
|
||||
} from '@documenso/lib/constants/toast';
|
||||
import { generateTwitterIntent } from '@documenso/lib/universal/generate-twitter-intent';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
|
||||
@ -39,11 +37,22 @@ export const DocumentShareButton = ({
|
||||
className,
|
||||
trigger,
|
||||
}: DocumentShareButtonProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { copyShareLink, createAndCopyShareLink, isCopyingShareLink } = useCopyShareLink({
|
||||
onSuccess: () => toast(TOAST_DOCUMENT_SHARE_SUCCESS),
|
||||
onError: () => toast(TOAST_DOCUMENT_SHARE_ERROR),
|
||||
onSuccess: () =>
|
||||
toast({
|
||||
title: _(msg`Copied to clipboard`),
|
||||
description: _(msg`The sharing link has been copied to your clipboard.`),
|
||||
}),
|
||||
onError: () =>
|
||||
toast({
|
||||
title: _(msg`Something went wrong`),
|
||||
description: _(msg`The sharing link could not be created at this time. Please try again.`),
|
||||
variant: 'destructive',
|
||||
duration: 5000,
|
||||
}),
|
||||
});
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
@ -123,7 +132,7 @@ export const DocumentShareButton = ({
|
||||
loading={isLoading}
|
||||
>
|
||||
{!isLoading && <Sparkles className="mr-2 h-5 w-5" />}
|
||||
Share Signature Card
|
||||
<Trans>Share Signature Card</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
@ -133,8 +142,9 @@ export const DocumentShareButton = ({
|
||||
<DialogTitle>Share your signing experience!</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
Don't worry, the document you signed or sent wont be shared; only your signing
|
||||
experience is. Share your signing card and showcase your signature!
|
||||
Rest assured, your document is strictly confidential and will never be shared. Only your
|
||||
signing experience will be highlighted. Share your personalized signing card to showcase
|
||||
your signature!
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import type { SelectProps } from '@radix-ui/react-select';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
|
||||
@ -19,10 +21,12 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitive
|
||||
export type RecipientActionAuthSelectProps = SelectProps;
|
||||
|
||||
export const RecipientActionAuthSelect = (props: RecipientActionAuthSelectProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
return (
|
||||
<Select {...props}>
|
||||
<SelectTrigger className="bg-background text-muted-foreground">
|
||||
<SelectValue placeholder="Inherit authentication method" />
|
||||
<SelectValue placeholder={_(msg`Inherit authentication method`)} />
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger className="-mr-1 ml-auto">
|
||||
@ -31,32 +35,46 @@ export const RecipientActionAuthSelect = (props: RecipientActionAuthSelectProps)
|
||||
|
||||
<TooltipContent className="text-foreground max-w-md p-4">
|
||||
<h2>
|
||||
<strong>Recipient action authentication</strong>
|
||||
<strong>
|
||||
<Trans>Recipient action authentication</Trans>
|
||||
</strong>
|
||||
</h2>
|
||||
|
||||
<p>The authentication required for recipients to sign fields</p>
|
||||
<p>
|
||||
<Trans>The authentication required for recipients to sign fields</Trans>
|
||||
</p>
|
||||
|
||||
<p className="mt-2">This will override any global settings.</p>
|
||||
<p className="mt-2">
|
||||
<Trans>This will override any global settings.</Trans>
|
||||
</p>
|
||||
|
||||
<ul className="ml-3.5 list-outside list-disc space-y-0.5 py-2">
|
||||
<li>
|
||||
<strong>Inherit authentication method</strong> - Use the global action signing
|
||||
authentication method configured in the "General Settings" step
|
||||
<Trans>
|
||||
<strong>Inherit authentication method</strong> - Use the global action signing
|
||||
authentication method configured in the "General Settings" step
|
||||
</Trans>
|
||||
</li>
|
||||
{/* <li>
|
||||
<strong>Require account</strong> - The recipient must be
|
||||
signed in
|
||||
</li> */}
|
||||
<li>
|
||||
<strong>Require passkey</strong> - The recipient must have an account and passkey
|
||||
configured via their settings
|
||||
<Trans>
|
||||
<strong>Require passkey</strong> - The recipient must have an account and passkey
|
||||
configured via their settings
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Require 2FA</strong> - The recipient must have an account and 2FA enabled
|
||||
via their settings
|
||||
<Trans>
|
||||
<strong>Require 2FA</strong> - The recipient must have an account and 2FA enabled
|
||||
via their settings
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<strong>None</strong> - No authentication required
|
||||
<Trans>
|
||||
<strong>None</strong> - No authentication required
|
||||
</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
</TooltipContent>
|
||||
@ -65,7 +83,9 @@ export const RecipientActionAuthSelect = (props: RecipientActionAuthSelectProps)
|
||||
|
||||
<SelectContent position="popper">
|
||||
{/* Note: -1 is remapped in the Zod schema to the required value. */}
|
||||
<SelectItem value="-1">Inherit authentication method</SelectItem>
|
||||
<SelectItem value="-1">
|
||||
<Trans>Inherit authentication method</Trans>
|
||||
</SelectItem>
|
||||
|
||||
{Object.values(RecipientActionAuth)
|
||||
.filter((auth) => auth !== RecipientActionAuth.ACCOUNT)
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import React, { forwardRef } from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import type { SelectProps } from '@radix-ui/react-select';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
|
||||
@ -27,14 +28,18 @@ export const RecipientRoleSelect = forwardRef<HTMLButtonElement, RecipientRoleSe
|
||||
<div className="flex items-center">
|
||||
<div className="flex w-[150px] items-center">
|
||||
<span className="mr-2">{ROLE_ICONS[RecipientRole.SIGNER]}</span>
|
||||
Needs to sign
|
||||
<Trans>Needs to sign</Trans>
|
||||
</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="text-foreground z-9999 max-w-md p-4">
|
||||
<p>The recipient is required to sign the document for it to be completed.</p>
|
||||
<p>
|
||||
<Trans>
|
||||
The recipient is required to sign the document for it to be completed.
|
||||
</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
@ -44,14 +49,18 @@ export const RecipientRoleSelect = forwardRef<HTMLButtonElement, RecipientRoleSe
|
||||
<div className="flex items-center">
|
||||
<div className="flex w-[150px] items-center">
|
||||
<span className="mr-2">{ROLE_ICONS[RecipientRole.APPROVER]}</span>
|
||||
Needs to approve
|
||||
<Trans>Needs to approve</Trans>
|
||||
</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="text-foreground z-9999 max-w-md p-4">
|
||||
<p>The recipient is required to approve the document for it to be completed.</p>
|
||||
<p>
|
||||
<Trans>
|
||||
The recipient is required to approve the document for it to be completed.
|
||||
</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
@ -61,14 +70,18 @@ export const RecipientRoleSelect = forwardRef<HTMLButtonElement, RecipientRoleSe
|
||||
<div className="flex items-center">
|
||||
<div className="flex w-[150px] items-center">
|
||||
<span className="mr-2">{ROLE_ICONS[RecipientRole.VIEWER]}</span>
|
||||
Needs to view
|
||||
<Trans>Needs to view</Trans>
|
||||
</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="text-foreground z-9999 max-w-md p-4">
|
||||
<p>The recipient is required to view the document for it to be completed.</p>
|
||||
<p>
|
||||
<Trans>
|
||||
The recipient is required to view the document for it to be completed.
|
||||
</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
@ -79,7 +92,7 @@ export const RecipientRoleSelect = forwardRef<HTMLButtonElement, RecipientRoleSe
|
||||
<div className="flex items-center">
|
||||
<div className="flex w-[150px] items-center">
|
||||
<span className="mr-2">{ROLE_ICONS[RecipientRole.CC]}</span>
|
||||
Receives copy
|
||||
<Trans>Receives copy</Trans>
|
||||
</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
@ -87,8 +100,10 @@ export const RecipientRoleSelect = forwardRef<HTMLButtonElement, RecipientRoleSe
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="text-foreground z-9999 max-w-md p-4">
|
||||
<p>
|
||||
The recipient is not required to take any action and receives a copy of the
|
||||
document after it is completed.
|
||||
<Trans>
|
||||
The recipient is not required to take any action and receives a copy of the
|
||||
document after it is completed.
|
||||
</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
@ -241,6 +241,7 @@ const SigningCardImage = ({ signingCelebrationImage }: SigningCardImageProps) =>
|
||||
mask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 67%)',
|
||||
WebkitMask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 67%)',
|
||||
}}
|
||||
priority
|
||||
/>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
@ -20,16 +20,16 @@
|
||||
"@documenso/tailwind-config": "*",
|
||||
"@documenso/tsconfig": "*",
|
||||
"@types/luxon": "^3.3.2",
|
||||
"@types/react": "18.2.18",
|
||||
"@types/react-dom": "18.2.7",
|
||||
"react": "18.2.0",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"react": "^18",
|
||||
"typescript": "5.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@documenso/lib": "*",
|
||||
"@hookform/resolvers": "^3.3.0",
|
||||
"@lingui/macro": "^4.11.1",
|
||||
"@lingui/react": "^4.11.1",
|
||||
"@lingui/macro": "^4.11.3",
|
||||
"@lingui/react": "^4.11.3",
|
||||
"@radix-ui/react-accordion": "^1.1.1",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.3",
|
||||
"@radix-ui/react-aspect-ratio": "^1.0.2",
|
||||
@ -64,12 +64,12 @@
|
||||
"framer-motion": "^10.12.8",
|
||||
"lucide-react": "^0.279.0",
|
||||
"luxon": "^3.4.2",
|
||||
"next": "14.0.3",
|
||||
"next": "14.2.6",
|
||||
"pdfjs-dist": "3.11.174",
|
||||
"react": "18.2.0",
|
||||
"react": "^18",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-day-picker": "^8.7.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react-dom": "^18",
|
||||
"react-hook-form": "^7.45.4",
|
||||
"react-pdf": "7.7.3",
|
||||
"react-rnd": "^10.4.1",
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Check, ChevronDown } from 'lucide-react';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
@ -24,6 +26,8 @@ const Combobox = ({
|
||||
disabled = false,
|
||||
placeholder,
|
||||
}: ComboboxProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
const onOptionSelected = (newValue: string) => {
|
||||
@ -31,7 +35,7 @@ const Combobox = ({
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const placeholderValue = placeholder ?? 'Select an option';
|
||||
const placeholderValue = placeholder ?? _(msg`Select an option`);
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
@ -52,7 +56,9 @@ const Combobox = ({
|
||||
<Command>
|
||||
<CommandInput placeholder={value || placeholderValue} />
|
||||
|
||||
<CommandEmpty>No value found.</CommandEmpty>
|
||||
<CommandEmpty>
|
||||
<Trans>No value found.</Trans>
|
||||
</CommandEmpty>
|
||||
|
||||
<CommandGroup className="max-h-[250px] overflow-y-auto">
|
||||
{options.map((option, index) => (
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { Plural, Trans } from '@lingui/macro';
|
||||
import type { Table } from '@tanstack/react-table';
|
||||
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';
|
||||
import { match } from 'ts-pattern';
|
||||
@ -26,8 +27,10 @@ export function DataTablePagination<TData>({
|
||||
{match(additionalInformation)
|
||||
.with('SelectedCount', () => (
|
||||
<span>
|
||||
{table.getFilteredSelectedRowModel().rows.length} of{' '}
|
||||
{table.getFilteredRowModel().rows.length} row(s) selected.
|
||||
<Trans>
|
||||
{table.getFilteredSelectedRowModel().rows.length} of{' '}
|
||||
{table.getFilteredRowModel().rows.length} row(s) selected.
|
||||
</Trans>
|
||||
</span>
|
||||
))
|
||||
.with('VisibleCount', () => {
|
||||
@ -35,7 +38,11 @@ export function DataTablePagination<TData>({
|
||||
|
||||
return (
|
||||
<span data-testid="data-table-count">
|
||||
Showing {visibleRows} result{visibleRows > 1 && 's'}.
|
||||
<Plural
|
||||
value={visibleRows}
|
||||
one={`Showing # result.`}
|
||||
other={`Showing # results.`}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
})
|
||||
@ -44,7 +51,9 @@ export function DataTablePagination<TData>({
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-x-2">
|
||||
<p className="whitespace-nowrap text-sm font-medium">Rows per page</p>
|
||||
<p className="whitespace-nowrap text-sm font-medium">
|
||||
<Trans>Rows per page</Trans>
|
||||
</p>
|
||||
<Select
|
||||
value={`${table.getState().pagination.pageSize}`}
|
||||
onValueChange={(value) => {
|
||||
@ -65,7 +74,9 @@ export function DataTablePagination<TData>({
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-x-6 gap-y-4 lg:gap-x-8">
|
||||
<div className="flex items-center text-sm font-medium md:justify-center">
|
||||
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount() || 1}
|
||||
<Trans>
|
||||
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount() || 1}
|
||||
</Trans>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-x-2">
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import type {
|
||||
ColumnDef,
|
||||
PaginationState,
|
||||
@ -16,6 +17,8 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '.
|
||||
|
||||
export type DataTableChildren<TData> = (_table: TTable<TData>) => React.ReactNode;
|
||||
|
||||
export type { ColumnDef as DataTableColumnDef } from '@tanstack/react-table';
|
||||
|
||||
export interface DataTableProps<TData, TValue> {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
columnVisibility?: VisibilityState;
|
||||
@ -130,7 +133,7 @@ export function DataTable<TData, TValue>({
|
||||
<TableRow>
|
||||
{error.component ?? (
|
||||
<TableCell colSpan={columns.length} className="h-32 text-center">
|
||||
Something went wrong.
|
||||
<Trans>Something went wrong.</Trans>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
@ -141,14 +144,16 @@ export function DataTable<TData, TValue>({
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-32 text-center">
|
||||
<p>No results found</p>
|
||||
<p>
|
||||
<Trans>No results found</Trans>
|
||||
</p>
|
||||
|
||||
{hasFilters && onClearFilters !== undefined && (
|
||||
<button
|
||||
onClick={() => onClearFilters()}
|
||||
className="text-foreground mt-1 text-sm"
|
||||
>
|
||||
Clear filters
|
||||
<Trans>Clear filters</Trans>
|
||||
</button>
|
||||
)}
|
||||
</TableCell>
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
|
||||
import Link from 'next/link';
|
||||
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { AlertTriangle, Plus } from 'lucide-react';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
@ -25,7 +28,7 @@ import { Card, CardContent } from './card';
|
||||
export type DocumentDropzoneProps = {
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
disabledMessage?: string;
|
||||
disabledMessage?: MessageDescriptor;
|
||||
onDrop?: (_file: File) => void | Promise<void>;
|
||||
onDropRejected?: () => void | Promise<void>;
|
||||
type?: 'document' | 'template';
|
||||
@ -37,10 +40,12 @@ export const DocumentDropzone = ({
|
||||
onDrop,
|
||||
onDropRejected,
|
||||
disabled,
|
||||
disabledMessage = 'You cannot upload documents at this time.',
|
||||
disabledMessage = msg`You cannot upload documents at this time.`,
|
||||
type = 'document',
|
||||
...props
|
||||
}: DocumentDropzoneProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
accept: {
|
||||
'application/pdf': ['.pdf'],
|
||||
@ -61,8 +66,8 @@ export const DocumentDropzone = ({
|
||||
});
|
||||
|
||||
const heading = {
|
||||
document: disabled ? 'You have reached your document limit.' : 'Add a document',
|
||||
template: 'Upload Template Document',
|
||||
document: disabled ? msg`You have reached your document limit.` : msg`Add a document`,
|
||||
template: msg`Upload Template Document`,
|
||||
};
|
||||
|
||||
return (
|
||||
@ -151,15 +156,17 @@ export const DocumentDropzone = ({
|
||||
|
||||
<input {...getInputProps()} />
|
||||
|
||||
<p className="text-foreground mt-8 font-medium">{heading[type]}</p>
|
||||
<p className="text-foreground mt-8 font-medium">{_(heading[type])}</p>
|
||||
|
||||
<p className="text-muted-foreground/80 mt-1 text-center text-sm">
|
||||
{disabled ? disabledMessage : 'Drag & drop your PDF here.'}
|
||||
{_(disabled ? disabledMessage : msg`Drag & drop your PDF here.`)}
|
||||
</p>
|
||||
|
||||
{disabled && IS_BILLING_ENABLED() && (
|
||||
<Button className="hover:bg-warning/80 bg-warning mt-4 w-32" asChild>
|
||||
<Link href="/settings/billing">Upgrade</Link>
|
||||
<Link href="/settings/billing">
|
||||
<Trans>Upgrade</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { Caveat } from 'next/font/google';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import {
|
||||
CalendarDays,
|
||||
Check,
|
||||
@ -24,7 +25,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||
import { useDocumentElement } from '@documenso/lib/client-only/hooks/use-document-element';
|
||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import { RECIPIENT_ROLES_DESCRIPTION_ENG } from '@documenso/lib/constants/recipient-roles';
|
||||
import {
|
||||
type TFieldMetaSchema as FieldMeta,
|
||||
ZFieldMetaSchema,
|
||||
@ -506,8 +507,8 @@ export const AddFieldsFormPartial = ({
|
||||
<>
|
||||
{showAdvancedSettings && currentField ? (
|
||||
<FieldAdvancedSettings
|
||||
title="Advanced settings"
|
||||
description={`Configure the ${FRIENDLY_FIELD_TYPE[currentField.type]} field`}
|
||||
title={msg`Advanced settings`}
|
||||
description={msg`Configure the ${FRIENDLY_FIELD_TYPE[currentField.type]} field`}
|
||||
field={currentField}
|
||||
fields={localFields}
|
||||
onAdvancedSettings={handleAdvancedSettings}
|
||||
@ -609,14 +610,15 @@ export const AddFieldsFormPartial = ({
|
||||
|
||||
<CommandEmpty>
|
||||
<span className="text-muted-foreground inline-block px-4">
|
||||
No recipient matching this description was found.
|
||||
<Trans>No recipient matching this description was found.</Trans>
|
||||
</span>
|
||||
</CommandEmpty>
|
||||
|
||||
{recipientsByRoleToDisplay.map(([role, roleRecipients], roleIndex) => (
|
||||
<CommandGroup key={roleIndex}>
|
||||
<div className="text-muted-foreground mb-1 ml-2 mt-2 text-xs font-medium">
|
||||
{`${RECIPIENT_ROLES_DESCRIPTION[role].roleName}s`}
|
||||
{/* Todo: Translations - Add plural translations. */}
|
||||
{`${RECIPIENT_ROLES_DESCRIPTION_ENG[role].roleName}s`}
|
||||
</div>
|
||||
|
||||
{roleRecipients.length === 0 && (
|
||||
@ -624,7 +626,7 @@ export const AddFieldsFormPartial = ({
|
||||
key={`${role}-empty`}
|
||||
className="text-muted-foreground/80 px-4 pb-4 pt-2.5 text-center text-xs"
|
||||
>
|
||||
No recipients with this role
|
||||
<Trans>No recipients with this role</Trans>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -680,8 +682,10 @@ export const AddFieldsFormPartial = ({
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-muted-foreground max-w-xs">
|
||||
This document has already been sent to this recipient. You can
|
||||
no longer edit this recipient.
|
||||
<Trans>
|
||||
This document has already been sent to this recipient. You
|
||||
can no longer edit this recipient.
|
||||
</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
@ -717,7 +721,7 @@ export const AddFieldsFormPartial = ({
|
||||
fontCaveat.className,
|
||||
)}
|
||||
>
|
||||
Signature
|
||||
<Trans>Signature</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -769,7 +773,7 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Mail className="h-4 w-4" />
|
||||
Email
|
||||
<Trans>Email</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -795,7 +799,7 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<User className="h-4 w-4" />
|
||||
Name
|
||||
<Trans>Name</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -821,7 +825,7 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<CalendarDays className="h-4 w-4" />
|
||||
Date
|
||||
<Trans>Date</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -847,7 +851,7 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Type className="h-4 w-4" />
|
||||
Text
|
||||
<Trans>Text</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -873,7 +877,7 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Hash className="h-4 w-4" />
|
||||
Number
|
||||
<Trans>Number</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -899,7 +903,7 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Disc className="h-4 w-4" />
|
||||
Radio
|
||||
<Trans>Radio</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -925,7 +929,7 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<CheckSquare className="h-4 w-4" />
|
||||
Checkbox
|
||||
<Trans>Checkbox</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -951,7 +955,7 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
Dropdown
|
||||
<Trans>Dropdown</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -964,23 +968,21 @@ export const AddFieldsFormPartial = ({
|
||||
<div className="mt-4">
|
||||
<ul>
|
||||
<li className="text-sm text-red-500">
|
||||
To proceed further, please set at least one value for the{' '}
|
||||
{emptyCheckboxFields.length > 0
|
||||
? 'Checkbox'
|
||||
: emptyRadioFields.length > 0
|
||||
? 'Radio'
|
||||
: 'Select'}{' '}
|
||||
field.
|
||||
<Trans>
|
||||
To proceed further, please set at least one value for the{' '}
|
||||
{emptyCheckboxFields.length > 0
|
||||
? 'Checkbox'
|
||||
: emptyRadioFields.length > 0
|
||||
? 'Radio'
|
||||
: 'Select'}{' '}
|
||||
field.
|
||||
</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
<DocumentFlowFormContainerFooter>
|
||||
<DocumentFlowFormContainerStep
|
||||
title={documentFlow.title}
|
||||
step={currentStep}
|
||||
maxStep={totalSteps}
|
||||
/>
|
||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||
|
||||
<DocumentFlowFormContainerActions
|
||||
loading={isSubmitting}
|
||||
@ -991,7 +993,7 @@ export const AddFieldsFormPartial = ({
|
||||
remove();
|
||||
documentFlow.onBackStep?.();
|
||||
}}
|
||||
goBackLabel={canRenderBackButtonAsRemove ? 'Remove' : undefined}
|
||||
goBackLabel={canRenderBackButtonAsRemove ? msg`Remove` : undefined}
|
||||
onGoNextClick={handleGoNextClick}
|
||||
/>
|
||||
</DocumentFlowFormContainerFooter>
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
@ -140,7 +141,9 @@ export const AddSettingsFormPartial = ({
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>Title</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Title</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
@ -160,7 +163,7 @@ export const AddSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
Document access
|
||||
<Trans>Document access</Trans>
|
||||
<DocumentGlobalAuthAccessTooltip />
|
||||
</FormLabel>
|
||||
|
||||
@ -178,7 +181,7 @@ export const AddSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
Recipient action authentication
|
||||
<Trans>Recipient action authentication</Trans>
|
||||
<DocumentGlobalAuthActionTooltip />
|
||||
</FormLabel>
|
||||
|
||||
@ -193,7 +196,7 @@ export const AddSettingsFormPartial = ({
|
||||
<Accordion type="multiple" className="mt-6">
|
||||
<AccordionItem value="advanced-options" className="border-none">
|
||||
<AccordionTrigger className="text-foreground mb-2 rounded border px-3 py-2 text-left hover:bg-neutral-200/30 hover:no-underline">
|
||||
Advanced Options
|
||||
<Trans>Advanced Options</Trans>
|
||||
</AccordionTrigger>
|
||||
|
||||
<AccordionContent className="text-muted-foreground -mx-1 px-1 pt-2 text-sm leading-relaxed">
|
||||
@ -204,15 +207,17 @@ export const AddSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
External ID{' '}
|
||||
<Trans>External ID</Trans>{' '}
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-muted-foreground max-w-xs">
|
||||
Add an external ID to the document. This can be used to identify the
|
||||
document in external systems.
|
||||
<Trans>
|
||||
Add an external ID to the document. This can be used to identify
|
||||
the document in external systems.
|
||||
</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormLabel>
|
||||
@ -231,7 +236,9 @@ export const AddSettingsFormPartial = ({
|
||||
name="meta.dateFormat"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Date Format</FormLabel>
|
||||
<FormLabel>
|
||||
<Trans>Date Format</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Select
|
||||
@ -263,7 +270,9 @@ export const AddSettingsFormPartial = ({
|
||||
name="meta.timezone"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Time Zone</FormLabel>
|
||||
<FormLabel>
|
||||
<Trans>Time Zone</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Combobox
|
||||
@ -286,14 +295,16 @@ export const AddSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
Redirect URL{' '}
|
||||
<Trans>Redirect URL</Trans>{' '}
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-muted-foreground max-w-xs">
|
||||
Add a URL to redirect the user to once the document is signed
|
||||
<Trans>
|
||||
Add a URL to redirect the user to once the document is signed
|
||||
</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormLabel>
|
||||
@ -315,11 +326,7 @@ export const AddSettingsFormPartial = ({
|
||||
</DocumentFlowFormContainerContent>
|
||||
|
||||
<DocumentFlowFormContainerFooter>
|
||||
<DocumentFlowFormContainerStep
|
||||
title={documentFlow.title}
|
||||
step={currentStep}
|
||||
maxStep={totalSteps}
|
||||
/>
|
||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||
|
||||
<DocumentFlowFormContainerActions
|
||||
loading={form.formState.isSubmitting}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { DateTime } from 'luxon';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { match } from 'ts-pattern';
|
||||
@ -267,7 +268,9 @@ export const AddSignatureFormPartial = ({
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>Email</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Email</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="bg-background"
|
||||
@ -291,7 +294,9 @@ export const AddSignatureFormPartial = ({
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required={requireName}>Name</FormLabel>
|
||||
<FormLabel required={requireName}>
|
||||
<Trans>Name</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="bg-background"
|
||||
@ -314,7 +319,9 @@ export const AddSignatureFormPartial = ({
|
||||
name="signature"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required={requireSignature}>Signature</FormLabel>
|
||||
<FormLabel required={requireSignature}>
|
||||
<Trans>Signature</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Card
|
||||
className={cn('mt-2', {
|
||||
@ -349,7 +356,9 @@ export const AddSignatureFormPartial = ({
|
||||
name="customText"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required={requireCustomText}>Custom Text</FormLabel>
|
||||
<FormLabel required={requireCustomText}>
|
||||
<Trans>Custom Text</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="bg-background"
|
||||
@ -369,11 +378,7 @@ export const AddSignatureFormPartial = ({
|
||||
</DocumentFlowFormContainerContent>
|
||||
|
||||
<DocumentFlowFormContainerFooter>
|
||||
<DocumentFlowFormContainerStep
|
||||
title={documentFlow.title}
|
||||
step={currentStep}
|
||||
maxStep={totalSteps}
|
||||
/>
|
||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||
|
||||
<DocumentFlowFormContainerActions
|
||||
loading={form.formState.isSubmitting}
|
||||
@ -386,7 +391,7 @@ export const AddSignatureFormPartial = ({
|
||||
|
||||
{validateUninsertedFields && uninsertedFields[0] && (
|
||||
<FieldToolTip key={uninsertedFields[0].id} field={uninsertedFields[0]} color="warning">
|
||||
Click to insert field
|
||||
<Trans>Click to insert field</Trans>
|
||||
</FieldToolTip>
|
||||
)}
|
||||
|
||||
|
||||
@ -5,6 +5,8 @@ import React, { useId, useMemo, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Plus, Trash } from 'lucide-react';
|
||||
import { useSession } from 'next-auth/react';
|
||||
@ -63,10 +65,12 @@ export const AddSignersFormPartial = ({
|
||||
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();
|
||||
@ -318,13 +322,15 @@ export const AddSignersFormPartial = ({
|
||||
})}
|
||||
>
|
||||
{!showAdvancedSettings && index === 0 && (
|
||||
<FormLabel required>Email</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Email</Trans>
|
||||
</FormLabel>
|
||||
)}
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
placeholder={_(msg`Email`)}
|
||||
{...field}
|
||||
disabled={isSubmitting || hasBeenSentToRecipientId(signer.nativeId)}
|
||||
onKeyDown={onKeyDown}
|
||||
@ -351,7 +357,7 @@ export const AddSignersFormPartial = ({
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Name"
|
||||
placeholder={_(msg`Name`)}
|
||||
{...field}
|
||||
disabled={isSubmitting || hasBeenSentToRecipientId(signer.nativeId)}
|
||||
onKeyDown={onKeyDown}
|
||||
@ -435,7 +441,7 @@ export const AddSignersFormPartial = ({
|
||||
onClick={() => onAddSigner()}
|
||||
>
|
||||
<Plus className="-ml-1 mr-2 h-5 w-5" />
|
||||
Add Signer
|
||||
<Trans>Add Signer</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -446,7 +452,7 @@ export const AddSignersFormPartial = ({
|
||||
onClick={() => onAddSelfSigner()}
|
||||
>
|
||||
<Plus className="-ml-1 mr-2 h-5 w-5" />
|
||||
Add myself
|
||||
<Trans>Add myself</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -464,7 +470,7 @@ export const AddSignersFormPartial = ({
|
||||
className="text-muted-foreground ml-2 text-sm"
|
||||
htmlFor="showAdvancedRecipientSettings"
|
||||
>
|
||||
Show advanced settings
|
||||
<Trans>Show advanced settings</Trans>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
@ -473,11 +479,7 @@ export const AddSignersFormPartial = ({
|
||||
</DocumentFlowFormContainerContent>
|
||||
|
||||
<DocumentFlowFormContainerFooter>
|
||||
<DocumentFlowFormContainerStep
|
||||
title={documentFlow.title}
|
||||
step={currentStep}
|
||||
maxStep={totalSteps}
|
||||
/>
|
||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||
|
||||
<DocumentFlowFormContainerActions
|
||||
loading={isSubmitting}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import type { Field, Recipient } from '@documenso/prisma/client';
|
||||
@ -74,7 +75,9 @@ export const AddSubjectFormPartial = ({
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<div>
|
||||
<Label htmlFor="subject">
|
||||
Subject <span className="text-muted-foreground">(Optional)</span>
|
||||
<Trans>
|
||||
Subject <span className="text-muted-foreground">(Optional)</span>
|
||||
</Trans>
|
||||
</Label>
|
||||
|
||||
<Input
|
||||
@ -89,7 +92,9 @@ export const AddSubjectFormPartial = ({
|
||||
|
||||
<div>
|
||||
<Label htmlFor="message">
|
||||
Message <span className="text-muted-foreground">(Optional)</span>
|
||||
<Trans>
|
||||
Message <span className="text-muted-foreground">(Optional)</span>
|
||||
</Trans>
|
||||
</Label>
|
||||
|
||||
<Textarea
|
||||
@ -111,16 +116,12 @@ export const AddSubjectFormPartial = ({
|
||||
</DocumentFlowFormContainerContent>
|
||||
|
||||
<DocumentFlowFormContainerFooter>
|
||||
<DocumentFlowFormContainerStep
|
||||
title={documentFlow.title}
|
||||
step={currentStep}
|
||||
maxStep={totalSteps}
|
||||
/>
|
||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||
|
||||
<DocumentFlowFormContainerActions
|
||||
loading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
goNextLabel={document.status === DocumentStatus.DRAFT ? 'Send' : 'Update'}
|
||||
goNextLabel={document.status === DocumentStatus.DRAFT ? msg`Send` : msg`Update`}
|
||||
onGoBackClick={previousStep}
|
||||
onGoNextClick={() => void onFormSubmit()}
|
||||
/>
|
||||
|
||||
@ -3,6 +3,9 @@
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
@ -33,19 +36,21 @@ export const DocumentFlowFormContainer = ({
|
||||
};
|
||||
|
||||
export type DocumentFlowFormContainerHeaderProps = {
|
||||
title: string;
|
||||
description: string;
|
||||
title: MessageDescriptor;
|
||||
description: MessageDescriptor;
|
||||
};
|
||||
|
||||
export const DocumentFlowFormContainerHeader = ({
|
||||
title,
|
||||
description,
|
||||
}: DocumentFlowFormContainerHeaderProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="text-foreground text-2xl font-semibold">{title}</h3>
|
||||
<h3 className="text-foreground text-2xl font-semibold">{_(title)}</h3>
|
||||
|
||||
<p className="text-muted-foreground mt-2 text-sm">{description}</p>
|
||||
<p className="text-muted-foreground mt-2 text-sm">{_(description)}</p>
|
||||
|
||||
<hr className="border-border mb-8 mt-4" />
|
||||
</>
|
||||
@ -88,7 +93,6 @@ export const DocumentFlowFormContainerFooter = ({
|
||||
};
|
||||
|
||||
export type DocumentFlowFormContainerStepProps = {
|
||||
title: string;
|
||||
step: number;
|
||||
maxStep: number;
|
||||
};
|
||||
@ -100,7 +104,9 @@ export const DocumentFlowFormContainerStep = ({
|
||||
return (
|
||||
<div>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Step <span>{`${step} of ${maxStep}`}</span>
|
||||
<Trans>
|
||||
Step <span>{`${step} of ${maxStep}`}</span>
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<div className="bg-muted relative mt-4 h-[2px] rounded-md">
|
||||
@ -120,8 +126,8 @@ export const DocumentFlowFormContainerStep = ({
|
||||
export type DocumentFlowFormContainerActionsProps = {
|
||||
canGoBack?: boolean;
|
||||
canGoNext?: boolean;
|
||||
goNextLabel?: string;
|
||||
goBackLabel?: string;
|
||||
goNextLabel?: MessageDescriptor;
|
||||
goBackLabel?: MessageDescriptor;
|
||||
onGoBackClick?: () => void;
|
||||
onGoNextClick?: () => void;
|
||||
loading?: boolean;
|
||||
@ -132,14 +138,15 @@ export type DocumentFlowFormContainerActionsProps = {
|
||||
export const DocumentFlowFormContainerActions = ({
|
||||
canGoBack = true,
|
||||
canGoNext = true,
|
||||
goNextLabel = 'Continue',
|
||||
goBackLabel = 'Go Back',
|
||||
goNextLabel = msg`Continue`,
|
||||
goBackLabel = msg`Go Back`,
|
||||
onGoBackClick,
|
||||
onGoNextClick,
|
||||
loading,
|
||||
disabled,
|
||||
disableNextStep = false,
|
||||
}: DocumentFlowFormContainerActionsProps) => {
|
||||
const { _ } = useLingui();
|
||||
return (
|
||||
<div className="mt-4 flex gap-x-4">
|
||||
<Button
|
||||
@ -150,7 +157,7 @@ export const DocumentFlowFormContainerActions = ({
|
||||
disabled={disabled || loading || !canGoBack || !onGoBackClick}
|
||||
onClick={onGoBackClick}
|
||||
>
|
||||
{goBackLabel}
|
||||
{_(goBackLabel)}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -161,7 +168,7 @@ export const DocumentFlowFormContainerActions = ({
|
||||
loading={loading}
|
||||
onClick={onGoNextClick}
|
||||
>
|
||||
{goNextLabel}
|
||||
{_(goNextLabel)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import {
|
||||
CalendarDays,
|
||||
CheckSquare,
|
||||
@ -48,7 +49,7 @@ export const FieldIcon = ({
|
||||
fontCaveatClassName,
|
||||
)}
|
||||
>
|
||||
Signature
|
||||
<Trans>Signature</Trans>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
|
||||
@ -5,6 +5,9 @@ import { forwardRef, useEffect, useState } from 'react';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import {
|
||||
@ -37,8 +40,8 @@ import { TextFieldAdvancedSettings } from './field-items-advanced-settings/text-
|
||||
|
||||
export type FieldAdvancedSettingsProps = {
|
||||
teamId?: number;
|
||||
title: string;
|
||||
description: string;
|
||||
title: MessageDescriptor;
|
||||
description: MessageDescriptor;
|
||||
field: FieldFormType;
|
||||
fields: FieldFormType[];
|
||||
onAdvancedSettings?: () => void;
|
||||
@ -121,7 +124,9 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const params = useParams();
|
||||
const pathname = usePathname();
|
||||
const id = params?.id;
|
||||
@ -150,17 +155,7 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
|
||||
|
||||
const doesFieldExist = (!!document || !!template) && field.nativeId !== undefined;
|
||||
|
||||
const { data: fieldData } = trpc.field.getField.useQuery(
|
||||
{
|
||||
fieldId: Number(field.nativeId),
|
||||
teamId,
|
||||
},
|
||||
{
|
||||
enabled: doesFieldExist,
|
||||
},
|
||||
);
|
||||
|
||||
const fieldMeta = fieldData?.fieldMeta;
|
||||
const fieldMeta = field?.fieldMeta;
|
||||
|
||||
const localStorageKey = `field_${field.formId}_${field.type}`;
|
||||
|
||||
@ -218,8 +213,8 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
|
||||
console.error('Failed to save to localStorage:', error);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Failed to save settings.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`Failed to save settings.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -288,8 +283,8 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
|
||||
</DocumentFlowFormContainerContent>
|
||||
<DocumentFlowFormContainerFooter className="mt-auto">
|
||||
<DocumentFlowFormContainerActions
|
||||
goNextLabel="Save"
|
||||
goBackLabel="Cancel"
|
||||
goNextLabel={msg`Save`}
|
||||
goBackLabel={msg`Cancel`}
|
||||
onGoBackClick={onAdvancedSettings}
|
||||
onGoNextClick={handleOnGoNextClick}
|
||||
disableNextStep={errors.length > 0}
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { ChevronDown, ChevronUp, Trash } from 'lucide-react';
|
||||
|
||||
import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
|
||||
@ -35,6 +37,8 @@ export const CheckboxFieldAdvancedSettings = ({
|
||||
handleFieldChange,
|
||||
handleErrors,
|
||||
}: CheckboxFieldAdvancedSettingsProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const [showValidation, setShowValidation] = useState(false);
|
||||
const [values, setValues] = useState(fieldState.values ?? [{ id: 1, checked: false, value: '' }]);
|
||||
const [readOnly, setReadOnly] = useState(fieldState.readOnly ?? false);
|
||||
@ -122,13 +126,15 @@ export const CheckboxFieldAdvancedSettings = ({
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-row items-center gap-x-4">
|
||||
<div className="flex w-2/3 flex-col">
|
||||
<Label>Validation</Label>
|
||||
<Label>
|
||||
<Trans>Validation</Trans>
|
||||
</Label>
|
||||
<Select
|
||||
value={fieldState.validationRule}
|
||||
onValueChange={(val) => handleToggleChange('validationRule', val)}
|
||||
>
|
||||
<SelectTrigger className="text-muted-foreground bg-background mt-2 w-full">
|
||||
<SelectValue placeholder="Select at least" />
|
||||
<SelectValue placeholder={_(msg`Select at least`)} />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper">
|
||||
{checkboxValidationRules.map((item, index) => (
|
||||
@ -145,7 +151,7 @@ export const CheckboxFieldAdvancedSettings = ({
|
||||
onValueChange={(val) => handleToggleChange('validationLength', val)}
|
||||
>
|
||||
<SelectTrigger className="text-muted-foreground bg-background mt-2 w-full">
|
||||
<SelectValue placeholder="Pick a number" />
|
||||
<SelectValue placeholder={_(msg`Pick a number`)} />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper">
|
||||
{checkboxValidationLength.map((item, index) => (
|
||||
@ -164,7 +170,9 @@ export const CheckboxFieldAdvancedSettings = ({
|
||||
checked={fieldState.required}
|
||||
onCheckedChange={(checked) => handleToggleChange('required', checked)}
|
||||
/>
|
||||
<Label>Required field</Label>
|
||||
<Label>
|
||||
<Trans>Required field</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Switch
|
||||
@ -172,7 +180,9 @@ export const CheckboxFieldAdvancedSettings = ({
|
||||
checked={fieldState.readOnly}
|
||||
onCheckedChange={(checked) => handleToggleChange('readOnly', checked)}
|
||||
/>
|
||||
<Label>Read only</Label>
|
||||
<Label>
|
||||
<Trans>Read only</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
@ -181,7 +191,9 @@ export const CheckboxFieldAdvancedSettings = ({
|
||||
onClick={() => setShowValidation((prev) => !prev)}
|
||||
>
|
||||
<span className="flex w-full flex-row justify-between">
|
||||
<span className="flex items-center">Checkbox values</span>
|
||||
<span className="flex items-center">
|
||||
<Trans>Checkbox values</Trans>
|
||||
</span>
|
||||
{showValidation ? <ChevronUp /> : <ChevronDown />}
|
||||
</span>
|
||||
</Button>
|
||||
@ -215,7 +227,7 @@ export const CheckboxFieldAdvancedSettings = ({
|
||||
variant="outline"
|
||||
onClick={addValue}
|
||||
>
|
||||
Add another value
|
||||
<Trans>Add another value</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { ChevronDown, ChevronUp, Trash } from 'lucide-react';
|
||||
|
||||
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
|
||||
@ -32,6 +34,8 @@ export const DropdownFieldAdvancedSettings = ({
|
||||
handleFieldChange,
|
||||
handleErrors,
|
||||
}: DropdownFieldAdvancedSettingsProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const [showValidation, setShowValidation] = useState(false);
|
||||
const [values, setValues] = useState(fieldState.values ?? [{ value: 'Option 1' }]);
|
||||
const [readOnly, setReadOnly] = useState(fieldState.readOnly ?? false);
|
||||
@ -87,7 +91,9 @@ export const DropdownFieldAdvancedSettings = ({
|
||||
return (
|
||||
<div className="text-dark flex flex-col gap-4">
|
||||
<div>
|
||||
<Label>Select default option</Label>
|
||||
<Label>
|
||||
<Trans>Select default option</Trans>
|
||||
</Label>
|
||||
<Select
|
||||
defaultValue={defaultValue}
|
||||
onValueChange={(val) => {
|
||||
@ -96,7 +102,7 @@ export const DropdownFieldAdvancedSettings = ({
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="text-muted-foreground bg-background mt-2 w-full">
|
||||
<SelectValue defaultValue={defaultValue} placeholder="-- Select --" />
|
||||
<SelectValue defaultValue={defaultValue} placeholder={`-- ${_(msg`Select`)} --`} />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper">
|
||||
{values.map((item, index) => (
|
||||
@ -117,7 +123,9 @@ export const DropdownFieldAdvancedSettings = ({
|
||||
checked={fieldState.required}
|
||||
onCheckedChange={(checked) => handleToggleChange('required', checked)}
|
||||
/>
|
||||
<Label>Required field</Label>
|
||||
<Label>
|
||||
<Trans>Required field</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Switch
|
||||
@ -125,7 +133,9 @@ export const DropdownFieldAdvancedSettings = ({
|
||||
checked={fieldState.readOnly}
|
||||
onCheckedChange={(checked) => handleToggleChange('readOnly', checked)}
|
||||
/>
|
||||
<Label>Read only</Label>
|
||||
<Label>
|
||||
<Trans>Read only</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
@ -134,7 +144,9 @@ export const DropdownFieldAdvancedSettings = ({
|
||||
onClick={() => setShowValidation((prev) => !prev)}
|
||||
>
|
||||
<span className="flex w-full flex-row justify-between">
|
||||
<span className="flex items-center">Dropdown options</span>
|
||||
<span className="flex items-center">
|
||||
<Trans>Dropdown options</Trans>
|
||||
</span>
|
||||
{showValidation ? <ChevronUp /> : <ChevronDown />}
|
||||
</span>
|
||||
</Button>
|
||||
@ -162,7 +174,7 @@ export const DropdownFieldAdvancedSettings = ({
|
||||
variant="outline"
|
||||
onClick={addValue}
|
||||
>
|
||||
Add another option
|
||||
<Trans>Add another option</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||
|
||||
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
|
||||
@ -31,6 +33,8 @@ export const NumberFieldAdvancedSettings = ({
|
||||
handleFieldChange,
|
||||
handleErrors,
|
||||
}: NumberFieldAdvancedSettingsProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const [showValidation, setShowValidation] = useState(false);
|
||||
|
||||
const handleInput = (field: keyof NumberFieldMeta, value: string | boolean) => {
|
||||
@ -56,43 +60,51 @@ export const NumberFieldAdvancedSettings = ({
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<Label>Label</Label>
|
||||
<Label>
|
||||
<Trans>Label</Trans>
|
||||
</Label>
|
||||
<Input
|
||||
id="label"
|
||||
className="bg-background mt-2"
|
||||
placeholder="Label"
|
||||
placeholder={_(msg`Label`)}
|
||||
value={fieldState.label}
|
||||
onChange={(e) => handleFieldChange('label', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="mt-4">Placeholder</Label>
|
||||
<Label className="mt-4">
|
||||
<Trans>Placeholder</Trans>
|
||||
</Label>
|
||||
<Input
|
||||
id="placeholder"
|
||||
className="bg-background mt-2"
|
||||
placeholder="Placeholder"
|
||||
placeholder={_(msg`Placeholder`)}
|
||||
value={fieldState.placeholder}
|
||||
onChange={(e) => handleFieldChange('placeholder', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="mt-4">Value</Label>
|
||||
<Label className="mt-4">
|
||||
<Trans>Value</Trans>
|
||||
</Label>
|
||||
<Input
|
||||
id="value"
|
||||
className="bg-background mt-2"
|
||||
placeholder="Value"
|
||||
placeholder={_(msg`Value`)}
|
||||
value={fieldState.value}
|
||||
onChange={(e) => handleInput('value', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Number format</Label>
|
||||
<Label>
|
||||
<Trans>Number format</Trans>
|
||||
</Label>
|
||||
<Select
|
||||
value={fieldState.numberFormat}
|
||||
onValueChange={(val) => handleInput('numberFormat', val)}
|
||||
>
|
||||
<SelectTrigger className="text-muted-foreground bg-background mt-2 w-full">
|
||||
<SelectValue placeholder="Field format" />
|
||||
<SelectValue placeholder={_(msg`Field format`)} />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper">
|
||||
{numberFormatValues.map((item, index) => (
|
||||
@ -110,7 +122,9 @@ export const NumberFieldAdvancedSettings = ({
|
||||
checked={fieldState.required}
|
||||
onCheckedChange={(checked) => handleInput('required', checked)}
|
||||
/>
|
||||
<Label>Required field</Label>
|
||||
<Label>
|
||||
<Trans>Required field</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Switch
|
||||
@ -118,7 +132,9 @@ export const NumberFieldAdvancedSettings = ({
|
||||
checked={fieldState.readOnly}
|
||||
onCheckedChange={(checked) => handleInput('readOnly', checked)}
|
||||
/>
|
||||
<Label>Read only</Label>
|
||||
<Label>
|
||||
<Trans>Read only</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
@ -127,14 +143,18 @@ export const NumberFieldAdvancedSettings = ({
|
||||
onClick={() => setShowValidation((prev) => !prev)}
|
||||
>
|
||||
<span className="flex w-full flex-row justify-between">
|
||||
<span className="flex items-center">Validation</span>
|
||||
<span className="flex items-center">
|
||||
<Trans>Validation</Trans>
|
||||
</span>
|
||||
{showValidation ? <ChevronUp /> : <ChevronDown />}
|
||||
</span>
|
||||
</Button>
|
||||
{showValidation && (
|
||||
<div className="mb-4 flex flex-row gap-x-4">
|
||||
<div className="flex flex-col">
|
||||
<Label className="mt-4">Min</Label>
|
||||
<Label className="mt-4">
|
||||
<Trans>Min</Trans>
|
||||
</Label>
|
||||
<Input
|
||||
id="minValue"
|
||||
className="bg-background mt-2"
|
||||
@ -144,7 +164,9 @@ export const NumberFieldAdvancedSettings = ({
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<Label className="mt-4">Max</Label>
|
||||
<Label className="mt-4">
|
||||
<Trans>Max</Trans>
|
||||
</Label>
|
||||
<Input
|
||||
id="maxValue"
|
||||
className="bg-background mt-2"
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { ChevronDown, ChevronUp, Trash } from 'lucide-react';
|
||||
|
||||
import { validateRadioField } from '@documenso/lib/advanced-fields-validation/validate-radio';
|
||||
@ -107,7 +108,9 @@ export const RadioFieldAdvancedSettings = ({
|
||||
checked={fieldState.required}
|
||||
onCheckedChange={(checked) => handleToggleChange('required', checked)}
|
||||
/>
|
||||
<Label>Required field</Label>
|
||||
<Label>
|
||||
<Trans>Required field</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Switch
|
||||
@ -115,7 +118,9 @@ export const RadioFieldAdvancedSettings = ({
|
||||
checked={fieldState.readOnly}
|
||||
onCheckedChange={(checked) => handleToggleChange('readOnly', checked)}
|
||||
/>
|
||||
<Label>Read only</Label>
|
||||
<Label>
|
||||
<Trans>Read only</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
@ -124,7 +129,9 @@ export const RadioFieldAdvancedSettings = ({
|
||||
onClick={() => setShowValidation((prev) => !prev)}
|
||||
>
|
||||
<span className="flex w-full flex-row justify-between">
|
||||
<span className="flex items-center">Radio values</span>
|
||||
<span className="flex items-center">
|
||||
<Trans>Radio values</Trans>
|
||||
</span>
|
||||
{showValidation ? <ChevronUp /> : <ChevronDown />}
|
||||
</span>
|
||||
</Button>
|
||||
@ -157,7 +164,7 @@ export const RadioFieldAdvancedSettings = ({
|
||||
variant="outline"
|
||||
onClick={addValue}
|
||||
>
|
||||
Add another value
|
||||
<Trans>Add another value</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { validateTextField } from '@documenso/lib/advanced-fields-validation/validate-text';
|
||||
import { type TTextFieldMeta as TextFieldMeta } from '@documenso/lib/types/field-meta';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
@ -16,6 +19,8 @@ export const TextFieldAdvancedSettings = ({
|
||||
handleFieldChange,
|
||||
handleErrors,
|
||||
}: TextFieldAdvancedSettingsProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const handleInput = (field: keyof TextFieldMeta, value: string | boolean) => {
|
||||
const text = field === 'text' ? String(value) : fieldState.text || '';
|
||||
const limit =
|
||||
@ -36,45 +41,53 @@ export const TextFieldAdvancedSettings = ({
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<Label>Label</Label>
|
||||
<Label>
|
||||
<Trans>Label</Trans>
|
||||
</Label>
|
||||
<Input
|
||||
id="label"
|
||||
className="bg-background mt-2"
|
||||
placeholder="Field label"
|
||||
placeholder={_(msg`Field label`)}
|
||||
value={fieldState.label}
|
||||
onChange={(e) => handleFieldChange('label', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="mt-4">Placeholder</Label>
|
||||
<Label className="mt-4">
|
||||
<Trans>Placeholder</Trans>
|
||||
</Label>
|
||||
<Input
|
||||
id="placeholder"
|
||||
className="bg-background mt-2"
|
||||
placeholder="Field placeholder"
|
||||
placeholder={_(msg`Field placeholder`)}
|
||||
value={fieldState.placeholder}
|
||||
onChange={(e) => handleFieldChange('placeholder', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="mt-4">Add text</Label>
|
||||
<Label className="mt-4">
|
||||
<Trans>Add text</Trans>
|
||||
</Label>
|
||||
<Textarea
|
||||
id="text"
|
||||
className="bg-background mt-2"
|
||||
placeholder="Add text to the field"
|
||||
placeholder={_(msg`Add text to the field`)}
|
||||
value={fieldState.text}
|
||||
onChange={(e) => handleInput('text', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>Character Limit</Label>
|
||||
<Label>
|
||||
<Trans>Character Limit</Trans>
|
||||
</Label>
|
||||
<Input
|
||||
id="characterLimit"
|
||||
type="number"
|
||||
min={0}
|
||||
className="bg-background mt-2"
|
||||
placeholder="Field character limit"
|
||||
placeholder={_(msg`Field character limit`)}
|
||||
value={fieldState.characterLimit}
|
||||
onChange={(e) => handleInput('characterLimit', e.target.value)}
|
||||
/>
|
||||
@ -87,7 +100,9 @@ export const TextFieldAdvancedSettings = ({
|
||||
checked={fieldState.required}
|
||||
onCheckedChange={(checked) => handleInput('required', checked)}
|
||||
/>
|
||||
<Label>Required field</Label>
|
||||
<Label>
|
||||
<Trans>Required field</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Switch
|
||||
@ -95,7 +110,9 @@ export const TextFieldAdvancedSettings = ({
|
||||
checked={fieldState.readOnly}
|
||||
onCheckedChange={(checked) => handleInput('readOnly', checked)}
|
||||
/>
|
||||
<Label>Read only</Label>
|
||||
<Label>
|
||||
<Trans>Read only</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { DialogClose } from '@radix-ui/react-dialog';
|
||||
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@ -25,18 +26,22 @@ export const MissingSignatureFieldDialog = ({
|
||||
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-lg" position="center">
|
||||
<DialogHeader>
|
||||
<DialogTitle>No signature field found</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>No signature field found</Trans>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
<p className="mt-2">
|
||||
Some signers have not been assigned a signature field. Please assign at least 1
|
||||
signature field to each signer before proceeding.
|
||||
<Trans>
|
||||
Some signers have not been assigned a signature field. Please assign at least 1
|
||||
signature field to each signer before proceeding.
|
||||
</Trans>
|
||||
</p>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button type="button" variant="secondary">
|
||||
Close
|
||||
<Trans>Close</Trans>
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { Loader } from 'lucide-react';
|
||||
|
||||
import type { ButtonProps } from '../button';
|
||||
@ -30,16 +31,20 @@ export const SendDocumentActionDialog = ({
|
||||
<DialogTrigger asChild>
|
||||
<Button type="button" className={className}>
|
||||
{loading && <Loader className="text-documenso mr-2 h-5 w-5 animate-spin" />}
|
||||
Send
|
||||
<Trans>Send</Trans>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-center text-lg font-semibold">Send Document</DialogTitle>
|
||||
<DialogTitle className="text-center text-lg font-semibold">
|
||||
<Trans>Send Document</Trans>
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-center text-base">
|
||||
You are about to send this document to the recipients. Are you sure you want to
|
||||
continue?
|
||||
<Trans>
|
||||
You are about to send this document to the recipients. Are you sure you want to
|
||||
continue?
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@ -50,13 +55,13 @@ export const SendDocumentActionDialog = ({
|
||||
variant="secondary"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
{/* We would use DialogAction here but it interrupts the submit action */}
|
||||
<Button className={className} {...props}>
|
||||
{loading && <Loader className="mr-2 h-5 w-5 animate-spin" />}
|
||||
Send
|
||||
<Trans>Send</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
@ -58,8 +59,8 @@ export const FRIENDLY_FIELD_TYPE: Record<FieldType, string> = {
|
||||
};
|
||||
|
||||
export interface DocumentFlowStep {
|
||||
title: string;
|
||||
description: string;
|
||||
title: MessageDescriptor;
|
||||
description: MessageDescriptor;
|
||||
stepIndex?: number;
|
||||
onBackStep?: () => unknown;
|
||||
onNextStep?: () => unknown;
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
@ -30,6 +32,8 @@ export const PasswordDialog = ({
|
||||
onPasswordSubmit,
|
||||
isError,
|
||||
}: PasswordDialogProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const form = useForm<TPasswordDialogFormSchema>({
|
||||
defaultValues: {
|
||||
password: defaultPassword ?? '',
|
||||
@ -45,7 +49,7 @@ export const PasswordDialog = ({
|
||||
if (isError) {
|
||||
form.setError('password', {
|
||||
type: 'manual',
|
||||
message: 'The password you have entered is incorrect. Please try again.',
|
||||
message: _(msg`The password you have entered is incorrect. Please try again.`),
|
||||
});
|
||||
}
|
||||
}, [form, isError]);
|
||||
@ -54,10 +58,14 @@ export const PasswordDialog = ({
|
||||
<Dialog open={open}>
|
||||
<DialogContent className="w-full max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Password Required</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>Password Required</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="text-muted-foreground">
|
||||
This document is password protected. Please enter the password to view the document.
|
||||
<Trans>
|
||||
This document is password protected. Please enter the password to view the document.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@ -73,7 +81,7 @@ export const PasswordDialog = ({
|
||||
<Input
|
||||
type="password"
|
||||
className="bg-background"
|
||||
placeholder="Enter password"
|
||||
placeholder={_(msg`Enter password`)}
|
||||
autoComplete="off"
|
||||
{...field}
|
||||
/>
|
||||
@ -85,7 +93,9 @@ export const PasswordDialog = ({
|
||||
/>
|
||||
|
||||
<div>
|
||||
<Button>Submit</Button>
|
||||
<Button>
|
||||
<Trans>Submit</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import { Check, ChevronsUpDown, Loader, XIcon } from 'lucide-react';
|
||||
|
||||
@ -24,7 +27,7 @@ type MultiSelectComboboxProps<T = OptionValue> = {
|
||||
emptySelectionPlaceholder?: React.ReactNode | string;
|
||||
enableClearAllButton?: boolean;
|
||||
loading?: boolean;
|
||||
inputPlaceholder?: string;
|
||||
inputPlaceholder?: MessageDescriptor;
|
||||
onChange: (_values: T[]) => void;
|
||||
options: ComboBoxOption<T>[];
|
||||
selectedValues: T[];
|
||||
@ -46,6 +49,8 @@ export function MultiSelectCombobox<T = OptionValue>({
|
||||
options,
|
||||
selectedValues,
|
||||
}: MultiSelectComboboxProps<T>) {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
const handleSelect = (selectedOption: T) => {
|
||||
@ -143,8 +148,10 @@ export function MultiSelectCombobox<T = OptionValue>({
|
||||
|
||||
<PopoverContent className="w-[200px] p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder={inputPlaceholder} />
|
||||
<CommandEmpty>No value found.</CommandEmpty>
|
||||
<CommandInput placeholder={inputPlaceholder && _(inputPlaceholder)} />
|
||||
<CommandEmpty>
|
||||
<Trans>No value found.</Trans>
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{options.map((option, i) => (
|
||||
<CommandItem key={i} onSelect={() => handleSelect(option.value)}>
|
||||
|
||||
@ -3,11 +3,19 @@
|
||||
import type { HTMLAttributes, MouseEvent, PointerEvent, TouchEvent } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { Undo2 } from 'lucide-react';
|
||||
import type { StrokeOptions } from 'perfect-freehand';
|
||||
import { getStroke } from 'perfect-freehand';
|
||||
|
||||
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
import { getSvgPathFromStroke } from './helper';
|
||||
@ -35,6 +43,7 @@ export const SignaturePad = ({
|
||||
const [isPressed, setIsPressed] = useState(false);
|
||||
const [lines, setLines] = useState<Point[][]>([]);
|
||||
const [currentLine, setCurrentLine] = useState<Point[]>([]);
|
||||
const [selectedColor, setSelectedColor] = useState('black');
|
||||
|
||||
const perfectFreehandOptions = useMemo(() => {
|
||||
const size = $el.current ? Math.min($el.current.height, $el.current.width) * 0.03 : 10;
|
||||
@ -84,6 +93,7 @@ export const SignaturePad = ({
|
||||
ctx.restore();
|
||||
ctx.imageSmoothingEnabled = true;
|
||||
ctx.imageSmoothingQuality = 'high';
|
||||
ctx.fillStyle = selectedColor;
|
||||
|
||||
lines.forEach((line) => {
|
||||
const pathData = new Path2D(
|
||||
@ -128,6 +138,7 @@ export const SignaturePad = ({
|
||||
|
||||
ctx.imageSmoothingEnabled = true;
|
||||
ctx.imageSmoothingQuality = 'high';
|
||||
ctx.fillStyle = selectedColor;
|
||||
|
||||
newLines.forEach((line) => {
|
||||
const pathData = new Path2D(
|
||||
@ -236,7 +247,13 @@ export const SignaturePad = ({
|
||||
>
|
||||
<canvas
|
||||
ref={$el}
|
||||
className={cn('relative block dark:invert', className)}
|
||||
className={cn(
|
||||
'relative block',
|
||||
{
|
||||
'dark:hue-rotate-180 dark:invert': selectedColor === 'black',
|
||||
},
|
||||
className,
|
||||
)}
|
||||
style={{ touchAction: 'none' }}
|
||||
onPointerMove={(event) => onMouseMove(event)}
|
||||
onPointerDown={(event) => onMouseDown(event)}
|
||||
@ -246,13 +263,51 @@ export const SignaturePad = ({
|
||||
{...props}
|
||||
/>
|
||||
|
||||
<div className="text-foreground absolute right-2 top-2 filter">
|
||||
<Select defaultValue={selectedColor} onValueChange={(value) => setSelectedColor(value)}>
|
||||
<SelectTrigger className="h-auto w-auto border-none p-1">
|
||||
<SelectValue placeholder="" />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent className="w-[150px]" align="end">
|
||||
<SelectItem value="black">
|
||||
<div className="text-muted-foreground flex items-center px-1 text-sm">
|
||||
<div className="border-border mr-2 h-5 w-5 rounded-full border-2 bg-black shadow-sm" />
|
||||
<Trans>Black</Trans>
|
||||
</div>
|
||||
</SelectItem>
|
||||
|
||||
<SelectItem value="red">
|
||||
<div className="text-muted-foreground flex items-center px-1 text-sm">
|
||||
<div className="border-border mr-2 h-5 w-5 rounded-full border-2 bg-[red] shadow-sm" />
|
||||
<Trans>Red</Trans>
|
||||
</div>
|
||||
</SelectItem>
|
||||
|
||||
<SelectItem value="blue">
|
||||
<div className="text-muted-foreground flex items-center px-1 text-sm">
|
||||
<div className="border-border mr-2 h-5 w-5 rounded-full border-2 bg-[blue] shadow-sm" />
|
||||
<Trans>Blue</Trans>
|
||||
</div>
|
||||
</SelectItem>
|
||||
|
||||
<SelectItem value="green">
|
||||
<div className="text-muted-foreground flex items-center px-1 text-sm">
|
||||
<div className="border-border mr-2 h-5 w-5 rounded-full border-2 bg-[green] shadow-sm" />
|
||||
<Trans>Green</Trans>
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-4 right-4 flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="focus-visible:ring-ring ring-offset-background text-muted-foreground/60 hover:text-muted-foreground rounded-full p-0 text-xs focus-visible:outline-none focus-visible:ring-2"
|
||||
onClick={() => onClearClick()}
|
||||
>
|
||||
Clear Signature
|
||||
<Trans>Clear Signature</Trans>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { Caveat } from 'next/font/google';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import {
|
||||
CalendarDays,
|
||||
CheckSquare,
|
||||
@ -21,7 +22,7 @@ import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||
import { useDocumentElement } from '@documenso/lib/client-only/hooks/use-document-element';
|
||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import { RECIPIENT_ROLES_DESCRIPTION_ENG } from '@documenso/lib/constants/recipient-roles';
|
||||
import {
|
||||
type TFieldMetaSchema as FieldMeta,
|
||||
ZFieldMetaSchema,
|
||||
@ -111,6 +112,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
recipients.find((recipient) => recipient.id === field.recipientId)?.email ?? '',
|
||||
signerToken:
|
||||
recipients.find((recipient) => recipient.id === field.recipientId)?.token ?? '',
|
||||
fieldMeta: field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : undefined,
|
||||
})),
|
||||
},
|
||||
});
|
||||
@ -155,6 +157,38 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
selectedSignerIndex === -1 ? 0 : selectedSignerIndex,
|
||||
);
|
||||
|
||||
const filterFieldsWithEmptyValues = (fields: typeof localFields, fieldType: string) =>
|
||||
fields
|
||||
.filter((field) => field.type === fieldType)
|
||||
.filter((field) => {
|
||||
if (field.fieldMeta && 'values' in field.fieldMeta) {
|
||||
return field.fieldMeta.values?.length === 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const emptyCheckboxFields = useMemo(
|
||||
() => filterFieldsWithEmptyValues(localFields, FieldType.CHECKBOX),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[localFields],
|
||||
);
|
||||
|
||||
const emptyRadioFields = useMemo(
|
||||
() => filterFieldsWithEmptyValues(localFields, FieldType.RADIO),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[localFields],
|
||||
);
|
||||
|
||||
const emptySelectFields = useMemo(
|
||||
() => filterFieldsWithEmptyValues(localFields, FieldType.DROPDOWN),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[localFields],
|
||||
);
|
||||
|
||||
const hasErrors =
|
||||
emptyCheckboxFields.length > 0 || emptyRadioFields.length > 0 || emptySelectFields.length > 0;
|
||||
|
||||
const [isFieldWithinBounds, setIsFieldWithinBounds] = useState(false);
|
||||
const [coords, setCoords] = useState({
|
||||
x: 0,
|
||||
@ -365,8 +399,8 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<>
|
||||
{showAdvancedSettings && currentField ? (
|
||||
<FieldAdvancedSettings
|
||||
title="Advanced settings"
|
||||
description={`Configure the ${FRIENDLY_FIELD_TYPE[currentField.type]} field`}
|
||||
title={msg`Advanced settings`}
|
||||
description={msg`Configure the ${FRIENDLY_FIELD_TYPE[currentField.type]} field`}
|
||||
field={currentField}
|
||||
fields={localFields}
|
||||
onAdvancedSettings={handleAdvancedSettings}
|
||||
@ -460,14 +494,15 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
|
||||
<CommandEmpty>
|
||||
<span className="text-muted-foreground inline-block px-4">
|
||||
No recipient matching this description was found.
|
||||
<Trans>No recipient matching this description was found.</Trans>
|
||||
</span>
|
||||
</CommandEmpty>
|
||||
|
||||
{recipientsByRoleToDisplay.map(([role, roleRecipients], roleIndex) => (
|
||||
<CommandGroup key={roleIndex}>
|
||||
<div className="text-muted-foreground mb-1 ml-2 mt-2 text-xs font-medium">
|
||||
{`${RECIPIENT_ROLES_DESCRIPTION[role].roleName}s`}
|
||||
{/* Todo: Translations - Add plural translations. */}
|
||||
{`${RECIPIENT_ROLES_DESCRIPTION_ENG[role].roleName}s`}
|
||||
</div>
|
||||
|
||||
{roleRecipients.length === 0 && (
|
||||
@ -475,7 +510,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
key={`${role}-empty`}
|
||||
className="text-muted-foreground/80 px-4 pb-4 pt-2.5 text-center text-xs"
|
||||
>
|
||||
No recipients with this role
|
||||
<Trans>No recipients with this role</Trans>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -542,7 +577,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
fontCaveat.className,
|
||||
)}
|
||||
>
|
||||
Signature
|
||||
<Trans>Signature</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -594,7 +629,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Mail className="h-4 w-4" />
|
||||
Email
|
||||
<Trans>Email</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -620,7 +655,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<User className="h-4 w-4" />
|
||||
Name
|
||||
<Trans>Name</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -646,7 +681,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<CalendarDays className="h-4 w-4" />
|
||||
Date
|
||||
<Trans>Date</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -672,7 +707,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Type className="h-4 w-4" />
|
||||
Text
|
||||
<Trans>Text</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -698,7 +733,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Hash className="h-4 w-4" />
|
||||
Number
|
||||
<Trans>Number</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -724,7 +759,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Disc className="h-4 w-4" />
|
||||
Radio
|
||||
<Trans>Radio</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -750,7 +785,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<CheckSquare className="h-4 w-4" />
|
||||
Checkbox
|
||||
<Trans>Checkbox</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -776,7 +811,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
Dropdown
|
||||
<Trans>Dropdown</Trans>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -786,17 +821,32 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
</div>
|
||||
</DocumentFlowFormContainerContent>
|
||||
|
||||
{hasErrors && (
|
||||
<div className="mt-4">
|
||||
<ul>
|
||||
<li className="text-sm text-red-500">
|
||||
<Trans>
|
||||
To proceed further, please set at least one value for the{' '}
|
||||
{emptyCheckboxFields.length > 0
|
||||
? 'Checkbox'
|
||||
: emptyRadioFields.length > 0
|
||||
? 'Radio'
|
||||
: 'Select'}{' '}
|
||||
field.
|
||||
</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DocumentFlowFormContainerFooter>
|
||||
<DocumentFlowFormContainerStep
|
||||
title={documentFlow.title}
|
||||
step={currentStep}
|
||||
maxStep={totalSteps}
|
||||
/>
|
||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||
|
||||
<DocumentFlowFormContainerActions
|
||||
loading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
goNextLabel="Save Template"
|
||||
goNextLabel={msg`Save Template`}
|
||||
disableNextStep={hasErrors}
|
||||
onGoBackClick={() => {
|
||||
previousStep();
|
||||
remove();
|
||||
|
||||
@ -5,6 +5,8 @@ import React, { useEffect, useId, useMemo, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Link2Icon, Plus, Trash } from 'lucide-react';
|
||||
import { useSession } from 'next-auth/react';
|
||||
@ -67,6 +69,8 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
const initialId = useId();
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { data: session } = useSession();
|
||||
|
||||
const user = session?.user;
|
||||
@ -290,13 +294,15 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
})}
|
||||
>
|
||||
{!showAdvancedSettings && index === 0 && (
|
||||
<FormLabel required>Email</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Email</Trans>
|
||||
</FormLabel>
|
||||
)}
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
placeholder={_(msg`Email`)}
|
||||
{...field}
|
||||
disabled={field.disabled || isSubmitting}
|
||||
onBlur={() => void handleOnBlur(index)}
|
||||
@ -318,11 +324,15 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
'col-span-4': showAdvancedSettings,
|
||||
})}
|
||||
>
|
||||
{!showAdvancedSettings && index === 0 && <FormLabel>Name</FormLabel>}
|
||||
{!showAdvancedSettings && index === 0 && (
|
||||
<FormLabel>
|
||||
<Trans>Name</Trans>
|
||||
</FormLabel>
|
||||
)}
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Name"
|
||||
placeholder={_(msg`Name`)}
|
||||
{...field}
|
||||
disabled={field.disabled || isSubmitting}
|
||||
onBlur={() => void handleOnBlur(index)}
|
||||
@ -382,12 +392,14 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="text-foreground z-9999 max-w-md p-4">
|
||||
<h3 className="text-foreground text-lg font-semibold">
|
||||
Direct link receiver
|
||||
<Trans>Direct link receiver</Trans>
|
||||
</h3>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
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>
|
||||
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>
|
||||
@ -423,7 +435,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
onClick={() => onAddPlaceholderRecipient()}
|
||||
>
|
||||
<Plus className="-ml-1 mr-2 h-5 w-5" />
|
||||
Add Placeholder Recipient
|
||||
<Trans>Add Placeholder Recipient</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -437,7 +449,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
onClick={() => onAddPlaceholderSelfRecipient()}
|
||||
>
|
||||
<Plus className="-ml-1 mr-2 h-5 w-5" />
|
||||
Add Myself
|
||||
<Trans>Add Myself</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -455,7 +467,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
className="text-muted-foreground ml-2 text-sm"
|
||||
htmlFor="showAdvancedRecipientSettings"
|
||||
>
|
||||
Show advanced settings
|
||||
<Trans>Show advanced settings</Trans>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
@ -464,11 +476,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
</DocumentFlowFormContainerContent>
|
||||
|
||||
<DocumentFlowFormContainerFooter>
|
||||
<DocumentFlowFormContainerStep
|
||||
title={documentFlow.title}
|
||||
step={currentStep}
|
||||
maxStep={totalSteps}
|
||||
/>
|
||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||
|
||||
<DocumentFlowFormContainerActions
|
||||
loading={isSubmitting}
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
@ -72,6 +74,8 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
template,
|
||||
onSubmit,
|
||||
}: AddTemplateSettingsFormProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const { documentAuthOption } = extractDocumentAuthMethods({
|
||||
documentAuth: template.authOptions,
|
||||
});
|
||||
@ -126,7 +130,9 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>Template title</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Template title</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Input className="bg-background" {...field} />
|
||||
@ -142,7 +148,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
Document access
|
||||
<Trans>Document access</Trans>
|
||||
<DocumentGlobalAuthAccessTooltip />
|
||||
</FormLabel>
|
||||
|
||||
@ -160,7 +166,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
Recipient action authentication
|
||||
<Trans>Recipient action authentication</Trans>
|
||||
<DocumentGlobalAuthActionTooltip />
|
||||
</FormLabel>
|
||||
|
||||
@ -175,7 +181,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
<Accordion type="multiple">
|
||||
<AccordionItem value="email-options" className="border-none">
|
||||
<AccordionTrigger className="text-foreground rounded border px-3 py-2 text-left hover:bg-neutral-200/30 hover:no-underline">
|
||||
Email Options
|
||||
<Trans>Email Options</Trans>
|
||||
</AccordionTrigger>
|
||||
|
||||
<AccordionContent className="text-muted-foreground -mx-1 px-1 pt-4 text-sm leading-relaxed [&>div]:pb-0">
|
||||
@ -186,7 +192,9 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Subject <span className="text-muted-foreground">(Optional)</span>
|
||||
<Trans>
|
||||
Subject <span className="text-muted-foreground">(Optional)</span>
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
@ -204,7 +212,9 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Message <span className="text-muted-foreground">(Optional)</span>
|
||||
<Trans>
|
||||
Message <span className="text-muted-foreground">(Optional)</span>
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
@ -225,7 +235,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
<Accordion type="multiple">
|
||||
<AccordionItem value="advanced-options" className="border-none">
|
||||
<AccordionTrigger className="text-foreground rounded border px-3 py-2 text-left hover:bg-neutral-200/30 hover:no-underline">
|
||||
Advanced Options
|
||||
<Trans>Advanced Options</Trans>
|
||||
</AccordionTrigger>
|
||||
|
||||
<AccordionContent className="text-muted-foreground -mx-1 px-1 pt-4 text-sm leading-relaxed">
|
||||
@ -236,15 +246,17 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
External ID{' '}
|
||||
<Trans>External ID</Trans>{' '}
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-muted-foreground max-w-xs">
|
||||
Add an external ID to the template. This can be used to identify in
|
||||
external systems.
|
||||
<Trans>
|
||||
Add an external ID to the template. This can be used to identify
|
||||
in external systems.
|
||||
</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormLabel>
|
||||
@ -263,7 +275,9 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
name="meta.dateFormat"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Date Format</FormLabel>
|
||||
<FormLabel>
|
||||
<Trans>Date Format</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Select {...field} onValueChange={field.onChange}>
|
||||
@ -291,7 +305,9 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
name="meta.timezone"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Time Zone</FormLabel>
|
||||
<FormLabel>
|
||||
<Trans>Time Zone</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Combobox
|
||||
@ -313,14 +329,16 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
Redirect URL{' '}
|
||||
<Trans>Redirect URL</Trans>{' '}
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-muted-foreground max-w-xs">
|
||||
Add a URL to redirect the user to once the document is signed
|
||||
<Trans>
|
||||
Add a URL to redirect the user to once the document is signed
|
||||
</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormLabel>
|
||||
@ -342,11 +360,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
</DocumentFlowFormContainerContent>
|
||||
|
||||
<DocumentFlowFormContainerFooter>
|
||||
<DocumentFlowFormContainerStep
|
||||
title={documentFlow.title}
|
||||
step={currentStep}
|
||||
maxStep={totalSteps}
|
||||
/>
|
||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||
|
||||
<DocumentFlowFormContainerActions
|
||||
loading={form.formState.isSubmitting}
|
||||
|
||||
Reference in New Issue
Block a user