diff --git a/apps/web/src/app/(signing)/sign/[token]/email-field.tsx b/apps/web/src/app/(signing)/sign/[token]/email-field.tsx index f6f790799..c8e283ded 100644 --- a/apps/web/src/app/(signing)/sign/[token]/email-field.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/email-field.tsx @@ -6,8 +6,8 @@ import { useRouter } from 'next/navigation'; import { Loader } from 'lucide-react'; -import { Recipient } from '@documenso/prisma/client'; -import { FieldWithSignature } from '@documenso/prisma/types/field-with-signature'; +import type { Recipient } from '@documenso/prisma/client'; +import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature'; import { trpc } from '@documenso/trpc/react'; import { useToast } from '@documenso/ui/primitives/use-toast'; diff --git a/packages/lib/server-only/pdf/insert-field-in-pdf.ts b/packages/lib/server-only/pdf/insert-field-in-pdf.ts index 53ebbaf6c..f07c8f6d2 100644 --- a/packages/lib/server-only/pdf/insert-field-in-pdf.ts +++ b/packages/lib/server-only/pdf/insert-field-in-pdf.ts @@ -40,10 +40,19 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu const fieldX = pageWidth * (Number(field.positionX) / 100); const fieldY = pageHeight * (Number(field.positionY) / 100); - const font = await pdf.embedFont(isSignatureField ? fontCaveat : StandardFonts.Helvetica); + // const url = + // 'https://fonts.gstatic.com/s/dancingscript/v25/If2cXTr6YS-zF4S-kcSWSVi_sxjsohD9F50Ruu7BMSoHTQ.ttf'; + + const url = 'https://fonts.gstatic.com/s/caveat/v18/WnznHAc5bAfYB2QRah7pcpNvOx-pjfJ9SII.ttf'; + const googleFont = await fetch(url).then(async (res) => res.arrayBuffer()); + + const font = await pdf.embedFont(isSignatureField ? googleFont : StandardFonts.Helvetica, { + subset: true, + features: { liga: false }, + }); if (field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE) { - await pdf.embedFont(fontCaveat); + await pdf.embedFont(googleFont, { subset: true, features: { liga: false } }); } const CUSTOM_TEXT = field.customText || field.Signature?.typedSignature || ''; diff --git a/packages/ui/primitives/signature-pad/signature-pad.tsx b/packages/ui/primitives/signature-pad/signature-pad.tsx index d11a6d81c..39e70c8a2 100644 --- a/packages/ui/primitives/signature-pad/signature-pad.tsx +++ b/packages/ui/primitives/signature-pad/signature-pad.tsx @@ -3,10 +3,15 @@ import type { HTMLAttributes, MouseEvent, PointerEvent, TouchEvent } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react'; +import { zodResolver } from '@hookform/resolvers/zod'; import type { StrokeOptions } from 'perfect-freehand'; import { getStroke } from 'perfect-freehand'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { useRequiredSigningContext } from '../../../../apps/web/src/app/(signing)/sign/[token]/provider'; import { cn } from '../../lib/utils'; +import { Input } from '../input'; import { getSvgPathFromStroke } from './helper'; import { Point } from './point'; @@ -16,13 +21,28 @@ export type SignaturePadProps = Omit, 'onChang onChange?: (_signatureDataUrl: string | null) => void; containerClassName?: string; clearSignatureClassName?: string; + onFormSubmit?: (_data: TSigningpadSchema) => void; }; +const ZSigningpadSchema = z.union([ + z.object({ + signatureDataUrl: z.string().min(1), + signatureText: z.null().or(z.string().max(0)), + }), + z.object({ + signatureDataUrl: z.null().or(z.string().max(0)), + signatureText: z.string().trim().min(1), + }), +]); + +export type TSigningpadSchema = z.infer; + export const SignaturePad = ({ className, containerClassName, defaultValue, clearSignatureClassName, + onFormSubmit, onChange, ...props }: SignaturePadProps) => { @@ -31,6 +51,26 @@ export const SignaturePad = ({ const [isPressed, setIsPressed] = useState(false); const [points, setPoints] = useState([]); + const { signature, setSignature } = useRequiredSigningContext(); + + const { + register, + handleSubmit, + setValue, + watch, + formState: { isSubmitting }, + } = useForm({ + mode: 'onChange', + defaultValues: { + signatureDataUrl: signature || null, + signatureText: '', + }, + resolver: zodResolver(ZSigningpadSchema), + }); + + const signatureDataUrl = watch('signatureDataUrl'); + const signatureText = watch('signatureText'); + const perfectFreehandOptions = useMemo(() => { const size = $el.current ? Math.min($el.current.height, $el.current.width) * 0.03 : 10; @@ -206,28 +246,92 @@ export const SignaturePad = ({ }, [defaultValue]); return ( -
- onMouseMove(event)} - onPointerDown={(event) => onMouseDown(event)} - onPointerUp={(event) => onMouseUp(event)} - onPointerLeave={(event) => onMouseLeave(event)} - onPointerEnter={(event) => onMouseEnter(event)} - {...props} - /> +
undefined))}> +
+ onMouseMove(event)} + onPointerDown={(event) => onMouseDown(event)} + onPointerUp={(event) => onMouseUp(event)} + onPointerLeave={(event) => onMouseLeave(event)} + onPointerEnter={(event) => onMouseEnter(event)} + {...props} + /> -
- + { + if (e.target.value !== '') { + setValue('signatureDataUrl', null); + } + + setValue('signatureText', e.target.value); + }, + + onBlur: (e) => { + if (e.target.value === '') { + return setValue('signatureText', ''); + } + + setSignature(e.target.value.trimStart()); + }, + })} + /> + + {/*
+ +
*/} +
+ +
+ +
-
+ ); };