mirror of
https://github.com/documenso/documenso.git
synced 2025-11-23 05:01:54 +10:00
Merge branch 'main' into feat/auto-placing-fields
This commit is contained in:
@ -27,13 +27,13 @@ type HandleOAuthAuthorizeUrlOptions = {
|
||||
/**
|
||||
* Optional prompt to pass to the authorization endpoint.
|
||||
*/
|
||||
prompt?: 'login' | 'consent' | 'select_account';
|
||||
prompt?: 'none' | 'login' | 'consent' | 'select_account';
|
||||
};
|
||||
|
||||
const oauthCookieMaxAge = 60 * 10; // 10 minutes.
|
||||
|
||||
export const handleOAuthAuthorizeUrl = async (options: HandleOAuthAuthorizeUrlOptions) => {
|
||||
const { c, clientOptions, redirectPath, prompt = 'login' } = options;
|
||||
const { c, clientOptions, redirectPath } = options;
|
||||
|
||||
if (!clientOptions.clientId || !clientOptions.clientSecret) {
|
||||
throw new AppError(AppErrorCode.NOT_SETUP);
|
||||
@ -63,7 +63,11 @@ export const handleOAuthAuthorizeUrl = async (options: HandleOAuthAuthorizeUrlOp
|
||||
);
|
||||
|
||||
// Pass the prompt to the authorization endpoint.
|
||||
url.searchParams.append('prompt', prompt);
|
||||
if (process.env.NEXT_PRIVATE_OIDC_PROMPT !== '') {
|
||||
const prompt = process.env.NEXT_PRIVATE_OIDC_PROMPT ?? 'login';
|
||||
|
||||
url.searchParams.append('prompt', prompt);
|
||||
}
|
||||
|
||||
setCookie(c, `${clientOptions.id}_oauth_state`, state, {
|
||||
...sessionCookieOptions,
|
||||
|
||||
@ -1,6 +1,17 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const SUPPORTED_LANGUAGE_CODES = ['de', 'en', 'fr', 'es', 'it', 'pl'] as const;
|
||||
export const SUPPORTED_LANGUAGE_CODES = [
|
||||
'de',
|
||||
'en',
|
||||
'fr',
|
||||
'es',
|
||||
'it',
|
||||
'pl',
|
||||
'pt-BR',
|
||||
'ja',
|
||||
'ko',
|
||||
'zh',
|
||||
] as const;
|
||||
|
||||
export const ZSupportedLanguageCodeSchema = z.enum(SUPPORTED_LANGUAGE_CODES).catch('en');
|
||||
|
||||
@ -54,6 +65,22 @@ export const SUPPORTED_LANGUAGES: Record<string, SupportedLanguage> = {
|
||||
short: 'pl',
|
||||
full: 'Polish',
|
||||
},
|
||||
'pt-BR': {
|
||||
short: 'pt-BR',
|
||||
full: 'Portuguese (Brazil)',
|
||||
},
|
||||
ja: {
|
||||
short: 'ja',
|
||||
full: 'Japanese',
|
||||
},
|
||||
ko: {
|
||||
short: 'ko',
|
||||
full: 'Korean',
|
||||
},
|
||||
zh: {
|
||||
short: 'zh',
|
||||
full: 'Chinese',
|
||||
},
|
||||
} satisfies Record<SupportedLanguageCodes, SupportedLanguage>;
|
||||
|
||||
export const isValidLanguageCode = (code: unknown): code is SupportedLanguageCodes =>
|
||||
|
||||
@ -25,7 +25,6 @@ import { signPdf } from '@documenso/signing';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../../errors/app-error';
|
||||
import { sendCompletedEmail } from '../../../server-only/document/send-completed-email';
|
||||
import PostHogServerClient from '../../../server-only/feature-flags/get-post-hog-server-client';
|
||||
import { getAuditLogsPdf } from '../../../server-only/htmltopdf/get-audit-logs-pdf';
|
||||
import { getCertificatePdf } from '../../../server-only/htmltopdf/get-certificate-pdf';
|
||||
import { addRejectionStampToPdf } from '../../../server-only/pdf/add-rejection-stamp-to-pdf';
|
||||
@ -62,171 +61,120 @@ export const run = async ({
|
||||
}) => {
|
||||
const { documentId, sendEmail = true, isResealing = false, requestMetadata } = payload;
|
||||
|
||||
const envelope = await prisma.envelope.findFirstOrThrow({
|
||||
where: {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
secondaryId: mapDocumentIdToSecondaryId(documentId),
|
||||
},
|
||||
include: {
|
||||
documentMeta: true,
|
||||
recipients: true,
|
||||
envelopeItems: {
|
||||
include: {
|
||||
documentData: true,
|
||||
field: {
|
||||
include: {
|
||||
signature: true,
|
||||
const { envelopeId, envelopeStatus, isRejected } = await io.runTask('seal-document', async () => {
|
||||
const envelope = await prisma.envelope.findFirstOrThrow({
|
||||
where: {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
secondaryId: mapDocumentIdToSecondaryId(documentId),
|
||||
},
|
||||
include: {
|
||||
documentMeta: true,
|
||||
recipients: true,
|
||||
envelopeItems: {
|
||||
include: {
|
||||
documentData: true,
|
||||
field: {
|
||||
include: {
|
||||
signature: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (envelope.envelopeItems.length === 0) {
|
||||
throw new Error('At least one envelope item required');
|
||||
}
|
||||
|
||||
const settings = await getTeamSettings({
|
||||
userId: envelope.userId,
|
||||
teamId: envelope.teamId,
|
||||
});
|
||||
|
||||
const isComplete =
|
||||
envelope.recipients.some((recipient) => recipient.signingStatus === SigningStatus.REJECTED) ||
|
||||
envelope.recipients.every((recipient) => recipient.signingStatus === SigningStatus.SIGNED);
|
||||
|
||||
if (!isComplete) {
|
||||
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
||||
message: 'Document is not complete',
|
||||
});
|
||||
}
|
||||
|
||||
// Seems silly but we need to do this in case the job is re-ran
|
||||
// after it has already run through the update task further below.
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
const documentStatus = await io.runTask('get-document-status', async () => {
|
||||
return envelope.status;
|
||||
});
|
||||
if (envelope.envelopeItems.length === 0) {
|
||||
throw new Error('At least one envelope item required');
|
||||
}
|
||||
|
||||
// This is the same case as above.
|
||||
let envelopeItems = await io.runTask(
|
||||
'get-document-data-id',
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
async () => {
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
return envelope.envelopeItems.map(({ field, ...rest }) => ({
|
||||
...rest,
|
||||
}));
|
||||
},
|
||||
);
|
||||
const settings = await getTeamSettings({
|
||||
userId: envelope.userId,
|
||||
teamId: envelope.teamId,
|
||||
});
|
||||
|
||||
if (envelopeItems.length < 1) {
|
||||
throw new Error(`Document ${envelope.id} has no envelope items`);
|
||||
}
|
||||
const isComplete =
|
||||
envelope.recipients.some((recipient) => recipient.signingStatus === SigningStatus.REJECTED) ||
|
||||
envelope.recipients.every((recipient) => recipient.signingStatus === SigningStatus.SIGNED);
|
||||
|
||||
const recipients = await prisma.recipient.findMany({
|
||||
where: {
|
||||
envelopeId: envelope.id,
|
||||
role: {
|
||||
not: RecipientRole.CC,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!isComplete) {
|
||||
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
||||
message: 'Document is not complete',
|
||||
});
|
||||
}
|
||||
|
||||
// Determine if the document has been rejected by checking if any recipient has rejected it
|
||||
const rejectedRecipient = recipients.find(
|
||||
(recipient) => recipient.signingStatus === SigningStatus.REJECTED,
|
||||
);
|
||||
let envelopeItems = envelope.envelopeItems;
|
||||
|
||||
const isRejected = Boolean(rejectedRecipient);
|
||||
if (envelopeItems.length < 1) {
|
||||
throw new Error(`Document ${envelope.id} has no envelope items`);
|
||||
}
|
||||
|
||||
// Get the rejection reason from the rejected recipient
|
||||
const rejectionReason = rejectedRecipient?.rejectionReason ?? '';
|
||||
|
||||
const fields = await prisma.field.findMany({
|
||||
where: {
|
||||
envelopeId: envelope.id,
|
||||
},
|
||||
include: {
|
||||
signature: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Skip the field check if the document is rejected
|
||||
if (!isRejected && fieldsContainUnsignedRequiredField(fields)) {
|
||||
throw new Error(`Document ${envelope.id} has unsigned required fields`);
|
||||
}
|
||||
|
||||
if (isResealing) {
|
||||
// If we're resealing we want to use the initial data for the document
|
||||
// so we aren't placing fields on top of eachother.
|
||||
envelopeItems = envelopeItems.map((envelopeItem) => ({
|
||||
...envelopeItem,
|
||||
documentData: {
|
||||
...envelopeItem.documentData,
|
||||
data: envelopeItem.documentData.initialData,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
if (!envelope.qrToken) {
|
||||
await prisma.envelope.update({
|
||||
const recipients = await prisma.recipient.findMany({
|
||||
where: {
|
||||
id: envelope.id,
|
||||
},
|
||||
data: {
|
||||
qrToken: prefixedId('qr'),
|
||||
envelopeId: envelope.id,
|
||||
role: {
|
||||
not: RecipientRole.CC,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const legacyDocumentId = mapSecondaryIdToDocumentId(envelope.secondaryId);
|
||||
// Determine if the document has been rejected by checking if any recipient has rejected it
|
||||
const rejectedRecipient = recipients.find(
|
||||
(recipient) => recipient.signingStatus === SigningStatus.REJECTED,
|
||||
);
|
||||
|
||||
const { certificateData, auditLogData } = await getCertificateAndAuditLogData({
|
||||
legacyDocumentId,
|
||||
documentMeta: envelope.documentMeta,
|
||||
settings,
|
||||
});
|
||||
const isRejected = Boolean(rejectedRecipient);
|
||||
|
||||
// !: The commented out code is our desired implementation but we're seemingly
|
||||
// !: running into issues with inngest parallelism in production.
|
||||
// !: Until this is resolved we will do this sequentially which is slower but
|
||||
// !: will actually work.
|
||||
// const decoratePromises: Array<Promise<{ oldDocumentDataId: string; newDocumentDataId: string }>> =
|
||||
// [];
|
||||
// Get the rejection reason from the rejected recipient
|
||||
const rejectionReason = rejectedRecipient?.rejectionReason ?? '';
|
||||
|
||||
// for (const envelopeItem of envelopeItems) {
|
||||
// const task = io.runTask(`decorate-${envelopeItem.id}`, async () => {
|
||||
// const envelopeItemFields = envelope.envelopeItems.find(
|
||||
// (item) => item.id === envelopeItem.id,
|
||||
// )?.field;
|
||||
const fields = await prisma.field.findMany({
|
||||
where: {
|
||||
envelopeId: envelope.id,
|
||||
},
|
||||
include: {
|
||||
signature: true,
|
||||
},
|
||||
});
|
||||
|
||||
// if (!envelopeItemFields) {
|
||||
// throw new Error(`Envelope item fields not found for envelope item ${envelopeItem.id}`);
|
||||
// }
|
||||
// Skip the field check if the document is rejected
|
||||
if (!isRejected && fieldsContainUnsignedRequiredField(fields)) {
|
||||
throw new Error(`Document ${envelope.id} has unsigned required fields`);
|
||||
}
|
||||
|
||||
// return decorateAndSignPdf({
|
||||
// envelope,
|
||||
// envelopeItem,
|
||||
// envelopeItemFields,
|
||||
// isRejected,
|
||||
// rejectionReason,
|
||||
// certificateData,
|
||||
// auditLogData,
|
||||
// });
|
||||
// });
|
||||
if (isResealing) {
|
||||
// If we're resealing we want to use the initial data for the document
|
||||
// so we aren't placing fields on top of eachother.
|
||||
envelopeItems = envelopeItems.map((envelopeItem) => ({
|
||||
...envelopeItem,
|
||||
documentData: {
|
||||
...envelopeItem.documentData,
|
||||
data: envelopeItem.documentData.initialData,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
// decoratePromises.push(task);
|
||||
// }
|
||||
if (!envelope.qrToken) {
|
||||
await prisma.envelope.update({
|
||||
where: {
|
||||
id: envelope.id,
|
||||
},
|
||||
data: {
|
||||
qrToken: prefixedId('qr'),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// const newDocumentData = await Promise.all(decoratePromises);
|
||||
const legacyDocumentId = mapSecondaryIdToDocumentId(envelope.secondaryId);
|
||||
|
||||
// TODO: Remove once parallelization is working
|
||||
const newDocumentData: Array<{ oldDocumentDataId: string; newDocumentDataId: string }> = [];
|
||||
const { certificateData, auditLogData } = await getCertificateAndAuditLogData({
|
||||
legacyDocumentId,
|
||||
documentMeta: envelope.documentMeta,
|
||||
settings,
|
||||
});
|
||||
|
||||
for (const envelopeItem of envelopeItems) {
|
||||
const result = await io.runTask(`decorate-${envelopeItem.id}`, async () => {
|
||||
const newDocumentData: Array<{ oldDocumentDataId: string; newDocumentDataId: string }> = [];
|
||||
|
||||
for (const envelopeItem of envelopeItems) {
|
||||
const envelopeItemFields = envelope.envelopeItems.find(
|
||||
(item) => item.id === envelopeItem.id,
|
||||
)?.field;
|
||||
@ -235,7 +183,7 @@ export const run = async ({
|
||||
throw new Error(`Envelope item fields not found for envelope item ${envelopeItem.id}`);
|
||||
}
|
||||
|
||||
return decorateAndSignPdf({
|
||||
const result = await decorateAndSignPdf({
|
||||
envelope,
|
||||
envelopeItem,
|
||||
envelopeItemFields,
|
||||
@ -244,25 +192,10 @@ export const run = async ({
|
||||
certificateData,
|
||||
auditLogData,
|
||||
});
|
||||
});
|
||||
|
||||
newDocumentData.push(result);
|
||||
}
|
||||
newDocumentData.push(result);
|
||||
}
|
||||
|
||||
const postHog = PostHogServerClient();
|
||||
|
||||
if (postHog) {
|
||||
postHog.capture({
|
||||
distinctId: nanoid(),
|
||||
event: 'App: Document Sealed',
|
||||
properties: {
|
||||
documentId: envelope.id,
|
||||
isRejected,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await io.runTask('update-document', async () => {
|
||||
await prisma.$transaction(async (tx) => {
|
||||
for (const { oldDocumentDataId, newDocumentDataId } of newDocumentData) {
|
||||
const newData = await tx.documentData.findFirstOrThrow({
|
||||
@ -304,18 +237,24 @@ export const run = async ({
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
envelopeId: envelope.id,
|
||||
envelopeStatus: envelope.status,
|
||||
isRejected,
|
||||
};
|
||||
});
|
||||
|
||||
await io.runTask('send-completed-email', async () => {
|
||||
let shouldSendCompletedEmail = sendEmail && !isResealing && !isRejected;
|
||||
|
||||
if (isResealing && !isDocumentCompleted(envelope.status)) {
|
||||
if (isResealing && !isDocumentCompleted(envelopeStatus)) {
|
||||
shouldSendCompletedEmail = sendEmail;
|
||||
}
|
||||
|
||||
if (shouldSendCompletedEmail) {
|
||||
await sendCompletedEmail({
|
||||
id: { type: 'envelopeId', id: envelope.id },
|
||||
id: { type: 'envelopeId', id: envelopeId },
|
||||
requestMetadata,
|
||||
});
|
||||
}
|
||||
@ -323,7 +262,7 @@ export const run = async ({
|
||||
|
||||
const updatedEnvelope = await prisma.envelope.findFirstOrThrow({
|
||||
where: {
|
||||
id: envelope.id,
|
||||
id: envelopeId,
|
||||
},
|
||||
include: {
|
||||
documentMeta: true,
|
||||
|
||||
@ -103,6 +103,7 @@ export const getDocumentAndSenderByToken = async ({
|
||||
select: {
|
||||
name: true,
|
||||
teamEmail: true,
|
||||
url: true,
|
||||
teamGlobalSettings: {
|
||||
select: {
|
||||
brandingEnabled: true,
|
||||
|
||||
@ -31,26 +31,16 @@ export const viewedDocument = async ({
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
envelope: {
|
||||
include: {
|
||||
documentMeta: true,
|
||||
recipients: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!recipient) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { envelope } = recipient;
|
||||
|
||||
await prisma.documentAuditLog.create({
|
||||
data: createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_VIEWED,
|
||||
envelopeId: envelope.id,
|
||||
envelopeId: recipient.envelopeId,
|
||||
user: {
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
@ -86,7 +76,7 @@ export const viewedDocument = async ({
|
||||
await tx.documentAuditLog.create({
|
||||
data: createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED,
|
||||
envelopeId: envelope.id,
|
||||
envelopeId: recipient.envelopeId,
|
||||
user: {
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
@ -103,6 +93,16 @@ export const viewedDocument = async ({
|
||||
});
|
||||
});
|
||||
|
||||
const envelope = await prisma.envelope.findUniqueOrThrow({
|
||||
where: {
|
||||
id: recipient.envelopeId,
|
||||
},
|
||||
include: {
|
||||
documentMeta: true,
|
||||
recipients: true,
|
||||
},
|
||||
});
|
||||
|
||||
await triggerWebhook({
|
||||
event: WebhookTriggerEvents.DOCUMENT_OPENED,
|
||||
data: ZWebhookDocumentSchema.parse(mapEnvelopeToWebhookDocumentPayload(envelope)),
|
||||
|
||||
@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: pl\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2025-11-17 02:33\n"
|
||||
"PO-Revision-Date: 2025-11-20 02:32\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Polish\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
|
||||
@ -179,7 +179,7 @@ msgstr "Sprawdź i {recipientActionVerb} dokument utworzony przez zespół {0}"
|
||||
#: apps/remix/app/components/general/envelope/envelope-drop-zone-wrapper.tsx
|
||||
#: apps/remix/app/components/general/document/document-upload-button-legacy.tsx
|
||||
msgid "{0} of {1} documents remaining this month."
|
||||
msgstr "{0} z {1} dokumentów pozostałych w tym miesiącu."
|
||||
msgstr "Pozostało {0} z {1} dokumentów w tym miesiącu."
|
||||
|
||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||
|
||||
10966
packages/lib/translations/pt-BR/web.po
Normal file
10966
packages/lib/translations/pt-BR/web.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -8,15 +8,17 @@ export type EnvelopeItemPdfUrlOptions =
|
||||
envelopeItem: Pick<EnvelopeItem, 'id' | 'envelopeId'>;
|
||||
token: string | undefined;
|
||||
version: 'original' | 'signed';
|
||||
presignToken?: undefined;
|
||||
}
|
||||
| {
|
||||
type: 'view';
|
||||
envelopeItem: Pick<EnvelopeItem, 'id' | 'envelopeId'>;
|
||||
token: string | undefined;
|
||||
presignToken?: string | undefined;
|
||||
};
|
||||
|
||||
export const getEnvelopeItemPdfUrl = (options: EnvelopeItemPdfUrlOptions) => {
|
||||
const { envelopeItem, token, type } = options;
|
||||
const { envelopeItem, token, type, presignToken } = options;
|
||||
|
||||
const { id, envelopeId } = envelopeItem;
|
||||
|
||||
@ -24,11 +26,11 @@ export const getEnvelopeItemPdfUrl = (options: EnvelopeItemPdfUrlOptions) => {
|
||||
const version = options.version;
|
||||
|
||||
return token
|
||||
? `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/token/${token}/envelopeItem/${id}/download/${version}`
|
||||
? `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/token/${token}/envelopeItem/${id}/download/${version}${presignToken ? `?presignToken=${presignToken}` : ''}`
|
||||
: `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/envelope/${envelopeId}/envelopeItem/${id}/download/${version}`;
|
||||
}
|
||||
|
||||
return token
|
||||
? `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/token/${token}/envelopeItem/${id}`
|
||||
? `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/token/${token}/envelopeItem/${id}${presignToken ? `?presignToken=${presignToken}` : ''}`
|
||||
: `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/envelope/${envelopeId}/envelopeItem/${id}`;
|
||||
};
|
||||
|
||||
@ -25,6 +25,7 @@ import { redistributeEnvelopeRoute } from './redistribute-envelope';
|
||||
import { setEnvelopeFieldsRoute } from './set-envelope-fields';
|
||||
import { setEnvelopeRecipientsRoute } from './set-envelope-recipients';
|
||||
import { signEnvelopeFieldRoute } from './sign-envelope-field';
|
||||
import { signingStatusEnvelopeRoute } from './signing-status-envelope';
|
||||
import { updateEnvelopeRoute } from './update-envelope';
|
||||
import { updateEnvelopeItemsRoute } from './update-envelope-items';
|
||||
import { useEnvelopeRoute } from './use-envelope';
|
||||
@ -72,4 +73,5 @@ export const envelopeRouter = router({
|
||||
duplicate: duplicateEnvelopeRoute,
|
||||
distribute: distributeEnvelopeRoute,
|
||||
redistribute: redistributeEnvelopeRoute,
|
||||
signingStatus: signingStatusEnvelopeRoute,
|
||||
});
|
||||
|
||||
@ -0,0 +1,82 @@
|
||||
import { DocumentStatus, EnvelopeType, RecipientRole, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { maybeAuthenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZSigningStatusEnvelopeRequestSchema,
|
||||
ZSigningStatusEnvelopeResponseSchema,
|
||||
} from './signing-status-envelope.types';
|
||||
|
||||
// Internal route - not intended for public API usage
|
||||
export const signingStatusEnvelopeRoute = maybeAuthenticatedProcedure
|
||||
.input(ZSigningStatusEnvelopeRequestSchema)
|
||||
.output(ZSigningStatusEnvelopeResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { token } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
token,
|
||||
},
|
||||
});
|
||||
|
||||
const envelope = await prisma.envelope.findFirst({
|
||||
where: {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
recipients: {
|
||||
some: {
|
||||
token,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
recipients: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
signingStatus: true,
|
||||
role: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!envelope) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Envelope not found',
|
||||
});
|
||||
}
|
||||
|
||||
// Check if envelope is rejected
|
||||
if (envelope.status === DocumentStatus.REJECTED) {
|
||||
return {
|
||||
status: 'REJECTED',
|
||||
};
|
||||
}
|
||||
|
||||
if (envelope.status === DocumentStatus.COMPLETED) {
|
||||
return {
|
||||
status: 'COMPLETED',
|
||||
};
|
||||
}
|
||||
|
||||
const isComplete =
|
||||
envelope.recipients.some((recipient) => recipient.signingStatus === SigningStatus.REJECTED) ||
|
||||
envelope.recipients.every(
|
||||
(recipient) =>
|
||||
recipient.role === RecipientRole.CC || recipient.signingStatus === SigningStatus.SIGNED,
|
||||
);
|
||||
|
||||
if (isComplete) {
|
||||
return {
|
||||
status: 'PROCESSING',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'PENDING',
|
||||
};
|
||||
});
|
||||
@ -0,0 +1,14 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const EnvelopeSigningStatus = z.enum(['PENDING', 'PROCESSING', 'COMPLETED', 'REJECTED']);
|
||||
|
||||
export const ZSigningStatusEnvelopeRequestSchema = z.object({
|
||||
token: z.string().describe('The recipient token to check the signing status for'),
|
||||
});
|
||||
|
||||
export const ZSigningStatusEnvelopeResponseSchema = z.object({
|
||||
status: EnvelopeSigningStatus.describe('The current signing status of the envelope'),
|
||||
});
|
||||
|
||||
export type TSigningStatusEnvelopeRequest = z.infer<typeof ZSigningStatusEnvelopeRequestSchema>;
|
||||
export type TSigningStatusEnvelopeResponse = z.infer<typeof ZSigningStatusEnvelopeResponseSchema>;
|
||||
@ -127,11 +127,11 @@ export const DocumentShareButton = ({
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={!token || !documentId}
|
||||
className={cn('flex-1 text-[11px]', className)}
|
||||
className={cn('h-11 w-full max-w-lg flex-1', className)}
|
||||
loading={isLoading}
|
||||
>
|
||||
{!isLoading && <Sparkles className="mr-2 h-5 w-5" />}
|
||||
<Trans>Share Signature Card</Trans>
|
||||
<Trans>Share</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
|
||||
@ -56,6 +56,7 @@ export type PDFViewerProps = {
|
||||
className?: string;
|
||||
envelopeItem: Pick<EnvelopeItem, 'id' | 'envelopeId'>;
|
||||
token: string | undefined;
|
||||
presignToken?: string | undefined;
|
||||
version: 'original' | 'signed';
|
||||
onDocumentLoad?: (_doc: LoadedPDFDocument) => void;
|
||||
onPageClick?: OnPDFViewerPageClick;
|
||||
@ -67,6 +68,7 @@ export const PDFViewer = ({
|
||||
className,
|
||||
envelopeItem,
|
||||
token,
|
||||
presignToken,
|
||||
version,
|
||||
onDocumentLoad,
|
||||
onPageClick,
|
||||
@ -166,6 +168,7 @@ export const PDFViewer = ({
|
||||
type: 'view',
|
||||
envelopeItem: envelopeItem,
|
||||
token,
|
||||
presignToken,
|
||||
});
|
||||
|
||||
const bytes = await fetch(documentUrl).then(async (res) => await res.arrayBuffer());
|
||||
|
||||
Reference in New Issue
Block a user