mirror of
https://github.com/documenso/documenso.git
synced 2025-11-20 11:41:44 +10:00
fix: wip
This commit is contained in:
@ -42,8 +42,8 @@ import {
|
||||
ZRadioFieldMeta,
|
||||
ZTextFieldMeta,
|
||||
} from '@documenso/lib/types/field-meta';
|
||||
import { getFile } from '@documenso/lib/universal/upload/get-file';
|
||||
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
|
||||
import { getFileServerSide } from '@documenso/lib/universal/upload/get-file.server';
|
||||
import { putPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
||||
import {
|
||||
getPresignGetUrl,
|
||||
getPresignPostUrl,
|
||||
@ -490,14 +490,14 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
let documentDataId = document.documentDataId;
|
||||
|
||||
if (body.formValues) {
|
||||
const pdf = await getFile(document.documentData);
|
||||
const pdf = await getFileServerSide(document.documentData);
|
||||
|
||||
const prefilled = await insertFormValuesInPdf({
|
||||
pdf: Buffer.from(pdf),
|
||||
formValues: body.formValues,
|
||||
});
|
||||
|
||||
const newDocumentData = await putPdfFile({
|
||||
const newDocumentData = await putPdfFileServerSide({
|
||||
name: fileName,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(prefilled),
|
||||
@ -598,14 +598,14 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
if (body.formValues) {
|
||||
const fileName = document.title.endsWith('.pdf') ? document.title : `${document.title}.pdf`;
|
||||
|
||||
const pdf = await getFile(document.documentData);
|
||||
const pdf = await getFileServerSide(document.documentData);
|
||||
|
||||
const prefilled = await insertFormValuesInPdf({
|
||||
pdf: Buffer.from(pdf),
|
||||
formValues: body.formValues,
|
||||
});
|
||||
|
||||
const newDocumentData = await putPdfFile({
|
||||
const newDocumentData = await putPdfFileServerSide({
|
||||
name: fileName,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(prefilled),
|
||||
|
||||
@ -12,11 +12,11 @@
|
||||
"dependencies": {
|
||||
"@documenso/lib": "*",
|
||||
"@documenso/prisma": "*",
|
||||
"@hono/zod-validator": "^0.4.2",
|
||||
"@hono/standard-validator": "^0.1.2",
|
||||
"@oslojs/crypto": "^1.0.1",
|
||||
"@oslojs/encoding": "^1.1.0",
|
||||
"arctic": "^3.1.0",
|
||||
"hono": "4.6.15",
|
||||
"hono": "4.7.0",
|
||||
"luxon": "^3.5.0",
|
||||
"nanoid": "^4.0.2",
|
||||
"ts-pattern": "^5.0.5",
|
||||
|
||||
@ -5,14 +5,28 @@ import { type Session, type User, UserSecurityAuditLogType } from '@prisma/clien
|
||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
/**
|
||||
* The user object to pass around the app.
|
||||
*
|
||||
* Do not put anything sensitive in here since it will be public.
|
||||
*/
|
||||
export type SessionUser = Pick<
|
||||
User,
|
||||
| 'id'
|
||||
| 'name'
|
||||
| 'email'
|
||||
| 'emailVerified'
|
||||
| 'avatarImageId'
|
||||
| 'twoFactorEnabled'
|
||||
| 'roles'
|
||||
| 'signature'
|
||||
| 'url'
|
||||
>;
|
||||
|
||||
export type SessionValidationResult =
|
||||
| {
|
||||
session: Session;
|
||||
user: User;
|
||||
// user: Pick<
|
||||
// User,
|
||||
// 'id' | 'name' | 'email' | 'emailVerified' | 'avatarImageId' | 'twoFactorEnabled' | 'roles' // Todo
|
||||
// >;
|
||||
user: SessionUser;
|
||||
isAuthenticated: true;
|
||||
}
|
||||
| { session: null; user: null; isAuthenticated: false };
|
||||
@ -36,7 +50,7 @@ export const createSession = async (
|
||||
|
||||
const session: Session = {
|
||||
id: hashedSessionId,
|
||||
sessionToken: hashedSessionId, // todo
|
||||
sessionToken: hashedSessionId,
|
||||
userId,
|
||||
updatedAt: new Date(),
|
||||
createdAt: new Date(),
|
||||
@ -69,23 +83,26 @@ export const validateSessionToken = async (token: string): Promise<SessionValida
|
||||
id: sessionId,
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
user: {
|
||||
/**
|
||||
* Do not expose anything sensitive here.
|
||||
*/
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
emailVerified: true,
|
||||
avatarImageId: true,
|
||||
twoFactorEnabled: true,
|
||||
roles: true,
|
||||
signature: true,
|
||||
url: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// user: {
|
||||
// select: {
|
||||
// id: true,
|
||||
// name: true,
|
||||
// email: true,
|
||||
// emailVerified: true,
|
||||
// avatarImageId: true,
|
||||
// twoFactorEnabled: true,
|
||||
// },
|
||||
// },
|
||||
|
||||
// todo; how can result.user be null?
|
||||
if (result === null || !result.user) {
|
||||
if (!result?.user) {
|
||||
return { session: null, user: null, isAuthenticated: false };
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { sValidator } from '@hono/standard-validator';
|
||||
import { compare } from '@node-rs/bcrypt';
|
||||
import { Hono } from 'hono';
|
||||
import { DateTime } from 'luxon';
|
||||
@ -42,7 +42,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
||||
/**
|
||||
* Authorize endpoint.
|
||||
*/
|
||||
.post('/authorize', zValidator('json', ZSignInSchema), async (c) => {
|
||||
.post('/authorize', sValidator('json', ZSignInSchema), async (c) => {
|
||||
const requestMetadata = c.get('requestMetadata');
|
||||
|
||||
const { email, password, totpCode, backupCode } = c.req.valid('json');
|
||||
@ -131,7 +131,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
||||
/**
|
||||
* Signup endpoint.
|
||||
*/
|
||||
.post('/signup', zValidator('json', ZSignUpSchema), async (c) => {
|
||||
.post('/signup', sValidator('json', ZSignUpSchema), async (c) => {
|
||||
if (env('NEXT_PUBLIC_DISABLE_SIGNUP') === 'true') {
|
||||
throw new AppError('SIGNUP_DISABLED', {
|
||||
message: 'Signups are disabled.',
|
||||
@ -160,7 +160,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
||||
/**
|
||||
* Update password endpoint.
|
||||
*/
|
||||
.post('/update-password', zValidator('json', ZUpdatePasswordSchema), async (c) => {
|
||||
.post('/update-password', sValidator('json', ZUpdatePasswordSchema), async (c) => {
|
||||
const { password, currentPassword } = c.req.valid('json');
|
||||
const requestMetadata = c.get('requestMetadata');
|
||||
|
||||
@ -182,7 +182,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
||||
/**
|
||||
* Verify email endpoint.
|
||||
*/
|
||||
.post('/verify-email', zValidator('json', ZVerifyEmailSchema), async (c) => {
|
||||
.post('/verify-email', sValidator('json', ZVerifyEmailSchema), async (c) => {
|
||||
const { state, userId } = await verifyEmail({ token: c.req.valid('json').token });
|
||||
|
||||
// If email is verified, automatically authenticate user.
|
||||
@ -197,7 +197,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
||||
/**
|
||||
* Resend verification email endpoint.
|
||||
*/
|
||||
.post('/resend-verify-email', zValidator('json', ZResendVerifyEmailSchema), async (c) => {
|
||||
.post('/resend-verify-email', sValidator('json', ZResendVerifyEmailSchema), async (c) => {
|
||||
const { email } = c.req.valid('json');
|
||||
|
||||
await jobsClient.triggerJob({
|
||||
@ -212,7 +212,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
||||
/**
|
||||
* Forgot password endpoint.
|
||||
*/
|
||||
.post('/forgot-password', zValidator('json', ZForgotPasswordSchema), async (c) => {
|
||||
.post('/forgot-password', sValidator('json', ZForgotPasswordSchema), async (c) => {
|
||||
const { email } = c.req.valid('json');
|
||||
|
||||
await forgotPassword({
|
||||
@ -224,7 +224,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
||||
/**
|
||||
* Reset password endpoint.
|
||||
*/
|
||||
.post('/reset-password', zValidator('json', ZResetPasswordSchema), async (c) => {
|
||||
.post('/reset-password', sValidator('json', ZResetPasswordSchema), async (c) => {
|
||||
const { token, password } = c.req.valid('json');
|
||||
|
||||
const requestMetadata = c.get('requestMetadata');
|
||||
@ -258,7 +258,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
||||
*/
|
||||
.post(
|
||||
'/2fa/enable',
|
||||
zValidator(
|
||||
sValidator(
|
||||
'json',
|
||||
z.object({
|
||||
code: z.string(),
|
||||
@ -304,7 +304,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
||||
*/
|
||||
.post(
|
||||
'/2fa/disable',
|
||||
zValidator(
|
||||
sValidator(
|
||||
'json',
|
||||
z.object({
|
||||
totpCode: z.string().trim().optional(),
|
||||
@ -325,6 +325,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
||||
email: true,
|
||||
twoFactorEnabled: true,
|
||||
twoFactorSecret: true,
|
||||
twoFactorBackupCodes: true,
|
||||
},
|
||||
});
|
||||
|
||||
@ -349,7 +350,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
||||
*/
|
||||
.post(
|
||||
'/2fa/view-recovery-codes',
|
||||
zValidator(
|
||||
sValidator(
|
||||
'json',
|
||||
z.object({
|
||||
token: z.string(),
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { sValidator } from '@hono/standard-validator';
|
||||
import { Google, decodeIdToken, generateCodeVerifier, generateState } from 'arctic';
|
||||
import { Hono } from 'hono';
|
||||
import { deleteCookie, setCookie } from 'hono/cookie';
|
||||
@ -35,7 +35,7 @@ export const googleRoute = new Hono<HonoAuthContext>()
|
||||
/**
|
||||
* Authorize endpoint.
|
||||
*/
|
||||
.post('/authorize', zValidator('json', ZGoogleAuthorizeSchema), (c) => {
|
||||
.post('/authorize', sValidator('json', ZGoogleAuthorizeSchema), (c) => {
|
||||
const scopes = options.scope;
|
||||
const state = generateState();
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { sValidator } from '@hono/standard-validator';
|
||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||
import { verifyAuthenticationResponse } from '@simplewebauthn/server';
|
||||
import { Hono } from 'hono';
|
||||
@ -17,7 +17,7 @@ export const passkeyRoute = new Hono<HonoAuthContext>()
|
||||
/**
|
||||
* Authorize endpoint.
|
||||
*/
|
||||
.post('/authorize', zValidator('json', ZPasskeyAuthorizeSchema), async (c) => {
|
||||
.post('/authorize', sValidator('json', ZPasskeyAuthorizeSchema), async (c) => {
|
||||
const requestMetadata = c.get('requestMetadata');
|
||||
|
||||
const { csrfToken, credential } = c.req.valid('json');
|
||||
@ -129,7 +129,7 @@ export const passkeyRoute = new Hono<HonoAuthContext>()
|
||||
|
||||
// .post(
|
||||
// '/pre-authenticate',
|
||||
// zValidator(
|
||||
// sValidator(
|
||||
// 'json',
|
||||
// z.object({
|
||||
// code: z.string(),
|
||||
|
||||
1
packages/email/ambient.d.ts
vendored
1
packages/email/ambient.d.ts
vendored
@ -1 +0,0 @@
|
||||
declare module '@documenso/tailwind-config';
|
||||
@ -9,6 +9,9 @@ export type RenderOptions = ReactEmail.Options & {
|
||||
branding?: BrandingSettings;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const colors = (config.theme?.extend?.colors || {}) as Record<string, string>;
|
||||
|
||||
export const render = (element: React.ReactNode, options?: RenderOptions) => {
|
||||
const { branding, ...otherOptions } = options ?? {};
|
||||
|
||||
@ -17,7 +20,7 @@ export const render = (element: React.ReactNode, options?: RenderOptions) => {
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
@ -36,7 +39,7 @@ export const renderAsync = async (element: React.ReactNode, options?: RenderOpti
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: config.theme.extend.colors,
|
||||
colors,
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
||||
@ -31,7 +31,7 @@ export const DocumentInviteEmailTemplate = ({
|
||||
role,
|
||||
selfSigner = false,
|
||||
isTeamInvite = false,
|
||||
teamName,
|
||||
teamName = '',
|
||||
includeSenderDetails,
|
||||
}: DocumentInviteEmailTemplateProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import type { Session, User } from '@documenso/prisma/client';
|
||||
import type { SessionUser } from '@documenso/auth/server/lib/session/session';
|
||||
import type { Session } from '@documenso/prisma/client';
|
||||
|
||||
import type { TGetTeamByUrlResponse } from '../../server-only/team/get-team';
|
||||
import type { TGetTeamsResponse } from '../../server-only/team/get-teams';
|
||||
|
||||
export type AppSession = {
|
||||
session: Session;
|
||||
user: User; // Todo: Remove password, and redundant fields.
|
||||
user: SessionUser;
|
||||
currentTeam: TGetTeamByUrlResponse | null;
|
||||
teams: TGetTeamsResponse;
|
||||
};
|
||||
|
||||
@ -114,9 +114,11 @@ export const run = async ({
|
||||
emailMessage = customEmail?.message ?? '';
|
||||
|
||||
if (!emailMessage) {
|
||||
const inviterName = user.name || '';
|
||||
|
||||
emailMessage = i18n._(
|
||||
team.teamGlobalSettings?.includeSenderDetails
|
||||
? msg`${user.name} on behalf of "${team.name}" has invited you to ${recipientActionVerb} the document "${document.title}".`
|
||||
? msg`${inviterName} on behalf of "${team.name}" has invited you to ${recipientActionVerb} the document "${document.title}".`
|
||||
: msg`${team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`,
|
||||
);
|
||||
}
|
||||
|
||||
@ -19,8 +19,8 @@ import {
|
||||
ZWebhookDocumentSchema,
|
||||
mapDocumentToWebhookDocumentPayload,
|
||||
} from '../../../types/webhook-payload';
|
||||
import { getFile } from '../../../universal/upload/get-file';
|
||||
import { putPdfFile } from '../../../universal/upload/put-file';
|
||||
import { getFileServerSide } from '../../../universal/upload/get-file.server';
|
||||
import { putPdfFileServerSide } from '../../../universal/upload/put-file.server';
|
||||
import { fieldsContainUnsignedRequiredField } from '../../../utils/advanced-fields-helpers';
|
||||
import { createDocumentAuditLogData } from '../../../utils/document-audit-logs';
|
||||
import type { JobRunIO } from '../../client/_internal/job';
|
||||
@ -114,7 +114,7 @@ export const run = async ({
|
||||
documentData.data = documentData.initialData;
|
||||
}
|
||||
|
||||
const pdfData = await getFile(documentData);
|
||||
const pdfData = await getFileServerSide(documentData);
|
||||
|
||||
const certificateData =
|
||||
(document.team?.teamGlobalSettings?.includeSigningCertificate ?? true)
|
||||
@ -160,7 +160,7 @@ export const run = async ({
|
||||
|
||||
const { name } = path.parse(document.title);
|
||||
|
||||
const documentData = await putPdfFile({
|
||||
const documentData = await putPdfFileServerSide({
|
||||
name: `${name}_signed.pdf`,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(pdfBuffer),
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
"@upstash/redis": "^1.20.6",
|
||||
"@vvo/tzdb": "^6.117.0",
|
||||
"inngest": "^3.19.13",
|
||||
"kysely": "^0.26.3",
|
||||
"kysely": "0.26.3",
|
||||
"luxon": "^3.4.0",
|
||||
"micro": "^10.0.1",
|
||||
"nanoid": "^4.0.2",
|
||||
|
||||
@ -8,7 +8,10 @@ import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { validateTwoFactorAuthentication } from './validate-2fa';
|
||||
|
||||
type DisableTwoFactorAuthenticationOptions = {
|
||||
user: Pick<User, 'id' | 'email' | 'twoFactorEnabled' | 'twoFactorSecret'>;
|
||||
user: Pick<
|
||||
User,
|
||||
'id' | 'email' | 'twoFactorEnabled' | 'twoFactorSecret' | 'twoFactorBackupCodes'
|
||||
>;
|
||||
totpCode?: string;
|
||||
backupCode?: string;
|
||||
requestMetadata?: RequestMetadata;
|
||||
|
||||
@ -24,8 +24,8 @@ import {
|
||||
ZWebhookDocumentSchema,
|
||||
mapDocumentToWebhookDocumentPayload,
|
||||
} from '../../types/webhook-payload';
|
||||
import { getFile } from '../../universal/upload/get-file';
|
||||
import { putPdfFile } from '../../universal/upload/put-file';
|
||||
import { getFileServerSide } from '../../universal/upload/get-file.server';
|
||||
import { putPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||
import { createDocumentAuthOptions, createRecipientAuthOptions } from '../../utils/document-auth';
|
||||
import { determineDocumentVisibility } from '../../utils/document-visibility';
|
||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||
@ -97,11 +97,11 @@ export const createDocumentV2 = async ({
|
||||
});
|
||||
|
||||
if (documentData) {
|
||||
const buffer = await getFile(documentData);
|
||||
const buffer = await getFileServerSide(documentData);
|
||||
|
||||
const normalizedPdf = await makeNormalizedPdf(Buffer.from(buffer));
|
||||
|
||||
const newDocumentData = await putPdfFile({
|
||||
const newDocumentData = await putPdfFileServerSide({
|
||||
name: title.endsWith('.pdf') ? title : `${title}.pdf`,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(normalizedPdf),
|
||||
|
||||
@ -13,8 +13,8 @@ import {
|
||||
ZWebhookDocumentSchema,
|
||||
mapDocumentToWebhookDocumentPayload,
|
||||
} from '../../types/webhook-payload';
|
||||
import { getFile } from '../../universal/upload/get-file';
|
||||
import { putPdfFile } from '../../universal/upload/put-file';
|
||||
import { getFileServerSide } from '../../universal/upload/get-file.server';
|
||||
import { putPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||
import { determineDocumentVisibility } from '../../utils/document-visibility';
|
||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||
|
||||
@ -96,11 +96,11 @@ export const createDocument = async ({
|
||||
});
|
||||
|
||||
if (documentData) {
|
||||
const buffer = await getFile(documentData);
|
||||
const buffer = await getFileServerSide(documentData);
|
||||
|
||||
const normalizedPdf = await makeNormalizedPdf(Buffer.from(buffer));
|
||||
|
||||
const newDocumentData = await putPdfFile({
|
||||
const newDocumentData = await putPdfFileServerSide({
|
||||
name: title.endsWith('.pdf') ? title : `${title}.pdf`,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(normalizedPdf),
|
||||
|
||||
@ -14,8 +14,8 @@ import {
|
||||
mapDocumentToWebhookDocumentPayload,
|
||||
} from '../../types/webhook-payload';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { getFile } from '../../universal/upload/get-file';
|
||||
import { putPdfFile } from '../../universal/upload/put-file';
|
||||
import { getFileServerSide } from '../../universal/upload/get-file.server';
|
||||
import { putPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||
import { fieldsContainUnsignedRequiredField } from '../../utils/advanced-fields-helpers';
|
||||
import { getCertificatePdf } from '../htmltopdf/get-certificate-pdf';
|
||||
import { flattenAnnotations } from '../pdf/flatten-annotations';
|
||||
@ -102,7 +102,7 @@ export const sealDocument = async ({
|
||||
}
|
||||
|
||||
// !: Need to write the fields onto the document as a hard copy
|
||||
const pdfData = await getFile(documentData);
|
||||
const pdfData = await getFileServerSide(documentData);
|
||||
|
||||
const certificateData =
|
||||
(document.team?.teamGlobalSettings?.includeSigningCertificate ?? true)
|
||||
@ -142,7 +142,7 @@ export const sealDocument = async ({
|
||||
|
||||
const { name } = path.parse(document.title);
|
||||
|
||||
const { data: newData } = await putPdfFile({
|
||||
const { data: newData } = await putPdfFileServerSide({
|
||||
name: `${name}_signed.pdf`,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(pdfBuffer),
|
||||
|
||||
@ -12,7 +12,7 @@ import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||
import { extractDerivedDocumentEmailSettings } from '../../types/document-email';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { getFile } from '../../universal/upload/get-file';
|
||||
import { getFileServerSide } from '../../universal/upload/get-file.server';
|
||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||
import { env } from '../../utils/env';
|
||||
import { renderCustomEmailTemplate } from '../../utils/render-custom-email-template';
|
||||
@ -57,7 +57,7 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
|
||||
|
||||
const { user: owner } = document;
|
||||
|
||||
const completedDocument = await getFile(document.documentData);
|
||||
const completedDocument = await getFileServerSide(document.documentData);
|
||||
|
||||
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000';
|
||||
|
||||
|
||||
@ -9,7 +9,6 @@ import {
|
||||
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
@ -19,7 +18,8 @@ import {
|
||||
ZWebhookDocumentSchema,
|
||||
mapDocumentToWebhookDocumentPayload,
|
||||
} from '../../types/webhook-payload';
|
||||
import { getFile } from '../../universal/upload/get-file';
|
||||
import { getFileServerSide } from '../../universal/upload/get-file.server';
|
||||
import { putPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||
import { insertFormValuesInPdf } from '../pdf/insert-form-values-in-pdf';
|
||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||
|
||||
@ -100,7 +100,7 @@ export const sendDocument = async ({
|
||||
}
|
||||
|
||||
if (document.formValues) {
|
||||
const file = await getFile(documentData);
|
||||
const file = await getFileServerSide(documentData);
|
||||
|
||||
const prefilled = await insertFormValuesInPdf({
|
||||
pdf: Buffer.from(file),
|
||||
@ -114,7 +114,7 @@ export const sendDocument = async ({
|
||||
fileName = `${document.title}.pdf`;
|
||||
}
|
||||
|
||||
const newDocumentData = await putPdfFile({
|
||||
const newDocumentData = await putPdfFileServerSide({
|
||||
name: fileName,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(prefilled),
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
|
||||
export interface GetTemplateByDirectLinkTokenOptions {
|
||||
token: string;
|
||||
}
|
||||
@ -7,7 +9,7 @@ export interface GetTemplateByDirectLinkTokenOptions {
|
||||
export const getTemplateByDirectLinkToken = async ({
|
||||
token,
|
||||
}: GetTemplateByDirectLinkTokenOptions) => {
|
||||
const template = await prisma.template.findFirstOrThrow({
|
||||
const template = await prisma.template.findFirst({
|
||||
where: {
|
||||
directLink: {
|
||||
token,
|
||||
@ -26,8 +28,16 @@ export const getTemplateByDirectLinkToken = async ({
|
||||
},
|
||||
});
|
||||
|
||||
const directLink = template?.directLink;
|
||||
|
||||
// Doing this to enforce type safety for directLink.
|
||||
if (!directLink) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND);
|
||||
}
|
||||
|
||||
return {
|
||||
...template,
|
||||
directLink,
|
||||
fields: template.recipients.map((recipient) => recipient.fields).flat(),
|
||||
};
|
||||
};
|
||||
|
||||
50
packages/lib/universal/upload/get-file.server.ts
Normal file
50
packages/lib/universal/upload/get-file.server.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { DocumentDataType } from '@prisma/client';
|
||||
import { base64 } from '@scure/base';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
export type GetFileOptions = {
|
||||
type: DocumentDataType;
|
||||
data: string;
|
||||
};
|
||||
|
||||
export const getFileServerSide = async ({ type, data }: GetFileOptions) => {
|
||||
return await match(type)
|
||||
.with(DocumentDataType.BYTES, () => getFileFromBytes(data))
|
||||
.with(DocumentDataType.BYTES_64, () => getFileFromBytes64(data))
|
||||
.with(DocumentDataType.S3_PATH, async () => getFileFromS3(data))
|
||||
.exhaustive();
|
||||
};
|
||||
|
||||
const getFileFromBytes = (data: string) => {
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
const binaryData = encoder.encode(data);
|
||||
|
||||
return binaryData;
|
||||
};
|
||||
|
||||
const getFileFromBytes64 = (data: string) => {
|
||||
const binaryData = base64.decode(data);
|
||||
|
||||
return binaryData;
|
||||
};
|
||||
|
||||
const getFileFromS3 = async (key: string) => {
|
||||
const { getPresignGetUrl } = await import('./server-actions');
|
||||
|
||||
const { url } = await getPresignGetUrl(key);
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to get file "${key}", failed with status code ${response.status}`);
|
||||
}
|
||||
|
||||
const buffer = await response.arrayBuffer();
|
||||
|
||||
const binaryData = new Uint8Array(buffer);
|
||||
|
||||
return binaryData;
|
||||
};
|
||||
@ -30,9 +30,23 @@ const getFileFromBytes64 = (data: string) => {
|
||||
};
|
||||
|
||||
const getFileFromS3 = async (key: string) => {
|
||||
const { getPresignGetUrl } = await import('./server-actions');
|
||||
const getPresignedUrlResponse = await fetch(`/api/files/presigned-get-url`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
key,
|
||||
}),
|
||||
});
|
||||
|
||||
const { url } = await getPresignGetUrl(key);
|
||||
if (!getPresignedUrlResponse.ok) {
|
||||
throw new Error(
|
||||
`Failed to get presigned url with key "${key}", failed with status code ${getPresignedUrlResponse.status}`,
|
||||
);
|
||||
}
|
||||
|
||||
const { url } = await getPresignedUrlResponse.json();
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
|
||||
85
packages/lib/universal/upload/put-file.server.ts
Normal file
85
packages/lib/universal/upload/put-file.server.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import { DocumentDataType } from '@prisma/client';
|
||||
import { base64 } from '@scure/base';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { env } from '@documenso/lib/utils/env';
|
||||
|
||||
import { AppError } from '../../errors/app-error';
|
||||
import { createDocumentData } from '../../server-only/document-data/create-document-data';
|
||||
import { uploadS3File } from './server-actions';
|
||||
|
||||
type File = {
|
||||
name: string;
|
||||
type: string;
|
||||
arrayBuffer: () => Promise<ArrayBuffer>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Uploads a document file to the appropriate storage location and creates
|
||||
* a document data record.
|
||||
*/
|
||||
export const putPdfFileServerSide = async (file: File) => {
|
||||
const isEncryptedDocumentsAllowed = false; // Was feature flag.
|
||||
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
|
||||
const pdf = await PDFDocument.load(arrayBuffer).catch((e) => {
|
||||
console.error(`PDF upload parse error: ${e.message}`);
|
||||
|
||||
throw new AppError('INVALID_DOCUMENT_FILE');
|
||||
});
|
||||
|
||||
if (!isEncryptedDocumentsAllowed && pdf.isEncrypted) {
|
||||
throw new AppError('INVALID_DOCUMENT_FILE');
|
||||
}
|
||||
|
||||
if (!file.name.endsWith('.pdf')) {
|
||||
file.name = `${file.name}.pdf`;
|
||||
}
|
||||
|
||||
const { type, data } = await putFileServerSide(file);
|
||||
|
||||
return await createDocumentData({ type, data });
|
||||
};
|
||||
|
||||
/**
|
||||
* Uploads a file to the appropriate storage location.
|
||||
*/
|
||||
export const putFileServerSide = async (file: File) => {
|
||||
const NEXT_PUBLIC_UPLOAD_TRANSPORT = env('NEXT_PUBLIC_UPLOAD_TRANSPORT');
|
||||
|
||||
return await match(NEXT_PUBLIC_UPLOAD_TRANSPORT)
|
||||
.with('s3', async () => putFileInS3(file))
|
||||
.otherwise(async () => putFileInDatabase(file));
|
||||
};
|
||||
|
||||
const putFileInDatabase = async (file: File) => {
|
||||
const contents = await file.arrayBuffer();
|
||||
|
||||
const binaryData = new Uint8Array(contents);
|
||||
|
||||
const asciiData = base64.encode(binaryData);
|
||||
|
||||
return {
|
||||
type: DocumentDataType.BYTES_64,
|
||||
data: asciiData,
|
||||
};
|
||||
};
|
||||
|
||||
const putFileInS3 = async (file: File) => {
|
||||
const buffer = await file.arrayBuffer();
|
||||
|
||||
const blob = new Blob([buffer], { type: file.type });
|
||||
|
||||
const newFile = new File([blob], file.name, {
|
||||
type: file.type,
|
||||
});
|
||||
|
||||
const { key } = await uploadS3File(newFile);
|
||||
|
||||
return {
|
||||
type: DocumentDataType.S3_PATH,
|
||||
data: key,
|
||||
};
|
||||
};
|
||||
@ -1,12 +1,15 @@
|
||||
import { DocumentDataType } from '@prisma/client';
|
||||
import { base64 } from '@scure/base';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { env } from '@documenso/lib/utils/env';
|
||||
import type {
|
||||
TGetPresignedPostUrlResponse,
|
||||
TUploadPdfResponse,
|
||||
} from '@documenso/remix/server/api/files.types';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { AppError } from '../../errors/app-error';
|
||||
import { createDocumentData } from '../../server-only/document-data/create-document-data';
|
||||
|
||||
type File = {
|
||||
name: string;
|
||||
@ -14,32 +17,29 @@ type File = {
|
||||
arrayBuffer: () => Promise<ArrayBuffer>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Uploads a document file to the appropriate storage location and creates
|
||||
* a document data record.
|
||||
*/
|
||||
export const putPdfFile = async (file: File) => {
|
||||
const isEncryptedDocumentsAllowed = false; // Was feature flag.
|
||||
const formData = new FormData();
|
||||
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
// Create a proper File object from the data
|
||||
const buffer = await file.arrayBuffer();
|
||||
const blob = new Blob([buffer], { type: file.type });
|
||||
const properFile = new File([blob], file.name, { type: file.type });
|
||||
|
||||
const pdf = await PDFDocument.load(arrayBuffer).catch((e) => {
|
||||
console.error(`PDF upload parse error: ${e.message}`);
|
||||
formData.append('file', properFile);
|
||||
|
||||
throw new AppError('INVALID_DOCUMENT_FILE');
|
||||
const response = await fetch('/api/files/upload-pdf', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!isEncryptedDocumentsAllowed && pdf.isEncrypted) {
|
||||
throw new AppError('INVALID_DOCUMENT_FILE');
|
||||
if (!response.ok) {
|
||||
console.error('Upload failed:', response.statusText);
|
||||
throw new AppError('UPLOAD_FAILED');
|
||||
}
|
||||
|
||||
if (!file.name.endsWith('.pdf')) {
|
||||
file.name = `${file.name}.pdf`;
|
||||
}
|
||||
const result: TUploadPdfResponse = await response.json();
|
||||
|
||||
const { type, data } = await putFile(file);
|
||||
|
||||
return await createDocumentData({ type, data });
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -67,9 +67,27 @@ const putFileInDatabase = async (file: File) => {
|
||||
};
|
||||
|
||||
const putFileInS3 = async (file: File) => {
|
||||
const { getPresignPostUrl } = await import('./server-actions');
|
||||
const getPresignedUrlResponse = await fetch(
|
||||
`${NEXT_PUBLIC_WEBAPP_URL()}/api/files/presigned-post-url`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
fileName: file.name,
|
||||
contentType: file.type,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { url, key } = await getPresignPostUrl(file.name, file.type);
|
||||
if (!getPresignedUrlResponse.ok) {
|
||||
throw new Error(
|
||||
`Failed to get presigned post url, failed with status code ${getPresignedUrlResponse.status}`,
|
||||
);
|
||||
}
|
||||
|
||||
const { url, key }: TGetPresignedPostUrlResponse = await getPresignedUrlResponse.json();
|
||||
|
||||
const body = await file.arrayBuffer();
|
||||
|
||||
|
||||
@ -9,37 +9,21 @@ import path from 'node:path';
|
||||
|
||||
import { env } from '@documenso/lib/utils/env';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { ONE_HOUR, ONE_SECOND } from '../../constants/time';
|
||||
import { alphaid } from '../id';
|
||||
|
||||
export const getPresignPostUrl = async (fileName: string, contentType: string) => {
|
||||
export const getPresignPostUrl = async (fileName: string, contentType: string, userId?: number) => {
|
||||
const client = getS3Client();
|
||||
|
||||
const { getSignedUrl } = await import('@aws-sdk/s3-request-presigner');
|
||||
|
||||
const token: { id: string } | null = null;
|
||||
|
||||
try {
|
||||
const baseUrl = NEXT_PUBLIC_WEBAPP_URL();
|
||||
|
||||
// Todo
|
||||
// token = await getToken({
|
||||
// req: new NextRequest(baseUrl, {
|
||||
// headers: headers(),
|
||||
// }),
|
||||
// });
|
||||
} catch (err) {
|
||||
// Non server-component environment
|
||||
}
|
||||
|
||||
// Get the basename and extension for the file
|
||||
const { name, ext } = path.parse(fileName);
|
||||
|
||||
let key = `${alphaid(12)}/${slugify(name)}${ext}`;
|
||||
|
||||
if (token) {
|
||||
key = `${token.id}/${key}`;
|
||||
if (userId) {
|
||||
key = `${userId}/${key}`;
|
||||
}
|
||||
|
||||
const putObjectCommand = new PutObjectCommand({
|
||||
@ -104,6 +88,31 @@ export const getPresignGetUrl = async (key: string) => {
|
||||
return { key, url };
|
||||
};
|
||||
|
||||
/**
|
||||
* Uploads a file to S3.
|
||||
*/
|
||||
export const uploadS3File = async (file: File) => {
|
||||
const client = getS3Client();
|
||||
|
||||
// Get the basename and extension for the file
|
||||
const { name, ext } = path.parse(file.name);
|
||||
|
||||
const key = `${alphaid(12)}/${slugify(name)}${ext}`;
|
||||
|
||||
const fileBuffer = await file.arrayBuffer();
|
||||
|
||||
const response = await client.send(
|
||||
new PutObjectCommand({
|
||||
Bucket: env('NEXT_PRIVATE_UPLOAD_BUCKET'),
|
||||
Key: key,
|
||||
Body: Buffer.from(fileBuffer),
|
||||
ContentType: file.type,
|
||||
}),
|
||||
);
|
||||
|
||||
return { key, response };
|
||||
};
|
||||
|
||||
export const deleteS3File = async (key: string) => {
|
||||
const client = getS3Client();
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.4.2",
|
||||
"kysely": "^0.27.3",
|
||||
"kysely": "0.26.3",
|
||||
"prisma": "^5.4.2",
|
||||
"prisma-extension-kysely": "^2.1.0",
|
||||
"ts-pattern": "^5.0.6"
|
||||
|
||||
4
packages/tailwind-config/index.d.ts
vendored
Normal file
4
packages/tailwind-config/index.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
import type { Config } from 'tailwindcss';
|
||||
|
||||
declare const config: Config;
|
||||
export default config;
|
||||
@ -2,6 +2,7 @@
|
||||
"name": "@documenso/tailwind-config",
|
||||
"version": "0.0.0",
|
||||
"main": "index.cjs",
|
||||
"types": "index.d.ts",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"clean": "rimraf node_modules"
|
||||
@ -18,4 +19,4 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import type { Context } from 'hono';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { SessionUser } from '@documenso/auth/server/lib/session/session';
|
||||
import { getSession } from '@documenso/auth/server/lib/utils/get-session';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { extractRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import type { Session, User } from '@documenso/prisma/client';
|
||||
import type { Session } from '@documenso/prisma/client';
|
||||
|
||||
type CreateTrpcContextOptions = {
|
||||
c: Context;
|
||||
@ -59,7 +60,7 @@ export type TrpcContext = (
|
||||
}
|
||||
| {
|
||||
session: Session;
|
||||
user: User;
|
||||
user: SessionUser;
|
||||
}
|
||||
) & {
|
||||
teamId: number | undefined;
|
||||
|
||||
@ -67,13 +67,6 @@ import { FieldAdvancedSettings } from './field-item-advanced-settings';
|
||||
import { MissingSignatureFieldDialog } from './missing-signature-field-dialog';
|
||||
import { type DocumentFlowStep, FRIENDLY_FIELD_TYPE } from './types';
|
||||
|
||||
// const fontCaveat = Caveat({
|
||||
// weight: ['500'],
|
||||
// subsets: ['latin'],
|
||||
// display: 'swap',
|
||||
// variable: '--font-caveat',
|
||||
// });
|
||||
|
||||
const MIN_HEIGHT_PX = 12;
|
||||
const MIN_WIDTH_PX = 36;
|
||||
|
||||
@ -541,12 +534,6 @@ export const AddFieldsFormPartial = ({
|
||||
);
|
||||
}, [recipientsByRole]);
|
||||
|
||||
const isTypedSignatureEnabled = form.watch('typedSignatureEnabled');
|
||||
|
||||
const handleTypedSignatureChange = (value: boolean) => {
|
||||
form.setValue('typedSignatureEnabled', value, { shouldDirty: true });
|
||||
};
|
||||
|
||||
const handleAdvancedSettings = () => {
|
||||
setShowAdvancedSettings((prev) => !prev);
|
||||
};
|
||||
@ -831,8 +818,7 @@ export const AddFieldsFormPartial = ({
|
||||
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-lg font-normal',
|
||||
// fontCaveat.className,
|
||||
'text-muted-foreground group-data-[selected]:text-foreground font-signature flex items-center justify-center gap-x-1.5 text-lg font-normal',
|
||||
)}
|
||||
>
|
||||
<Trans>Signature</Trans>
|
||||
|
||||
@ -20,15 +20,13 @@ export const CheckboxField = ({ field }: CheckboxFieldProps) => {
|
||||
}
|
||||
|
||||
if (parsedFieldMeta && (!parsedFieldMeta.values || parsedFieldMeta.values.length === 0)) {
|
||||
return (
|
||||
<FieldIcon fieldMeta={field.fieldMeta} type={field.type} signerEmail={field.signerEmail} />
|
||||
);
|
||||
return <FieldIcon fieldMeta={field.fieldMeta} type={field.type} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-1">
|
||||
{!parsedFieldMeta?.values ? (
|
||||
<FieldIcon fieldMeta={field.fieldMeta} type={field.type} signerEmail={field.signerEmail} />
|
||||
<FieldIcon fieldMeta={field.fieldMeta} type={field.type} />
|
||||
) : (
|
||||
parsedFieldMeta.values.map((item: { value: string; checked: boolean }, index: number) => (
|
||||
<div key={index} className="flex items-center gap-x-1.5">
|
||||
|
||||
@ -20,15 +20,13 @@ export const RadioField = ({ field }: RadioFieldProps) => {
|
||||
}
|
||||
|
||||
if (parsedFieldMeta && (!parsedFieldMeta.values || parsedFieldMeta.values.length === 0)) {
|
||||
return (
|
||||
<FieldIcon fieldMeta={field.fieldMeta} type={field.type} signerEmail={field.signerEmail} />
|
||||
);
|
||||
return <FieldIcon fieldMeta={field.fieldMeta} type={field.type} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-2">
|
||||
{!parsedFieldMeta?.values ? (
|
||||
<FieldIcon fieldMeta={field.fieldMeta} type={field.type} signerEmail={field.signerEmail} />
|
||||
<FieldIcon fieldMeta={field.fieldMeta} type={field.type} />
|
||||
) : (
|
||||
<RadioGroup className="gap-y-1">
|
||||
{parsedFieldMeta.values?.map((item, index) => (
|
||||
|
||||
@ -4,7 +4,8 @@ import { Eye, EyeOff } from 'lucide-react';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
import { Button } from './button';
|
||||
import { Input, InputProps } from './input';
|
||||
import type { InputProps } from './input';
|
||||
import { Input } from './input';
|
||||
|
||||
const PasswordInput = React.forwardRef<HTMLInputElement, Omit<InputProps, 'type'>>(
|
||||
({ className, ...props }, ref) => {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {
|
||||
import type {
|
||||
MouseEvent as ReactMouseEvent,
|
||||
PointerEvent as ReactPointerEvent,
|
||||
TouchEvent as ReactTouchEvent,
|
||||
|
||||
@ -20,14 +20,6 @@ import { cn } from '../../lib/utils';
|
||||
import { getSvgPathFromStroke } from './helper';
|
||||
import { Point } from './point';
|
||||
|
||||
// Todo
|
||||
// const fontCaveat = Caveat({
|
||||
// weight: ['500'],
|
||||
// subsets: ['latin'],
|
||||
// display: 'swap',
|
||||
// variable: '--font-caveat',
|
||||
// });
|
||||
|
||||
const DPI = 2;
|
||||
|
||||
const isBase64Image = (value: string) => value.startsWith('data:image/png;base64,');
|
||||
@ -309,8 +301,7 @@ export const SignaturePad = ({
|
||||
if (ctx) {
|
||||
const canvasWidth = $el.current.width;
|
||||
const canvasHeight = $el.current.height;
|
||||
// const fontFamily = String(fontCaveat.style.fontFamily);
|
||||
const fontFamily = 'sans-serif';
|
||||
const fontFamily = 'Caveat';
|
||||
|
||||
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
||||
ctx.textAlign = 'center';
|
||||
|
||||
@ -61,13 +61,6 @@ import { Form, FormControl, FormField, FormItem, FormLabel } from '../form/form'
|
||||
import { useStep } from '../stepper';
|
||||
import type { TAddTemplateFieldsFormSchema } from './add-template-fields.types';
|
||||
|
||||
// const fontCaveat = Caveat({
|
||||
// weight: ['500'],
|
||||
// subsets: ['latin'],
|
||||
// display: 'swap',
|
||||
// variable: '--font-caveat',
|
||||
// });
|
||||
|
||||
const MIN_HEIGHT_PX = 12;
|
||||
const MIN_WIDTH_PX = 36;
|
||||
|
||||
@ -696,8 +689,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-lg font-normal',
|
||||
// fontCaveat.className,
|
||||
'text-muted-foreground group-data-[selected]:text-foreground font-signature flex items-center justify-center gap-x-1.5 text-lg font-normal',
|
||||
)}
|
||||
>
|
||||
<Trans>Signature</Trans>
|
||||
|
||||
Reference in New Issue
Block a user