From 044b267016e8e9d79d23aa8ea1a0009a65793ad8 Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Wed, 19 Nov 2025 23:22:33 +1100 Subject: [PATCH] fix: handle loading files in embedded authoring update flows --- .../embed/authoring/configure-fields-view.tsx | 109 +++++++++--------- .../v1+/authoring+/document.edit.$id.tsx | 6 +- .../v1+/authoring+/template.edit.$id.tsx | 6 +- apps/remix/server/api/files/files.ts | 18 ++- apps/remix/server/api/files/files.types.ts | 8 ++ packages/lib/utils/envelope-download.ts | 8 +- packages/ui/primitives/pdf-viewer.tsx | 3 + 7 files changed, 95 insertions(+), 63 deletions(-) diff --git a/apps/remix/app/components/embed/authoring/configure-fields-view.tsx b/apps/remix/app/components/embed/authoring/configure-fields-view.tsx index ed7057ee0..551b50ced 100644 --- a/apps/remix/app/components/embed/authoring/configure-fields-view.tsx +++ b/apps/remix/app/components/embed/authoring/configure-fields-view.tsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; -import type { DocumentData, FieldType } from '@prisma/client'; +import type { EnvelopeItem, FieldType } from '@prisma/client'; import { ReadStatus, type Recipient, SendStatus, SigningStatus } from '@prisma/client'; import { base64 } from '@scure/base'; import { ChevronsUpDown } from 'lucide-react'; @@ -40,7 +40,8 @@ const DEFAULT_WIDTH_PX = MIN_WIDTH_PX * 2.5; export type ConfigureFieldsViewProps = { configData: TConfigureEmbedFormSchema; - documentData?: DocumentData; + presignToken?: string | undefined; + envelopeItem?: Pick; defaultValues?: Partial; onBack?: (data: TConfigureFieldsFormSchema) => void; onSubmit: (data: TConfigureFieldsFormSchema) => void; @@ -48,7 +49,8 @@ export type ConfigureFieldsViewProps = { export const ConfigureFieldsView = ({ configData, - documentData, + presignToken, + envelopeItem, defaultValues, onBack, onSubmit, @@ -82,12 +84,12 @@ export const ConfigureFieldsView = ({ }, []); const normalizedDocumentData = useMemo(() => { - if (documentData) { - return documentData.data; + if (envelopeItem) { + return undefined; } if (!configData.documentData) { - return null; + return undefined; } return base64.encode(configData.documentData.data); @@ -534,56 +536,55 @@ export const ConfigureFieldsView = ({ )}
- {normalizedDocumentData && ( -
- + {normalizedDocumentData || + (envelopeItem && ( +
+ - - {localFields.map((field, index) => { - const recipientIndex = recipients.findIndex( - (r) => r.id === field.recipientId, - ); + + {localFields.map((field, index) => { + const recipientIndex = recipients.findIndex( + (r) => r.id === field.recipientId, + ); - return ( - onFieldResize(node, index)} - onMove={(node) => onFieldMove(node, index)} - onRemove={() => remove(index)} - onDuplicate={() => onFieldCopy(null, { duplicate: true })} - onDuplicateAllPages={() => onFieldCopy(null, { duplicateAll: true })} - onFocus={() => setLastActiveField(field)} - onBlur={() => setLastActiveField(null)} - onAdvancedSettings={() => { - setCurrentField(field); - setShowAdvancedSettings(true); - }} - recipientIndex={recipientIndex} - active={activeFieldId === field.formId} - onFieldActivate={() => setActiveFieldId(field.formId)} - onFieldDeactivate={() => setActiveFieldId(null)} - disabled={selectedRecipient?.id !== field.recipientId} - /> - ); - })} - -
- )} + return ( + onFieldResize(node, index)} + onMove={(node) => onFieldMove(node, index)} + onRemove={() => remove(index)} + onDuplicate={() => onFieldCopy(null, { duplicate: true })} + onDuplicateAllPages={() => onFieldCopy(null, { duplicateAll: true })} + onFocus={() => setLastActiveField(field)} + onBlur={() => setLastActiveField(null)} + onAdvancedSettings={() => { + setCurrentField(field); + setShowAdvancedSettings(true); + }} + recipientIndex={recipientIndex} + active={activeFieldId === field.formId} + onFieldActivate={() => setActiveFieldId(field.formId)} + onFieldDeactivate={() => setActiveFieldId(null)} + disabled={selectedRecipient?.id !== field.recipientId} + /> + ); + })} + +
+ ))}
diff --git a/apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx b/apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx index 9e698663b..818a7eb54 100644 --- a/apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx +++ b/apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx @@ -75,6 +75,7 @@ export const loader = async ({ request, params }: Route.LoaderArgs) => { })); return { + token, document: { ...document, fields, @@ -86,7 +87,7 @@ export default function EmbeddingAuthoringDocumentEditPage() { const { _ } = useLingui(); const { toast } = useToast(); - const { document } = useLoaderData(); + const { document, token } = useLoaderData(); const [hasFinishedInit, setHasFinishedInit] = useState(false); @@ -321,7 +322,8 @@ export default function EmbeddingAuthoringDocumentEditPage() { { })); return { + token, template: { ...template, fields, @@ -86,7 +87,7 @@ export default function EmbeddingAuthoringTemplateEditPage() { const { _ } = useLingui(); const { toast } = useToast(); - const { template } = useLoaderData(); + const { template, token } = useLoaderData(); const [hasFinishedInit, setHasFinishedInit] = useState(false); @@ -321,7 +322,8 @@ export default function EmbeddingAuthoringTemplateEditPage() { () .get( '/envelope/:envelopeId/envelopeItem/:envelopeItemId', sValidator('param', ZGetEnvelopeItemFileRequestParamsSchema), + sValidator('query', ZGetEnvelopeItemFileRequestQuerySchema), async (c) => { const { envelopeId, envelopeItemId } = c.req.valid('param'); + const { token } = c.req.query(); const session = await getOptionalSession(c); - if (!session.user) { + let userId = session.user?.id; + + if (token) { + const presignToken = await verifyEmbeddingPresignToken({ + token, + }).catch(() => undefined); + + userId = presignToken?.userId; + } + + if (!userId) { return c.json({ error: 'Unauthorized' }, 401); } @@ -104,7 +118,7 @@ export const filesRoute = new Hono() } const team = await getTeamById({ - userId: session.user.id, + userId: userId, teamId: envelope.teamId, }).catch((error) => { console.error(error); diff --git a/apps/remix/server/api/files/files.types.ts b/apps/remix/server/api/files/files.types.ts index 444dac8ae..0e7c2f8cf 100644 --- a/apps/remix/server/api/files/files.types.ts +++ b/apps/remix/server/api/files/files.types.ts @@ -36,6 +36,14 @@ export type TGetEnvelopeItemFileRequestParams = z.infer< typeof ZGetEnvelopeItemFileRequestParamsSchema >; +export const ZGetEnvelopeItemFileRequestQuerySchema = z.object({ + token: z.string().optional(), +}); + +export type TGetEnvelopeItemFileRequestQuery = z.infer< + typeof ZGetEnvelopeItemFileRequestQuerySchema +>; + export const ZGetEnvelopeItemFileTokenRequestParamsSchema = z.object({ token: z.string().min(1), envelopeItemId: z.string().min(1), diff --git a/packages/lib/utils/envelope-download.ts b/packages/lib/utils/envelope-download.ts index 1961c5126..a18857488 100644 --- a/packages/lib/utils/envelope-download.ts +++ b/packages/lib/utils/envelope-download.ts @@ -8,15 +8,17 @@ export type EnvelopeItemPdfUrlOptions = envelopeItem: Pick; token: string | undefined; version: 'original' | 'signed'; + presignToken?: undefined; } | { type: 'view'; envelopeItem: Pick; token: string | undefined; + presignToken?: string | undefined; }; export const getEnvelopeItemPdfUrl = (options: EnvelopeItemPdfUrlOptions) => { - const { envelopeItem, token, type } = options; + const { envelopeItem, token, type, presignToken } = options; const { id, envelopeId } = envelopeItem; @@ -24,11 +26,11 @@ export const getEnvelopeItemPdfUrl = (options: EnvelopeItemPdfUrlOptions) => { const version = options.version; return token - ? `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/token/${token}/envelopeItem/${id}/download/${version}` + ? `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/token/${token}/envelopeItem/${id}/download/${version}${presignToken ? `?presignToken=${presignToken}` : ''}` : `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/envelope/${envelopeId}/envelopeItem/${id}/download/${version}`; } return token - ? `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/token/${token}/envelopeItem/${id}` + ? `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/token/${token}/envelopeItem/${id}${presignToken ? `?presignToken=${presignToken}` : ''}` : `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/envelope/${envelopeId}/envelopeItem/${id}`; }; diff --git a/packages/ui/primitives/pdf-viewer.tsx b/packages/ui/primitives/pdf-viewer.tsx index 8cf2f9d61..e0bb23498 100644 --- a/packages/ui/primitives/pdf-viewer.tsx +++ b/packages/ui/primitives/pdf-viewer.tsx @@ -56,6 +56,7 @@ export type PDFViewerProps = { className?: string; envelopeItem: Pick; token: string | undefined; + presignToken?: string | undefined; version: 'original' | 'signed'; onDocumentLoad?: (_doc: LoadedPDFDocument) => void; onPageClick?: OnPDFViewerPageClick; @@ -67,6 +68,7 @@ export const PDFViewer = ({ className, envelopeItem, token, + presignToken, version, onDocumentLoad, onPageClick, @@ -166,6 +168,7 @@ export const PDFViewer = ({ type: 'view', envelopeItem: envelopeItem, token, + presignToken, }); const bytes = await fetch(documentUrl).then(async (res) => await res.arrayBuffer());