fix: minimal vite config

This commit is contained in:
David Nguyen
2025-02-07 16:33:30 +11:00
parent 8373af3f41
commit d3392dada7
17 changed files with 683 additions and 1394 deletions

View File

@ -25,4 +25,5 @@ async function main() {
}); });
} }
void main(); // eslint-disable-next-line @typescript-eslint/no-floating-promises
main();

View File

@ -1,11 +0,0 @@
import React from 'react';
import NextPlausibleProvider from 'next-plausible';
export type PlausibleProviderProps = {
children: React.ReactNode;
};
export const PlausibleProvider = ({ children }: PlausibleProviderProps) => {
return <NextPlausibleProvider domain="documenso.com">{children}</NextPlausibleProvider>;
};

View File

@ -2,7 +2,7 @@ import { Trans } from '@lingui/macro';
import { DocumentStatus, SigningStatus } from '@prisma/client'; import { DocumentStatus, SigningStatus } from '@prisma/client';
import { Clock8 } from 'lucide-react'; import { Clock8 } from 'lucide-react';
import { Link, redirect } from 'react-router'; import { Link, redirect } from 'react-router';
import { getOptionalLoaderSession } from 'server/utils/get-loader-session'; import { getOptionalLoaderContext } from 'server/utils/get-loader-session';
import signingCelebration from '@documenso/assets/images/signing-celebration.png'; import signingCelebration from '@documenso/assets/images/signing-celebration.png';
import { useOptionalSession } from '@documenso/lib/client-only/providers/session'; import { useOptionalSession } from '@documenso/lib/client-only/providers/session';
@ -27,7 +27,7 @@ import { superLoaderJson, useSuperLoaderData } from '~/utils/super-json-loader';
import type { Route } from './+types/_index'; import type { Route } from './+types/_index';
export async function loader({ params }: Route.LoaderArgs) { export async function loader({ params }: Route.LoaderArgs) {
const session = getOptionalLoaderSession(); const { session, requestMetadata } = getOptionalLoaderContext();
const { token } = params; const { token } = params;
@ -91,7 +91,7 @@ export async function loader({ params }: Route.LoaderArgs) {
await viewedDocument({ await viewedDocument({
token, token,
requestMetadata: context.requestMetadata, requestMetadata,
recipientAccessAuth: derivedRecipientAccessAuth, recipientAccessAuth: derivedRecipientAccessAuth,
}).catch(() => null); }).catch(() => null);

View File

@ -1,6 +1,6 @@
// Todo: This relies on NextJS // Todo: Test, used AI to migrate this component from NextJS to Remix.
import { ImageResponse } from 'next/og'; import satori from 'satori';
import sharp from 'sharp';
import { P, match } from 'ts-pattern'; import { P, match } from 'ts-pattern';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
@ -25,7 +25,7 @@ export const loader = async ({ params }: Route.LoaderArgs) => {
const baseUrl = NEXT_PUBLIC_WEBAPP_URL(); const baseUrl = NEXT_PUBLIC_WEBAPP_URL();
const [interSemiBold, interRegular, caveatRegular, shareFrameImage] = await Promise.all([ const [interSemiBold, interRegular, caveatRegular] = await Promise.all([
fetch(new URL(`${baseUrl}/fonts/inter-semibold.ttf`, import.meta.url)).then(async (res) => fetch(new URL(`${baseUrl}/fonts/inter-semibold.ttf`, import.meta.url)).then(async (res) =>
res.arrayBuffer(), res.arrayBuffer(),
), ),
@ -35,9 +35,6 @@ export const loader = async ({ params }: Route.LoaderArgs) => {
fetch(new URL(`${baseUrl}/fonts/caveat-regular.ttf`, import.meta.url)).then(async (res) => fetch(new URL(`${baseUrl}/fonts/caveat-regular.ttf`, import.meta.url)).then(async (res) =>
res.arrayBuffer(), res.arrayBuffer(),
), ),
fetch(new URL(`${baseUrl}/static/og-share-frame2.png`, import.meta.url)).then(async (res) =>
res.arrayBuffer(),
),
]); ]);
const recipientOrSender: ShareHandlerAPIResponse = await fetch( const recipientOrSender: ShareHandlerAPIResponse = await fetch(
@ -66,65 +63,100 @@ export const loader = async ({ params }: Route.LoaderArgs) => {
return sender.name || sender.email; return sender.name || sender.email;
}); });
return new ImageResponse( // Generate SVG using Satori
( const svg = await satori(
<div tw="relative flex h-full w-full bg-white"> <div
{/* @ts-expect-error Lack of typing from ImageResponse */} style={{
<img src={shareFrameImage} alt="og-share-frame" tw="absolute inset-0 w-full h-full" /> display: 'flex',
height: '100%',
{signatureImage ? ( width: '100%',
<div backgroundColor: 'white',
tw="absolute py-6 px-12 flex items-center justify-center text-center" position: 'relative',
style={{ }}
top: `${CARD_OFFSET_TOP}px`, >
left: `${CARD_OFFSET_LEFT}px`, <img
width: `${CARD_WIDTH}px`, src={`${baseUrl}/static/og-share-frame2.png`}
height: `${CARD_HEIGHT}px`, alt="og-share-frame"
}} style={{
> position: 'absolute',
<img src={signatureImage} alt="signature" tw="opacity-60 h-full max-w-[100%]" /> inset: 0,
</div> width: '100%',
) : ( height: '100%',
<p }}
tw="absolute py-6 px-12 -mt-2 flex items-center justify-center text-center text-slate-500" />
style={{
fontFamily: 'Caveat',
fontSize: `${Math.max(
Math.min((CARD_WIDTH * 1.5) / signatureName.length, 80),
36,
)}px`,
top: `${CARD_OFFSET_TOP}px`,
left: `${CARD_OFFSET_LEFT}px`,
width: `${CARD_WIDTH}px`,
height: `${CARD_HEIGHT}px`,
}}
>
{signatureName}
</p>
)}
{signatureImage ? (
<div <div
tw="absolute flex w-full"
style={{ style={{
top: `${CARD_OFFSET_TOP - 78}px`, position: 'absolute',
left: `${CARD_OFFSET_LEFT}px`, padding: '24px 48px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
textAlign: 'center',
top: CARD_OFFSET_TOP,
left: CARD_OFFSET_LEFT,
width: CARD_WIDTH,
height: CARD_HEIGHT,
}} }}
> >
<h2 <img
tw="text-xl" src={signatureImage}
alt="signature"
style={{ style={{
color: '#828282', opacity: 0.6,
fontFamily: 'Inter', height: '100%',
fontWeight: 700, maxWidth: '100%',
}} }}
> />
{isRecipient ? 'Document Signed!' : 'Document Sent!'}
</h2>
</div> </div>
) : (
<p
style={{
position: 'absolute',
padding: '24px 48px',
marginTop: '-8px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
textAlign: 'center',
color: '#64748b',
fontFamily: 'Caveat',
fontSize: Math.max(Math.min((CARD_WIDTH * 1.5) / signatureName.length, 80), 36),
top: CARD_OFFSET_TOP,
left: CARD_OFFSET_LEFT,
width: CARD_WIDTH,
height: CARD_HEIGHT,
}}
>
{signatureName}
</p>
)}
<div
style={{
position: 'absolute',
display: 'flex',
width: '100%',
top: CARD_OFFSET_TOP - 78,
left: CARD_OFFSET_LEFT,
}}
>
<h2
style={{
fontSize: '20px',
color: '#828282',
fontFamily: 'Inter',
fontWeight: 700,
}}
>
{isRecipient ? 'Document Signed!' : 'Document Sent!'}
</h2>
</div> </div>
), </div>,
{ {
...IMAGE_SIZE, width: IMAGE_SIZE.width,
height: IMAGE_SIZE.height,
fonts: [ fonts: [
{ {
name: 'Caveat', name: 'Caveat',
@ -134,20 +166,27 @@ export const loader = async ({ params }: Route.LoaderArgs) => {
{ {
name: 'Inter', name: 'Inter',
data: interRegular, data: interRegular,
style: 'normal',
weight: 400, weight: 400,
}, },
{ {
name: 'Inter', name: 'Inter',
data: interSemiBold, data: interSemiBold,
style: 'normal',
weight: 600, weight: 600,
}, },
], ],
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
},
}, },
); );
// Convert SVG to PNG using sharp
const pngBuffer = await sharp(Buffer.from(svg)).toFormat('png').toBuffer();
return new Response(pngBuffer, {
headers: {
'Content-Type': 'image/png',
'Content-Length': pngBuffer.length.toString(),
'Cache-Control': 'public, max-age=31536000, immutable',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
},
});
}; };

View File

@ -3,9 +3,8 @@
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "cross-env NODE_ENV=production bunx --bun vite build", "build": "cross-env NODE_ENV=production react-router build",
"dev": "bunx --bun vite", "dev": "react-router dev",
"dev:node": "react-router dev",
"start": "cross-env NODE_ENV=production node dist/server/index.js", "start": "cross-env NODE_ENV=production node dist/server/index.js",
"clean": "rimraf .react-router && rimraf node_modules", "clean": "rimraf .react-router && rimraf node_modules",
"typecheck": "react-router typegen && tsc", "typecheck": "react-router typegen && tsc",
@ -26,10 +25,10 @@
"@hono/trpc-server": "^0.3.4", "@hono/trpc-server": "^0.3.4",
"@hono/zod-validator": "^0.4.2", "@hono/zod-validator": "^0.4.2",
"@hookform/resolvers": "^3.1.0", "@hookform/resolvers": "^3.1.0",
"@lingui/core": "^4.11.3", "@lingui/core": "^5.2.0",
"@lingui/detect-locale": "^4.11.1", "@lingui/detect-locale": "^5.2.0",
"@lingui/macro": "^4.11.3", "@lingui/macro": "^5.2.0",
"@lingui/react": "^4.11.3", "@lingui/react": "^5.2.0",
"@oslojs/crypto": "^1.0.1", "@oslojs/crypto": "^1.0.1",
"@oslojs/encoding": "^1.1.0", "@oslojs/encoding": "^1.1.0",
"@react-router/node": "^7.1.5", "@react-router/node": "^7.1.5",
@ -62,6 +61,7 @@
"recharts": "^2.7.2", "recharts": "^2.7.2",
"remeda": "^2.17.3", "remeda": "^2.17.3",
"remix-themes": "^2.0.4", "remix-themes": "^2.0.4",
"satori": "^0.12.1",
"sharp": "0.32.6", "sharp": "0.32.6",
"tailwindcss": "^3.4.15", "tailwindcss": "^3.4.15",
"ts-pattern": "^5.0.5", "ts-pattern": "^5.0.5",

View File

@ -5,7 +5,6 @@ import handle from 'hono-react-router-adapter/node';
import server from '.'; import server from '.';
import * as build from '../build/server'; import * as build from '../build/server';
import { getLoadContext } from './load-context';
server.use( server.use(
serveStatic({ serveStatic({
@ -13,6 +12,6 @@ server.use(
}), }),
); );
const handler = handle(build, server, { getLoadContext }); const handler = handle(build, server);
serve({ fetch: handler.fetch, port: 3000 }); serve({ fetch: handler.fetch, port: 3000 });

View File

@ -1,9 +1,30 @@
import { getContext } from 'hono/context-storage'; import { getContext } from 'hono/context-storage';
import { redirect } from 'react-router'; import { redirect } from 'react-router';
import type { HonoEnv } from 'server'; import type { HonoEnv } from 'server';
import type { AppContext } from 'server/context';
import type { AppSession } from '@documenso/lib/client-only/providers/session'; import type { AppSession } from '@documenso/lib/client-only/providers/session';
/**
* Get the full context passed to the loader.
*
* @returns The full app context.
*/
export const getOptionalLoaderContext = (): AppContext => {
const { context } = getContext<HonoEnv>().var;
return context;
};
/**
* Returns the session extracted from the app context.
*
* @returns The session, or null if not authenticated.
*/
export const getOptionalLoaderSession = (): AppSession | null => {
const { context } = getContext<HonoEnv>().var;
return context.session;
};
/** /**
* Returns the session context or throws a redirect to signin if it is not present. * Returns the session context or throws a redirect to signin if it is not present.
*/ */
@ -17,11 +38,6 @@ export const getLoaderSession = (): AppSession => {
return session; return session;
}; };
export const getOptionalLoaderSession = (): AppSession | null => {
const { context } = getContext<HonoEnv>().var;
return context.session;
};
/** /**
* Returns the team session context or throws a redirect to signin if it is not present. * Returns the team session context or throws a redirect to signin if it is not present.
*/ */

View File

@ -22,50 +22,59 @@ export default defineConfig({
plugins: [tailwindcss, autoprefixer], plugins: [tailwindcss, autoprefixer],
}, },
}, },
ssr: {
noExternal: [
'react-dropzone',
'recharts',
'superjson',
// '@node-rs/bcrypt-wasm32-wasi',
// '@noble/ciphers/chacha',
// '@noble/ciphers/utils',
// '@noble/ciphers/webcrypto/utils',
// '@noble/hashes/sha256a',
// '@node-rs/bcrypt',
// 'crypto',
// '@documenso/assets',
// '@documenso/ee',
// '@documenso/lib',
// '@documenso/prisma',
// '@documenso/tailwind-config',
// '@documenso/trpc',
// '@documenso/ui',
],
},
server: { server: {
port: 3000, port: 3000,
strictPort: true, strictPort: true,
}, },
plugins: [ plugins: [
reactRouter(), reactRouter(),
lingui(),
macrosPlugin(), macrosPlugin(),
lingui(),
tsconfigPaths(),
serverAdapter({ serverAdapter({
entry: 'server/index.ts', entry: 'server/index.ts',
}), }),
tsconfigPaths(),
], ],
ssr: {
noExternal: ['react-dropzone', 'plausible-tracker', 'pdfjs-dist'],
external: ['@node-rs/bcrypt'],
},
optimizeDeps: { optimizeDeps: {
exclude: ['superjson'], exclude: ['@node-rs/bcrypt'],
// force: true,
},
build: {
commonjsOptions: {
include: ['superjson'],
},
rollupOptions: {
external: ['@node-rs/bcrypt'],
},
}, },
/**
* Throwing shit at a wall to see what sticks for building below onwards.
*/
// resolve: {
// alias: {
// // https: 'node:https',
// // '.prisma/client/default': '../../node_modules/.prisma/client/default.js',
// },
// },
// optimizeDeps: {
// include: [],
// },
// ssr: {
// // noExternal: true,
// noExternal: [
// '@documenso/assets',
// '@documenso/ee',
// '@documenso/lib',
// '@documenso/prisma',
// '@documenso/tailwind-config',
// '@documenso/trpc',
// '@documenso/ui',
// ],
// },
// build: {
// rollupOptions: {
// external: [
// '@node-rs/bcrypt',
// '@documenso/pdf-sign',
// 'nodemailer',
// 'playwright',
// '@aws-sdk/cloudfront-signer',
// ],
// },
// },
}); });

