chore: dependency updates (#2229)

This commit is contained in:
Lucas Smith
2025-11-22 20:28:20 +11:00
committed by GitHub
parent 17c6098638
commit d2176627ca
103 changed files with 16694 additions and 72473 deletions

View File

@ -17,14 +17,13 @@
"dependencies": {
"@documenso/lib": "*",
"@documenso/prisma": "*",
"@ts-rest/core": "^3.52.0",
"@ts-rest/open-api": "^3.52.0",
"@ts-rest/serverless": "^3.52.0",
"@ts-rest/core": "^3.52.1",
"@ts-rest/open-api": "^3.52.1",
"@ts-rest/serverless": "^3.52.1",
"@types/swagger-ui-react": "^5.18.0",
"luxon": "^3.4.0",
"luxon": "^3.7.2",
"superjson": "^2.2.5",
"swagger-ui-react": "^5.21.0",
"ts-pattern": "^5.0.5",
"ts-pattern": "^5.9.0",
"zod": "^3.25.76"
}
}

View File

@ -232,19 +232,19 @@ test('[DOCUMENT_FLOW]: should be able to create a document with multiple recipie
await page.getByLabel('Email').nth(1).fill('user2@example.com');
await page.getByLabel('Name').nth(1).fill('User 2');
await page.locator('button[role="combobox"]').nth(1).click();
await page.getByRole('combobox').nth(1).click();
await page.getByLabel('Receives copy').click();
await page.getByRole('button', { name: 'Add Signer' }).click();
await page.getByLabel('Email').nth(2).fill('user3@example.com');
await page.getByLabel('Name').nth(2).fill('User 3');
await page.locator('button[role="combobox"]').nth(2).click();
await page.getByRole('combobox').nth(2).click();
await page.getByLabel('Needs to approve').click();
await page.getByRole('button', { name: 'Add Signer' }).click();
await page.getByLabel('Email').nth(3).fill('user4@example.com');
await page.getByLabel('Name').nth(3).fill('User 4');
await page.locator('button[role="combobox"]').nth(3).click();
await page.getByRole('combobox').nth(3).click();
await page.getByLabel('Needs to view').click();
await page.getByRole('button', { name: 'Continue' }).click();
@ -252,8 +252,8 @@ test('[DOCUMENT_FLOW]: should be able to create a document with multiple recipie
// Add fields
await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();
await page.locator('button[role="combobox"]').nth(0).click();
await page.getByTitle('User 1 (user1@example.com)').click();
await page.getByRole('combobox').first().click();
await page.getByRole('option', { name: 'User 1 (user1@example.com)' }).click();
await page.getByRole('button', { name: 'Signature' }).click();
await page.locator('canvas').click({
@ -271,8 +271,8 @@ test('[DOCUMENT_FLOW]: should be able to create a document with multiple recipie
},
});
await page.locator('button[role="combobox"]').nth(0).click();
await page.getByTitle('User 3 (user3@example.com)').click();
await page.getByRole('combobox').first().click();
await page.getByRole('option', { name: 'User 3 (user3@example.com)' }).click();
await page.getByRole('button', { name: 'Signature' }).click();
await page.locator('canvas').click({
@ -574,6 +574,7 @@ test('[DOCUMENT_FLOW]: should be able to create and sign a document with 3 recip
if (i > 1) {
await page.getByText(`User ${i} (user${i}@example.com)`).click();
}
await page.getByRole('button', { name: 'Signature' }).click();
await page.locator('canvas').click({
position: {

View File

@ -85,16 +85,18 @@ test('[DOCUMENTS]: deleting a completed document should not remove it from recip
// Open document action menu.
await page
.locator('tr', { hasText: 'Document 1 - Completed' })
.getByRole('cell', { name: 'Download' })
.getByRole('button')
.nth(1)
.getByTestId('document-table-action-btn')
.click();
await page.waitForTimeout(200);
// delete document
await page.getByRole('menuitem', { name: 'Delete' }).click();
await page.getByPlaceholder("Type 'delete' to confirm").fill('delete');
await page.getByRole('button', { name: 'Delete' }).click();
await page.waitForTimeout(2500);
await expect(page.getByRole('row', { name: /Document 1 - Completed/ })).not.toBeVisible();
await apiSignout({ page });
@ -126,13 +128,20 @@ test('[DOCUMENTS]: deleting a pending document should remove it from recipients'
});
// Open document action menu.
await page.locator('tr', { hasText: 'Document 1 - Pending' }).getByRole('button').nth(1).click();
await page
.locator('tr', { hasText: 'Document 1 - Pending' })
.getByTestId('document-table-action-btn')
.click();
await page.waitForTimeout(200);
// delete document
await page.getByRole('menuitem', { name: 'Delete' }).click();
await page.getByPlaceholder("Type 'delete' to confirm").fill('delete');
await page.getByRole('button', { name: 'Delete' }).click();
await page.waitForTimeout(2500);
await expect(page.getByRole('row', { name: /Document 1 - Pending/ })).not.toBeVisible();
// signout
@ -165,11 +174,15 @@ test('[DOCUMENTS]: deleting draft documents should permanently remove it', async
.getByTestId('document-table-action-btn')
.click();
await page.waitForTimeout(200);
// delete document
await page.getByRole('menuitem', { name: 'Delete' }).click();
await expect(page.getByPlaceholder("Type 'delete' to confirm")).not.toBeVisible();
await page.getByRole('button', { name: 'Delete' }).click();
await page.waitForTimeout(2500);
await expect(page.getByRole('row', { name: /Document 1 - Draft/ })).not.toBeVisible();
// Check document counts.
@ -195,11 +208,15 @@ test('[DOCUMENTS]: deleting pending documents should permanently remove it', asy
.getByTestId('document-table-action-btn')
.click();
await page.waitForTimeout(200);
// Delete document.
await page.getByRole('menuitem', { name: 'Delete' }).click();
await page.getByPlaceholder("Type 'delete' to confirm").fill('delete');
await page.getByRole('button', { name: 'Delete' }).click();
await page.waitForTimeout(2500);
await expect(page.getByRole('row', { name: /Document 1 - Pending/ })).not.toBeVisible();
// Check document counts.
@ -227,11 +244,15 @@ test('[DOCUMENTS]: deleting completed documents as an owner should hide it from
.getByTestId('document-table-action-btn')
.click();
await page.waitForTimeout(200);
// Delete document.
await page.getByRole('menuitem', { name: 'Delete' }).click();
await page.getByPlaceholder("Type 'delete' to confirm").fill('delete');
await page.getByRole('button', { name: 'Delete' }).click();
await page.waitForTimeout(2500);
// Check document counts.
await expect(page.getByRole('row', { name: /Document 1 - Completed/ })).not.toBeVisible();
await checkDocumentTabCount(page, 'Inbox', 0);
@ -303,7 +324,8 @@ test('[DOCUMENTS]: deleting documents as a recipient should only hide it for the
await page.getByRole('menuitem', { name: 'Hide' }).waitFor({ state: 'visible' });
await page.getByRole('menuitem', { name: 'Hide' }).click({ force: true });
await page.getByRole('button', { name: 'Hide' }).click({ force: true });
await page.waitForTimeout(2000);
await page.waitForTimeout(2500);
// Check document counts.
await expect(page.getByRole('row', { name: /Document 1 - Completed/ })).not.toBeVisible();

View File

@ -1,46 +1,30 @@
// sort-imports-ignore
// ---- PATCH pdfjs-dist's canvas require BEFORE importing it ----
import Module from 'module';
import { Canvas, Image } from 'skia-canvas';
// Intercept require('canvas') and return skia-canvas equivalents
const originalRequire = Module.prototype.require;
Module.prototype.require = function (path: string) {
if (path === 'canvas') {
return {
createCanvas: (width: number, height: number) => new Canvas(width, height),
Image, // needed by pdfjs-dist
};
}
// eslint-disable-next-line prefer-rest-params, @typescript-eslint/consistent-type-assertions
return originalRequire.apply(this, arguments as unknown as [string]);
};
import pixelMatch from 'pixelmatch';
import { PNG } from 'pngjs';
import { createCanvas } from '@napi-rs/canvas';
import type { TestInfo } from '@playwright/test';
import { expect, test } from '@playwright/test';
import { DocumentStatus, EnvelopeType } from '@prisma/client';
import fs from 'node:fs';
import path from 'node:path';
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.js';
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs';
import pixelMatch from 'pixelmatch';
import { PNG } from 'pngjs';
import { getEnvelopeItemPdfUrl } from '@documenso/lib/utils/envelope-download';
import { prisma } from '@documenso/prisma';
import { seedAlignmentTestDocument } from '@documenso/prisma/seed/initial-seed';
import { seedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../lib/constants/app';
import { isBase64Image } from '../../../lib/constants/signatures';
import { createApiToken } from '../../../lib/server-only/public-api/create-api-token';
import { RecipientRole } from '../../../prisma/generated/types';
import type {
TCreateEnvelopePayload,
TCreateEnvelopeResponse,
} from '../../../trpc/server/envelope-router/create-envelope.types';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../lib/constants/app';
import { createApiToken } from '../../../lib/server-only/public-api/create-api-token';
import { RecipientRole } from '../../../prisma/generated/types';
import { FIELD_META_TEST_FIELDS } from '../../constants/field-meta-pdf';
import { ALIGNMENT_TEST_FIELDS } from '../../constants/field-alignment-pdf';
import type { TDistributeEnvelopeRequest } from '../../../trpc/server/envelope-router/distribute-envelope.types';
import { isBase64Image } from '../../../lib/constants/signatures';
import { ALIGNMENT_TEST_FIELDS } from '../../constants/field-alignment-pdf';
import { FIELD_META_TEST_FIELDS } from '../../constants/field-meta-pdf';
import { apiSignin } from '../fixtures/authentication';
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
const baseUrl = `${WEBAPP_BASE_URL}/api/v2`;
@ -406,15 +390,20 @@ async function renderPdfToImage(pdfBytes: Uint8Array) {
const viewport = page.getViewport({ scale });
const virtualCanvas = new Canvas(viewport.width, viewport.height);
const context = virtualCanvas.getContext('2d');
context.imageSmoothingEnabled = false;
const canvas = createCanvas(viewport.width, viewport.height);
const canvasContext = canvas.getContext('2d');
canvasContext.imageSmoothingEnabled = false;
// @ts-expect-error skia-canvas context satisfies runtime requirements for pdfjs
await page.render({ canvasContext: context, viewport }).promise;
await page.render({
// @ts-expect-error @napi-rs/canvas satisfies runtime requirements for pdfjs
canvas,
// @ts-expect-error @napi-rs/canvas satisfies runtime requirements for pdfjs
canvasContext,
viewport,
}).promise;
return {
image: await virtualCanvas.toBuffer('png'),
image: await canvas.encode('png'),
// Rounded down because the certificate page somehow gives dimensions with decimals
width: Math.floor(viewport.width),

View File

@ -57,6 +57,9 @@ test.describe('Signing Certificate Tests', () => {
}
await page.getByRole('button', { name: 'Complete' }).click();
await page.waitForTimeout(1000);
await page.getByRole('button', { name: 'Sign' }).click({ force: true });
await page.waitForURL(`/sign/${recipient.token}/complete`);

View File

@ -87,9 +87,7 @@ test('[TEAMS]: check signature modes can be disabled', async ({ page }) => {
await page.getByRole('button', { name: 'Update' }).first().click();
// Wait for the update to complete
const toast = page.locator('li[role="status"][data-state="open"]').first();
await expect(toast).toBeVisible();
await expect(toast.getByText('Document preferences updated', { exact: true })).toBeVisible();
await expect(page.getByText('Document preferences updated', { exact: true })).toBeVisible();
const document = await seedTeamDocumentWithMeta(team);
@ -154,9 +152,7 @@ test('[TEAMS]: check signature modes work for templates', async ({ page }) => {
await page.getByRole('button', { name: 'Update' }).first().click();
// Wait for finish
const toast = page.locator('li[role="status"][data-state="open"]').first();
await expect(toast).toBeVisible();
await expect(toast.getByText('Document preferences updated', { exact: true })).toBeVisible();
await expect(page.getByText('Document preferences updated', { exact: true })).toBeVisible();
const template = await seedTeamTemplateWithMeta(team);

View File

@ -97,9 +97,10 @@ test.describe('AutoSave Fields Step', () => {
const fields = retrievedFields.fields;
expect(fields.length).toBe(3);
expect(fields[0].type).toBe('SIGNATURE');
expect(fields[1].type).toBe('TEXT');
expect(fields[2].type).toBe('SIGNATURE');
expect(fields.map((field) => field.type).toSorted()).toEqual(
['SIGNATURE', 'TEXT', 'SIGNATURE'].toSorted(),
);
}).toPass();
});
@ -237,10 +238,9 @@ test.describe('AutoSave Fields Step', () => {
const fields = retrievedFields.fields;
expect(fields.length).toBe(4);
expect(fields[0].type).toBe('SIGNATURE');
expect(fields[1].type).toBe('TEXT');
expect(fields[2].type).toBe('SIGNATURE');
expect(fields[3].type).toBe('SIGNATURE');
expect(fields.map((field) => field.type).toSorted()).toEqual(
['SIGNATURE', 'TEXT', 'SIGNATURE', 'SIGNATURE'].toSorted(),
);
}).toPass();
});
@ -292,8 +292,9 @@ test.describe('AutoSave Fields Step', () => {
const fields = retrievedTemplate.fields;
expect(fields.length).toBe(2);
expect(fields[0].type).toBe('SIGNATURE');
expect(fields[1].type).toBe('TEXT');
expect(fields.map((field) => field.type).toSorted()).toEqual(
['SIGNATURE', 'TEXT'].toSorted(),
);
const textField = fields[1];
expect(textField.fieldMeta).toBeDefined();

View File

@ -14,13 +14,14 @@
"devDependencies": {
"@documenso/lib": "*",
"@documenso/prisma": "*",
"@playwright/test": "1.52.0",
"@napi-rs/canvas": "^0.1.82",
"@playwright/test": "1.56.1",
"@types/node": "^20",
"@types/pngjs": "^6.0.5",
"pixelmatch": "^7.1.0",
"pngjs": "^7.0.0"
},
"dependencies": {
"start-server-and-test": "^2.0.12"
"start-server-and-test": "^2.1.3"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -72,7 +72,11 @@ export class AuthClient {
public async getSession() {
const response = await this.client['session-json'].$get();
await this.handleError(response);
if (!response.ok) {
const error = await response.json();
throw AppError.parseError(error);
}
const result = await response.json();
@ -82,13 +86,19 @@ export class AuthClient {
public async getSessions() {
const response = await this.client['sessions'].$get();
await this.handleError(response);
if (!response.ok) {
const error = await response.json();
throw AppError.parseError(error);
}
const result = await response.json();
return superjson.deserialize<{ sessions: ActiveSession[] }>(result);
}
// !: Unused for now since it isn't providing the type narrowing
// !: we need.
private async handleError<T>(response: ClientResponse<T>): Promise<void> {
if (!response.ok) {
const error = await response.json();
@ -101,7 +111,11 @@ export class AuthClient {
getMany: async () => {
const response = await this.client['accounts'].$get();
await this.handleError(response);
if (!response.ok) {
const error = await response.json();
throw AppError.parseError(error);
}
const result = await response.json();
@ -112,7 +126,11 @@ export class AuthClient {
param: { accountId },
});
await this.handleError(response);
if (!response.ok) {
const error = await response.json();
throw AppError.parseError(error);
}
},
};
@ -131,41 +149,75 @@ export class AuthClient {
},
});
await this.handleError(response);
if (!response.ok) {
const error = await response.json();
throw AppError.parseError(error);
}
handleSignInRedirect(data.redirectPath);
},
updatePassword: async (data: TUpdatePasswordSchema) => {
const response = await this.client['email-password']['update-password'].$post({ json: data });
await this.handleError(response);
if (!response.ok) {
const error = await response.json();
throw AppError.parseError(error);
}
},
forgotPassword: async (data: TForgotPasswordSchema) => {
const response = await this.client['email-password']['forgot-password'].$post({ json: data });
await this.handleError(response);
if (!response.ok) {
const error = await response.json();
throw AppError.parseError(error);
}
},
resetPassword: async (data: TResetPasswordSchema) => {
const response = await this.client['email-password']['reset-password'].$post({ json: data });
await this.handleError(response);
if (!response.ok) {
const error = await response.json();
throw AppError.parseError(error);
}
},
signUp: async (data: TSignUpSchema) => {
const response = await this.client['email-password']['signup'].$post({ json: data });
await this.handleError(response);
if (!response.ok) {
const error = await response.json();
throw AppError.parseError(error);
}
},
resendVerifyEmail: async (data: TResendVerifyEmailSchema) => {
const response = await this.client['email-password']['resend-verify-email'].$post({
json: data,
});
await this.handleError(response);
if (!response.ok) {
const error = await response.json();
throw AppError.parseError(error);
}
},
verifyEmail: async (data: TVerifyEmailSchema) => {
const response = await this.client['email-password']['verify-email'].$post({ json: data });
await this.handleError(response);
if (!response.ok) {
const error = await response.json();
throw AppError.parseError(error);
}
return response.json();
},
@ -174,23 +226,43 @@ export class AuthClient {
public twoFactor = {
setup: async () => {
const response = await this.client['two-factor'].setup.$post();
await this.handleError(response);
if (!response.ok) {
const error = await response.json();
throw AppError.parseError(error);
}
return response.json();
},
enable: async (data: TEnableTwoFactorRequestSchema) => {
const response = await this.client['two-factor'].enable.$post({ json: data });
await this.handleError(response);
if (!response.ok) {
const error = await response.json();
throw AppError.parseError(error);
}
return response.json();
},
disable: async (data: TDisableTwoFactorRequestSchema) => {
const response = await this.client['two-factor'].disable.$post({ json: data });
await this.handleError(response);
if (!response.ok) {
const error = await response.json();
throw AppError.parseError(error);
}
},
viewRecoveryCodes: async (data: TViewTwoFactorRecoveryCodesRequestSchema) => {
const response = await this.client['two-factor']['view-recovery-codes'].$post({ json: data });
await this.handleError(response);
if (!response.ok) {
const error = await response.json();
throw AppError.parseError(error);
}
return response.json();
},
@ -199,7 +271,12 @@ export class AuthClient {
public passkey = {
signIn: async (data: TPasskeySignin) => {
const response = await this.client['passkey'].authorize.$post({ json: data });
await this.handleError(response);
if (!response.ok) {
const error = await response.json();
throw AppError.parseError(error);
}
handleSignInRedirect(data.redirectPath);
},
@ -211,7 +288,11 @@ export class AuthClient {
json: { redirectPath },
});
await this.handleError(response);
if (!response.ok) {
const error = await response.json();
throw AppError.parseError(error);
}
const data = await response.json();
@ -228,7 +309,11 @@ export class AuthClient {
json: { redirectPath },
});
await this.handleError(response);
if (!response.ok) {
const error = await response.json();
throw AppError.parseError(error);
}
const data = await response.json();
@ -241,7 +326,12 @@ export class AuthClient {
public oidc = {
signIn: async ({ redirectPath }: { redirectPath?: string } = {}) => {
const response = await this.client['oauth'].authorize.oidc.$post({ json: { redirectPath } });
await this.handleError(response);
if (!response.ok) {
const error = await response.json();
throw AppError.parseError(error);
}
const data = await response.json();
@ -256,7 +346,11 @@ export class AuthClient {
param: { orgUrl },
});
await this.handleError(response);
if (!response.ok) {
const error = await response.json();
throw AppError.parseError(error);
}
const data = await response.json();

View File

@ -12,14 +12,14 @@
"dependencies": {
"@documenso/lib": "*",
"@documenso/prisma": "*",
"@hono/standard-validator": "^0.1.2",
"@hono/standard-validator": "^0.2.0",
"@oslojs/crypto": "^1.0.1",
"@oslojs/encoding": "^1.1.0",
"arctic": "^3.1.0",
"hono": "4.7.0",
"luxon": "^3.5.0",
"nanoid": "^5.1.5",
"ts-pattern": "^5.0.5",
"arctic": "^3.7.0",
"hono": "4.10.6",
"luxon": "^3.7.2",
"nanoid": "^5.1.6",
"ts-pattern": "^5.9.0",
"zod": "^3.25.76"
}
}

View File

@ -13,12 +13,12 @@
"clean": "rimraf node_modules"
},
"dependencies": {
"@aws-sdk/client-sesv2": "^3.936.0",
"@documenso/lib": "*",
"@documenso/prisma": "*",
"luxon": "^3.4.0",
"micro": "^10.0.1",
"luxon": "^3.7.2",
"react": "^18",
"ts-pattern": "^5.0.5",
"ts-pattern": "^5.9.0",
"zod": "^3.25.76"
}
}

View File

@ -13,36 +13,36 @@
],
"scripts": {
"dev": "email dev --port 3002 --dir templates",
"clean": "rimraf node_modules",
"worker:test": "tsup worker/index.ts --format esm"
"clean": "rimraf node_modules"
},
"dependencies": {
"@documenso/tailwind-config": "*",
"@documenso/nodemailer-resend": "2.0.0",
"@react-email/body": "0.0.4",
"@react-email/button": "0.0.11",
"@react-email/column": "0.0.8",
"@react-email/container": "0.0.10",
"@react-email/font": "0.0.4",
"@react-email/head": "0.0.6",
"@react-email/heading": "0.0.9",
"@react-email/hr": "0.0.6",
"@react-email/html": "0.0.6",
"@react-email/img": "0.0.6",
"@react-email/link": "0.0.6",
"@react-email/preview": "0.0.7",
"@react-email/render": "0.0.9",
"@react-email/row": "0.0.6",
"@react-email/section": "0.0.10",
"@react-email/tailwind": "0.0.9",
"@react-email/text": "0.0.6",
"nodemailer": "^6.10.1",
"react-email": "1.9.5",
"resend": "2.0.0"
"@documenso/nodemailer-resend": "4.0.0",
"@react-email/body": "0.2.0",
"@react-email/button": "0.2.0",
"@react-email/code-block": "0.2.0",
"@react-email/code-inline": "0.0.5",
"@react-email/column": "0.0.13",
"@react-email/container": "0.0.15",
"@react-email/font": "0.0.9",
"@react-email/head": "0.0.12",
"@react-email/heading": "0.0.15",
"@react-email/hr": "0.0.11",
"@react-email/html": "0.0.11",
"@react-email/img": "0.0.11",
"@react-email/link": "0.0.12",
"@react-email/preview": "0.0.13",
"@react-email/render": "0.0.17",
"@react-email/row": "0.0.12",
"@react-email/section": "0.0.16",
"@react-email/tailwind": "^2.0.1",
"@react-email/text": "0.1.5",
"nodemailer": "^7.0.10",
"react-email": "^5.0.4",
"resend": "^6.5.2"
},
"devDependencies": {
"@documenso/tsconfig": "*",
"@types/nodemailer": "^6.4.14",
"tsup": "^7.1.0"
"@types/nodemailer": "^6.4.21"
}
}

View File

@ -1,3 +1,5 @@
import type { I18n } from '@lingui/core';
import { I18nProvider } from '@lingui/react';
import * as ReactEmail from '@react-email/render';
import config from '@documenso/tailwind-config';
@ -7,6 +9,7 @@ import { BrandingProvider, type BrandingSettings } from './providers/branding';
export type RenderOptions = ReactEmail.Options & {
branding?: BrandingSettings;
i18n?: I18n;
};
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
@ -16,17 +19,46 @@ export const render = (element: React.ReactNode, options?: RenderOptions) => {
const { branding, ...otherOptions } = options ?? {};
return ReactEmail.render(
<Tailwind
config={{
theme: {
extend: {
colors,
<BrandingProvider branding={branding}>
<Tailwind
config={{
theme: {
extend: {
colors,
},
},
},
}}
>
<BrandingProvider branding={branding}>{element}</BrandingProvider>
</Tailwind>,
}}
>
{element}
</Tailwind>
</BrandingProvider>,
otherOptions,
);
};
export const renderWithI18N = (element: React.ReactNode, options?: RenderOptions) => {
const { branding, i18n, ...otherOptions } = options ?? {};
if (!i18n) {
throw new Error('i18n is required');
}
return ReactEmail.render(
<I18nProvider i18n={i18n}>
<BrandingProvider branding={branding}>
<Tailwind
config={{
theme: {
extend: {
colors,
},
},
}}
>
{element}
</Tailwind>
</BrandingProvider>
</I18nProvider>,
otherOptions,
);
};
@ -35,17 +67,19 @@ export const renderAsync = async (element: React.ReactNode, options?: RenderOpti
const { branding, ...otherOptions } = options ?? {};
return await ReactEmail.renderAsync(
<Tailwind
config={{
theme: {
extend: {
colors,
<BrandingProvider branding={branding}>
<Tailwind
config={{
theme: {
extend: {
colors,
},
},
},
}}
>
<BrandingProvider branding={branding}>{element}</BrandingProvider>
</Tailwind>,
}}
>
{element}
</Tailwind>
</BrandingProvider>,
otherOptions,
);
};

View File

@ -7,14 +7,14 @@
"clean": "rimraf node_modules"
},
"dependencies": {
"@typescript-eslint/eslint-plugin": "^7.1.1",
"@typescript-eslint/parser": "^7.1.1",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"eslint": "^8.57.0",
"eslint-config-next": "^14.2.28",
"eslint-config-turbo": "^1.12.5",
"eslint-plugin-package-json": "^0.31.0",
"eslint-config-next": "^15",
"eslint-config-turbo": "^1.13.4",
"eslint-plugin-package-json": "^0.85.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-unused-imports": "^4.1.4",
"eslint-plugin-unused-imports": "^4.3.0",
"typescript": "5.6.2"
}
}

View File

@ -75,6 +75,7 @@ export function usePageRenderer(renderFunction: RenderFunction) {
canvas.style.height = `${Math.floor(scaledViewport.height)}px`;
const renderContext: RenderParameters = {
canvas,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
canvasContext: canvas.getContext('2d', { alpha: false }) as CanvasRenderingContext2D,
viewport: renderViewport,

View File

@ -1,8 +1,8 @@
import type { Context as HonoContext } from 'hono';
import type { Context, Handler, InngestFunction } from 'inngest';
import { Inngest as InngestClient } from 'inngest';
import type { Logger } from 'inngest';
import { serve as createHonoPagesRoute } from 'inngest/hono';
import type { Logger } from 'inngest/middleware/logger';
import { env } from '../../utils/env';
import type { JobDefinition, JobRunIO, SimpleTriggerJobOptions } from './_internal/job';

View File

@ -49,7 +49,8 @@ export const run = async ({
throw new Error('Template not found');
}
const rows = parse(csvContent, { columns: true, skip_empty_lines: true });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const rows = parse<any>(csvContent, { columns: true, skip_empty_lines: true });
if (rows.length > 100) {
throw new Error('Maximum 100 rows allowed per upload');

View File

@ -15,51 +15,52 @@
"clean": "rimraf node_modules"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.410.0",
"@aws-sdk/client-sesv2": "^3.410.0",
"@aws-sdk/cloudfront-signer": "^3.410.0",
"@aws-sdk/s3-request-presigner": "^3.410.0",
"@aws-sdk/signature-v4-crt": "^3.410.0",
"@aws-sdk/client-s3": "^3.936.0",
"@aws-sdk/client-sesv2": "^3.936.0",
"@aws-sdk/cloudfront-signer": "^3.935.0",
"@aws-sdk/s3-request-presigner": "^3.936.0",
"@aws-sdk/signature-v4-crt": "^3.936.0",
"@documenso/assets": "*",
"@documenso/email": "*",
"@documenso/prisma": "*",
"@documenso/signing": "*",
"@lingui/core": "^5.2.0",
"@lingui/macro": "^5.2.0",
"@lingui/react": "^5.2.0",
"@noble/ciphers": "0.4.0",
"@noble/hashes": "1.3.2",
"@node-rs/bcrypt": "^1.10.0",
"@lingui/core": "^5.6.0",
"@lingui/macro": "^5.6.0",
"@lingui/react": "^5.6.0",
"@noble/ciphers": "0.6.0",
"@noble/hashes": "1.8.0",
"@node-rs/bcrypt": "^1.10.7",
"@pdf-lib/fontkit": "^1.1.1",
"@scure/base": "^1.1.3",
"@sindresorhus/slugify": "^2.2.1",
"@team-plain/typescript-sdk": "^5.9.0",
"@vvo/tzdb": "^6.117.0",
"csv-parse": "^5.6.0",
"inngest": "^3.19.13",
"jose": "^6.0.0",
"kysely": "0.26.3",
"luxon": "^3.4.0",
"micro": "^10.0.1",
"nanoid": "^5.1.5",
"@scure/base": "^1.2.6",
"@sindresorhus/slugify": "^3.0.0",
"@team-plain/typescript-sdk": "^5.11.0",
"@vvo/tzdb": "^6.196.0",
"csv-parse": "^6.1.0",
"inngest": "^3.45.1",
"jose": "^6.1.2",
"konva": "^10.0.9",
"kysely": "0.28.8",
"luxon": "^3.7.2",
"nanoid": "^5.1.6",
"oslo": "^0.17.0",
"pg": "^8.11.3",
"pino": "^9.7.0",
"pino-pretty": "^13.0.0",
"playwright": "1.52.0",
"posthog-js": "^1.245.0",
"posthog-node": "^4.17.0",
"pg": "^8.16.3",
"pino": "^9.14.0",
"pino-pretty": "^13.1.2",
"playwright": "1.56.1",
"posthog-js": "^1.297.2",
"posthog-node": "^4.18.0",
"react": "^18",
"remeda": "^2.17.3",
"sharp": "0.32.6",
"react-pdf": "^10.2.0",
"remeda": "^2.32.0",
"sharp": "0.34.5",
"skia-canvas": "^3.0.8",
"stripe": "^12.7.0",
"ts-pattern": "^5.0.5",
"stripe": "^12.18.0",
"ts-pattern": "^5.9.0",
"zod": "^3.25.76"
},
"devDependencies": {
"@playwright/browser-chromium": "1.52.0",
"@types/luxon": "^3.3.1",
"@types/pg": "^8.11.4"
"@playwright/browser-chromium": "1.56.1",
"@types/luxon": "^3.7.1",
"@types/pg": "^8.15.6"
}
}

View File

@ -71,7 +71,7 @@ export const getMonthlyActiveUsers = async () => {
)
.as('cume_count'),
])
.where(sql`type = ${UserSecurityAuditLogType.SIGN_IN}::"UserSecurityAuditLogType"`)
.where(() => sql`type = ${UserSecurityAuditLogType.SIGN_IN}::"UserSecurityAuditLogType"`)
.groupBy(({ fn }) => fn('DATE_TRUNC', [sql.lit('MONTH'), 'UserSecurityAuditLog.createdAt']))
.orderBy('month', 'desc')
.limit(12);

View File

@ -1,7 +1,7 @@
import { xchacha20poly1305 } from '@noble/ciphers/chacha';
import { bytesToHex, hexToBytes, utf8ToBytes } from '@noble/ciphers/utils';
import { managedNonce } from '@noble/ciphers/webcrypto/utils';
import { sha256 } from '@noble/hashes/sha256';
import { managedNonce } from '@noble/ciphers/webcrypto';
import { sha256 } from '@noble/hashes/sha2';
export type SymmetricEncryptOptions = {
key: string;

View File

@ -96,6 +96,7 @@ const createFieldSignature = (
img.onload = () => {
image.setAttrs({
image: img,
...getImageDimensions(img, fieldWidth, fieldHeight),
});
};

View File

@ -1,7 +1,5 @@
import { I18nProvider } from '@lingui/react';
import type { RenderOptions } from '@documenso/email/render';
import { render } from '@documenso/email/render';
import { renderWithI18N } from '@documenso/email/render';
import { getI18nInstance } from '../client-only/providers/i18n-server';
import {
@ -26,7 +24,7 @@ export const renderEmailWithI18N = async (
i18n.activate(lang);
return render(<I18nProvider i18n={i18n}>{component}</I18nProvider>, otherOptions);
return renderWithI18N(component, { i18n, ...otherOptions });
} catch (err) {
console.error(err);
throw new Error('Failed to render email');

View File

@ -8,8 +8,8 @@
},
"dependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.9"
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.7.1"
},
"devDependencies": {}
}

View File

@ -21,19 +21,21 @@
"seed": "tsx ./seed-database.ts"
},
"dependencies": {
"@prisma/client": "^6.18.0",
"kysely": "0.26.3",
"prisma": "^6.18.0",
"@prisma/client": "^6.19.0",
"kysely": "0.28.8",
"nanoid": "^5.1.6",
"prisma": "^6.19.0",
"prisma-extension-kysely": "^3.0.0",
"prisma-kysely": "^1.8.0",
"prisma-kysely": "^2.2.1",
"prisma-json-types-generator": "^3.6.2",
"ts-pattern": "^5.0.6",
"ts-pattern": "^5.9.0",
"zod": "^3.25.76",
"zod-prisma-types": "3.3.5"
},
"devDependencies": {
"dotenv": "^16.5.0",
"dotenv-cli": "^8.0.0",
"tsx": "^4.19.2",
"dotenv": "^17.2.3",
"dotenv-cli": "^11.0.0",
"tsx": "^4.20.6",
"typescript": "5.6.2"
}
}

View File

@ -14,9 +14,9 @@
"dependencies": {
"@documenso/pdf-sign": "^0.1.0",
"@documenso/tsconfig": "*",
"ts-pattern": "^5.0.5"
"ts-pattern": "^5.9.0"
},
"devDependencies": {
"vitest": "^3.1.4"
"vitest": "^3.2.4"
}
}
}

View File

@ -9,14 +9,14 @@
},
"dependencies": {
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/typography": "^0.5.9",
"autoprefixer": "^10.4.13",
"postcss": "^8.4.32",
"tailwindcss": "3.4.15",
"tailwindcss-animate": "^1.0.5"
"@tailwindcss/typography": "^0.5.19",
"autoprefixer": "^10.4.22",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.18",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {},
"publishConfig": {
"access": "public"
}
}
}

View File

@ -12,16 +12,16 @@
"dependencies": {
"@documenso/lib": "*",
"@documenso/prisma": "*",
"@tanstack/react-query": "5.90.5",
"@trpc/client": "11.7.0",
"@trpc/react-query": "11.7.0",
"@trpc/server": "11.7.0",
"@ts-rest/core": "^3.52.0",
"@tanstack/react-query": "5.90.10",
"@trpc/client": "11.7.1",
"@trpc/react-query": "11.7.1",
"@trpc/server": "11.7.1",
"@ts-rest/core": "^3.52.1",
"formidable": "^3.5.4",
"luxon": "^3.4.0",
"luxon": "^3.7.2",
"superjson": "^2.2.5",
"trpc-to-openapi": "2.4.0",
"ts-pattern": "^5.0.5",
"ts-pattern": "^5.9.0",
"zod": "^3.25.76",
"zod-form-data": "^2.0.8",
"zod-openapi": "^4.2.4"
@ -29,4 +29,4 @@
"devDependencies": {
"@types/formidable": "^3.4.6"
}
}
}

View File

@ -10,7 +10,7 @@
"forceConsistentCasingInFileNames": true,
"inlineSources": false,
"isolatedModules": true,
"moduleResolution": "node",
"moduleResolution": "bundler",
"noUnusedLocals": false,
"noUnusedParameters": false,
"preserveWatchOutput": true,
@ -19,6 +19,12 @@
"strictNullChecks": true,
"target": "ES2018"
},
"include": ["**/*.ts", "**/*.tsx", "**/.d.ts"],
"exclude": ["node_modules"]
}
"include": [
"**/*.ts",
"**/*.tsx",
"**/.d.ts"
],
"exclude": [
"node_modules"
]
}

View File

@ -11,4 +11,4 @@
"nextjs.json",
"react-library.json"
]
}
}

View File

@ -1,11 +1,10 @@
import { useEffect, useState } from 'react';
import { useHydrated } from '../lib/use-hydrated';
export const ClientOnly = async ({ children }: { children: React.ReactNode }) => {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
return mounted ? children : null;
type ClientOnlyProps = {
children: () => React.ReactNode;
fallback?: React.ReactNode;
};
export const ClientOnly = ({ children, fallback = null }: ClientOnlyProps) => {
return useHydrated() ? <>{children()}</> : <>{fallback}</>;
};

View File

@ -19,19 +19,19 @@ export type LoadedPDFDocument = PDFDocumentProxy;
* This imports the worker from the `pdfjs-dist` package.
*/
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.js',
'pdfjs-dist/legacy/build/pdf.worker.min.mjs',
import.meta.url,
).toString();
const pdfViewerOptions = {
cMapUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/static/cmaps`,
cMapUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/static/cmaps/`,
};
const PDFLoader = () => (
<>
<Loader className="text-documenso h-12 w-12 animate-spin" />
<Loader className="h-12 w-12 animate-spin text-documenso" />
<p className="text-muted-foreground mt-4">
<p className="mt-4 text-muted-foreground">
<Trans>Loading document...</Trans>
</p>
</>
@ -145,9 +145,9 @@ export const PdfViewerKonva = ({
}}
externalLinkTarget="_blank"
loading={
<div className="dark:bg-background flex h-[80vh] max-h-[60rem] flex-col items-center justify-center bg-white/50">
<div className="flex h-[80vh] max-h-[60rem] flex-col items-center justify-center bg-white/50 dark:bg-background">
{pdfError ? (
<div className="text-muted-foreground text-center">
<div className="text-center text-muted-foreground">
<p>
<Trans>Something went wrong while loading the document.</Trans>
</p>
@ -161,8 +161,8 @@ export const PdfViewerKonva = ({
</div>
}
error={
<div className="dark:bg-background flex h-[80vh] max-h-[60rem] flex-col items-center justify-center bg-white/50">
<div className="text-muted-foreground text-center">
<div className="flex h-[80vh] max-h-[60rem] flex-col items-center justify-center bg-white/50 dark:bg-background">
<div className="text-center text-muted-foreground">
<p>
<Trans>Something went wrong while loading the document.</Trans>
</p>
@ -172,13 +172,13 @@ export const PdfViewerKonva = ({
</div>
</div>
}
// options={pdfViewerOptions}
options={pdfViewerOptions}
>
{Array(numPages)
.fill(null)
.map((_, i) => (
<div key={i} className="last:-mb-2">
<div className="border-border rounded border will-change-transform">
<div className="rounded border border-border will-change-transform">
<PDFPage
pageNumber={i + 1}
width={width}
@ -189,7 +189,7 @@ export const PdfViewerKonva = ({
customRenderer={customPageRenderer}
/>
</div>
<p className="text-muted-foreground/80 my-2 text-center text-[11px]">
<p className="my-2 text-center text-[11px] text-muted-foreground/80">
<Trans>
Page {i + 1} of {numPages}
</Trans>

View File

@ -0,0 +1,13 @@
import { useSyncExternalStore } from 'react';
const subscribe = () => {
return () => {};
};
export const useHydrated = () => {
return useSyncExternalStore(
subscribe,
() => true,
() => false,
);
};

View File

@ -19,8 +19,8 @@
"devDependencies": {
"@documenso/tailwind-config": "*",
"@documenso/tsconfig": "*",
"@types/luxon": "^3.3.2",
"@types/react": "^18",
"@types/luxon": "^3.7.1",
"@types/react": "18.3.27",
"@types/react-dom": "^18",
"react": "^18",
"typescript": "5.6.2"
@ -28,56 +28,57 @@
"dependencies": {
"@documenso/lib": "*",
"@hello-pangea/dnd": "^16.6.0",
"@hookform/resolvers": "^3.3.0",
"@lingui/macro": "^5.2.0",
"@lingui/react": "^5.2.0",
"@radix-ui/react-accordion": "^1.1.1",
"@radix-ui/react-alert-dialog": "^1.0.3",
"@radix-ui/react-aspect-ratio": "^1.0.2",
"@radix-ui/react-avatar": "^1.0.2",
"@radix-ui/react-checkbox": "^1.0.3",
"@radix-ui/react-collapsible": "^1.0.2",
"@radix-ui/react-context-menu": "^2.1.3",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.4",
"@radix-ui/react-hover-card": "^1.0.5",
"@radix-ui/react-label": "^2.0.1",
"@radix-ui/react-menubar": "^1.0.2",
"@radix-ui/react-navigation-menu": "^1.1.2",
"@radix-ui/react-popover": "^1.0.5",
"@radix-ui/react-progress": "^1.0.2",
"@radix-ui/react-radio-group": "^1.1.2",
"@radix-ui/react-scroll-area": "^1.0.3",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.2",
"@radix-ui/react-slider": "^1.1.1",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.2",
"@radix-ui/react-tabs": "^1.0.3",
"@radix-ui/react-toast": "^1.1.3",
"@radix-ui/react-toggle": "^1.0.2",
"@radix-ui/react-toggle-group": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.6",
"@tanstack/react-table": "^8.9.1",
"class-variance-authority": "^0.6.0",
"@hookform/resolvers": "^3",
"@lingui/macro": "^5.6.0",
"@lingui/react": "^5.6.0",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-aspect-ratio": "^1.1.8",
"@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-context-menu": "^2.2.16",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-hover-card": "^1.1.15",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-menubar": "^1.1.16",
"@radix-ui/react-navigation-menu": "^1.2.14",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-progress": "^1.1.8",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slider": "^1.3.6",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-toast": "^1.2.15",
"@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.8",
"@scure/base": "^1.2.6",
"@tanstack/react-table": "^8.21.3",
"class-variance-authority": "^0.7.1",
"clsx": "^1.2.1",
"cmdk": "^0.2.0",
"framer-motion": "^10.12.8",
"lucide-react": "^0.279.0",
"luxon": "^3.4.2",
"perfect-freehand": "^1.2.0",
"pdfjs-dist": "3.11.174",
"cmdk": "^0.2.1",
"framer-motion": "^12.23.24",
"lucide-react": "^0.554.0",
"luxon": "^3.7.2",
"perfect-freehand": "^1.2.2",
"pdfjs-dist": "5.4.296",
"react": "^18",
"react-colorful": "^5.6.1",
"react-day-picker": "^8.7.1",
"react-day-picker": "^8.10.1",
"react-dom": "^18",
"react-hook-form": "^7.45.4",
"react-pdf": "7.7.3",
"react-rnd": "^10.4.1",
"remeda": "^2.17.3",
"tailwind-merge": "^1.12.0",
"tailwindcss-animate": "^1.0.5",
"ts-pattern": "^5.0.5",
"react-hook-form": "^7.66.1",
"react-pdf": "^10.2.0",
"react-rnd": "^10.5.2",
"remeda": "^2.32.0",
"tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.7",
"ts-pattern": "^5.9.0",
"zod": "^3.25.76"
}
}

View File

@ -0,0 +1,10 @@
import {
type LoadedPDFDocument,
type OnPDFViewerPageClick,
PDFViewer,
type PDFViewerProps,
} from './base';
export { PDFViewer, type LoadedPDFDocument, type OnPDFViewerPageClick, type PDFViewerProps };
export default PDFViewer;

View File

@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
@ -10,26 +10,27 @@ import { type PDFDocumentProxy } from 'pdfjs-dist';
import { Document as PDFDocument, Page as PDFPage, pdfjs } from 'react-pdf';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
// import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
// import 'react-pdf/dist/esm/Page/TextLayer.css';
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import { getEnvelopeItemPdfUrl } from '@documenso/lib/utils/envelope-download';
import { cn } from '../lib/utils';
import { useToast } from './use-toast';
import { cn } from '../../lib/utils';
import { useToast } from '../use-toast';
export type LoadedPDFDocument = PDFDocumentProxy;
/**
* This imports the worker from the `pdfjs-dist` package.
* Wrapped in typeof window check to prevent SSR evaluation.
*/
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.js',
import.meta.url,
).toString();
if (typeof window !== 'undefined') {
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/legacy/build/pdf.worker.min.mjs',
import.meta.url,
).toString();
}
const pdfViewerOptions = {
cMapUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/static/cmaps`,
cMapUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/static/cmaps/`,
};
export type OnPDFViewerPageClick = (_event: {
@ -44,9 +45,9 @@ export type OnPDFViewerPageClick = (_event: {
const PDFLoader = () => (
<>
<Loader className="text-documenso h-12 w-12 animate-spin" />
<Loader className="h-12 w-12 animate-spin text-documenso" />
<p className="text-muted-foreground mt-4">
<p className="mt-4 text-muted-foreground">
<Trans>Loading document...</Trans>
</p>
</>
@ -61,6 +62,7 @@ export type PDFViewerProps = {
onDocumentLoad?: (_doc: LoadedPDFDocument) => void;
onPageClick?: OnPDFViewerPageClick;
overrideData?: string;
customPageRenderer?: React.FunctionComponent;
[key: string]: unknown;
} & Omit<React.HTMLAttributes<HTMLDivElement>, 'onPageClick'>;
@ -73,6 +75,7 @@ export const PDFViewer = ({
onDocumentLoad,
onPageClick,
overrideData,
customPageRenderer,
...props
}: PDFViewerProps) => {
const { _ } = useLingui();
@ -91,6 +94,16 @@ export const PDFViewer = ({
const isLoading = isDocumentBytesLoading || !documentBytes;
const envelopeItemFile = useMemo(() => {
if (!documentBytes) {
return null;
}
return {
data: documentBytes,
};
}, [documentBytes]);
const onDocumentLoaded = (doc: LoadedPDFDocument) => {
setNumPages(doc.numPages);
onDocumentLoad?.(doc);
@ -203,7 +216,7 @@ export const PDFViewer = ({
) : (
<>
<PDFDocument
file={documentBytes.buffer}
file={envelopeItemFile}
className={cn('w-full overflow-hidden rounded', {
'h-[80vh] max-h-[60rem]': numPages === 0,
})}
@ -215,9 +228,9 @@ export const PDFViewer = ({
}}
externalLinkTarget="_blank"
loading={
<div className="dark:bg-background flex h-[80vh] max-h-[60rem] flex-col items-center justify-center bg-white/50">
<div className="flex h-[80vh] max-h-[60rem] flex-col items-center justify-center bg-white/50 dark:bg-background">
{pdfError ? (
<div className="text-muted-foreground text-center">
<div className="text-center text-muted-foreground">
<p>
<Trans>Something went wrong while loading the document.</Trans>
</p>
@ -231,8 +244,8 @@ export const PDFViewer = ({
</div>
}
error={
<div className="dark:bg-background flex h-[80vh] max-h-[60rem] flex-col items-center justify-center bg-white/50">
<div className="text-muted-foreground text-center">
<div className="flex h-[80vh] max-h-[60rem] flex-col items-center justify-center bg-white/50 dark:bg-background">
<div className="text-center text-muted-foreground">
<p>
<Trans>Something went wrong while loading the document.</Trans>
</p>
@ -242,23 +255,25 @@ export const PDFViewer = ({
</div>
</div>
}
// options={pdfViewerOptions}
options={pdfViewerOptions}
>
{Array(numPages)
.fill(null)
.map((_, i) => (
<div key={i} className="last:-mb-2">
<div className="border-border overflow-hidden rounded border will-change-transform">
<div className="overflow-hidden rounded border border-border will-change-transform">
<PDFPage
pageNumber={i + 1}
width={width}
renderAnnotationLayer={false}
renderTextLayer={false}
loading={() => ''}
renderMode={customPageRenderer ? 'custom' : 'canvas'}
customRenderer={customPageRenderer}
onClick={(e) => onDocumentPageClick(e, i + 1)}
/>
</div>
<p className="text-muted-foreground/80 my-2 text-center text-[11px]">
<p className="my-2 text-center text-[11px] text-muted-foreground/80">
<Trans>
Page {i + 1} of {numPages}
</Trans>

View File

@ -0,0 +1 @@
export * from './base';

View File

@ -0,0 +1,7 @@
import { ClientOnly } from '../../components/client-only';
import { PDFViewer, type PDFViewerProps } from './base.client';
export const PDFViewerLazy = (props: PDFViewerProps) => {
return <ClientOnly fallback={<div>Loading...</div>}>{() => <PDFViewer {...props} />}</ClientOnly>;
};