View File

@ -9,7 +9,7 @@ const config: LinguiConfig = {
catalogs: [ catalogs: [
{ {
path: '<rootDir>/packages/lib/translations/{locale}/web', path: '<rootDir>/packages/lib/translations/{locale}/web',
include: ['apps/web/src', 'packages/ui', 'packages/lib', 'packages/email'], include: ['apps/remix/src', 'packages/ui', 'packages/lib', 'packages/email'],
exclude: ['**/node_modules/**'], exclude: ['**/node_modules/**'],
}, },
], ],

1738
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -43,7 +43,7 @@
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^17.7.1", "@commitlint/cli": "^17.7.1",
"@commitlint/config-conventional": "^17.7.0", "@commitlint/config-conventional": "^17.7.0",
"@lingui/cli": "^4.11.3", "@lingui/cli": "^5.2.0",
"@trigger.dev/cli": "^2.3.18", "@trigger.dev/cli": "^2.3.18",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"dotenv-cli": "^7.3.0", "dotenv-cli": "^7.3.0",
@ -65,7 +65,8 @@
"dependencies": { "dependencies": {
"@documenso/pdf-sign": "^0.1.0", "@documenso/pdf-sign": "^0.1.0",
"@documenso/prisma": "^0.0.0", "@documenso/prisma": "^0.0.0",
"@lingui/core": "^4.11.3", "@lingui/conf": "^5.2.0",
"@lingui/core": "^5.2.0",
"inngest-cli": "^0.29.1", "inngest-cli": "^0.29.1",
"luxon": "^3.5.0", "luxon": "^3.5.0",
"mupdf": "^1.0.0", "mupdf": "^1.0.0",
@ -79,4 +80,4 @@
"trigger.dev": { "trigger.dev": {
"endpointId": "documenso-app" "endpointId": "documenso-app"
} }
} }

View File

@ -35,6 +35,8 @@
"@react-email/section": "0.0.10", "@react-email/section": "0.0.10",
"@react-email/tailwind": "0.0.9", "@react-email/tailwind": "0.0.9",
"@react-email/text": "0.0.6", "@react-email/text": "0.0.6",
"@lingui/macro": "^5.2.0",
"@lingui/react": "^5.2.0",
"nodemailer": "^6.9.9", "nodemailer": "^6.9.9",
"react-email": "^1.9.5", "react-email": "^1.9.5",
"resend": "^2.0.0" "resend": "^2.0.0"
@ -45,4 +47,4 @@
"@types/nodemailer": "^6.4.14", "@types/nodemailer": "^6.4.14",
"tsup": "^7.1.0" "tsup": "^7.1.0"
} }
} }

View File

@ -25,9 +25,9 @@
"@documenso/prisma": "*", "@documenso/prisma": "*",
"@documenso/signing": "*", "@documenso/signing": "*",
"@honeybadger-io/js": "^6.10.1", "@honeybadger-io/js": "^6.10.1",
"@lingui/core": "^4.11.3", "@lingui/core": "^5.2.0",
"@lingui/macro": "^4.11.3", "@lingui/macro": "^5.2.0",
"@lingui/react": "^4.11.3", "@lingui/react": "^5.2.0",
"@noble/ciphers": "0.4.0", "@noble/ciphers": "0.4.0",
"@noble/hashes": "1.3.2", "@noble/hashes": "1.3.2",
"@node-rs/bcrypt": "^1.10.0", "@node-rs/bcrypt": "^1.10.0",

View File

@ -5,6 +5,9 @@ import type { PDFDocument } from 'pdf-lib';
import { RotationTypes, degrees, radiansToDegrees } from 'pdf-lib'; import { RotationTypes, degrees, radiansToDegrees } from 'pdf-lib';
import { P, match } from 'ts-pattern'; import { P, match } from 'ts-pattern';
// Todo: Check if this is okay to do compared to the old approach.
import fontCaveat from '@documenso/assets/fonts/caveat.ttf?inline';
import fontNoto from '@documenso/assets/fonts/noto-sans.ttf?inline';
import { import {
DEFAULT_HANDWRITING_FONT_SIZE, DEFAULT_HANDWRITING_FONT_SIZE,
DEFAULT_STANDARD_FONT_SIZE, DEFAULT_STANDARD_FONT_SIZE,
@ -25,20 +28,8 @@ import {
ZRadioFieldMeta, ZRadioFieldMeta,
ZTextFieldMeta, ZTextFieldMeta,
} from '../../types/field-meta'; } from '../../types/field-meta';
import { env } from '../../utils/env';
export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignature) => { export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignature) => {
const fontCaveatUri = env('FONT_CAVEAT_URI');
const fontNotoSansUri = env('FONT_NOTO_SANS_URI');
if (!fontCaveatUri || !fontNotoSansUri) {
throw new Error('Missing font URI');
}
const fontCaveat = await fetch(fontCaveatUri).then(async (res) => res.arrayBuffer());
const fontNoto = await fetch(fontNotoSansUri).then(async (res) => res.arrayBuffer());
const isSignatureField = isSignatureFieldType(field.type); const isSignatureField = isSignatureFieldType(field.type);
pdf.registerFontkit(fontkit); pdf.registerFontkit(fontkit);

View File

@ -29,8 +29,8 @@
"@documenso/lib": "*", "@documenso/lib": "*",
"@hello-pangea/dnd": "^16.6.0", "@hello-pangea/dnd": "^16.6.0",
"@hookform/resolvers": "^3.3.0", "@hookform/resolvers": "^3.3.0",
"@lingui/macro": "^4.11.3", "@lingui/macro": "^5.2.0",
"@lingui/react": "^4.11.3", "@lingui/react": "^5.2.0",
"@radix-ui/react-accordion": "^1.1.1", "@radix-ui/react-accordion": "^1.1.1",
"@radix-ui/react-alert-dialog": "^1.0.3", "@radix-ui/react-alert-dialog": "^1.0.3",
"@radix-ui/react-aspect-ratio": "^1.0.2", "@radix-ui/react-aspect-ratio": "^1.0.2",