mirror of
https://github.com/documenso/documenso.git
synced 2025-11-21 04:01:45 +10:00
fix: handle loading files in embedded authoring update flows
This commit is contained in:
@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|||||||
|
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
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 { ReadStatus, type Recipient, SendStatus, SigningStatus } from '@prisma/client';
|
||||||
import { base64 } from '@scure/base';
|
import { base64 } from '@scure/base';
|
||||||
import { ChevronsUpDown } from 'lucide-react';
|
import { ChevronsUpDown } from 'lucide-react';
|
||||||
@ -40,7 +40,8 @@ const DEFAULT_WIDTH_PX = MIN_WIDTH_PX * 2.5;
|
|||||||
|
|
||||||
export type ConfigureFieldsViewProps = {
|
export type ConfigureFieldsViewProps = {
|
||||||
configData: TConfigureEmbedFormSchema;
|
configData: TConfigureEmbedFormSchema;
|
||||||
documentData?: DocumentData;
|
presignToken?: string | undefined;
|
||||||
|
envelopeItem?: Pick<EnvelopeItem, 'id' | 'envelopeId'>;
|
||||||
defaultValues?: Partial<TConfigureFieldsFormSchema>;
|
defaultValues?: Partial<TConfigureFieldsFormSchema>;
|
||||||
onBack?: (data: TConfigureFieldsFormSchema) => void;
|
onBack?: (data: TConfigureFieldsFormSchema) => void;
|
||||||
onSubmit: (data: TConfigureFieldsFormSchema) => void;
|
onSubmit: (data: TConfigureFieldsFormSchema) => void;
|
||||||
@ -48,7 +49,8 @@ export type ConfigureFieldsViewProps = {
|
|||||||
|
|
||||||
export const ConfigureFieldsView = ({
|
export const ConfigureFieldsView = ({
|
||||||
configData,
|
configData,
|
||||||
documentData,
|
presignToken,
|
||||||
|
envelopeItem,
|
||||||
defaultValues,
|
defaultValues,
|
||||||
onBack,
|
onBack,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
@ -82,12 +84,12 @@ export const ConfigureFieldsView = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const normalizedDocumentData = useMemo(() => {
|
const normalizedDocumentData = useMemo(() => {
|
||||||
if (documentData) {
|
if (envelopeItem) {
|
||||||
return documentData.data;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!configData.documentData) {
|
if (!configData.documentData) {
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return base64.encode(configData.documentData.data);
|
return base64.encode(configData.documentData.data);
|
||||||
@ -534,14 +536,13 @@ export const ConfigureFieldsView = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
{normalizedDocumentData && (
|
{normalizedDocumentData ||
|
||||||
|
(envelopeItem && (
|
||||||
<div>
|
<div>
|
||||||
<PDFViewer
|
<PDFViewer
|
||||||
|
presignToken={presignToken}
|
||||||
overrideData={normalizedDocumentData}
|
overrideData={normalizedDocumentData}
|
||||||
envelopeItem={{
|
envelopeItem={envelopeItem}
|
||||||
id: '',
|
|
||||||
envelopeId: '',
|
|
||||||
}}
|
|
||||||
token={undefined}
|
token={undefined}
|
||||||
version="signed"
|
version="signed"
|
||||||
/>
|
/>
|
||||||
@ -583,7 +584,7 @@ export const ConfigureFieldsView = ({
|
|||||||
})}
|
})}
|
||||||
</ElementVisible>
|
</ElementVisible>
|
||||||
</div>
|
</div>
|
||||||
)}
|
))}
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -75,6 +75,7 @@ export const loader = async ({ request, params }: Route.LoaderArgs) => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
token,
|
||||||
document: {
|
document: {
|
||||||
...document,
|
...document,
|
||||||
fields,
|
fields,
|
||||||
@ -86,7 +87,7 @@ export default function EmbeddingAuthoringDocumentEditPage() {
|
|||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const { document } = useLoaderData<typeof loader>();
|
const { document, token } = useLoaderData<typeof loader>();
|
||||||
|
|
||||||
const [hasFinishedInit, setHasFinishedInit] = useState(false);
|
const [hasFinishedInit, setHasFinishedInit] = useState(false);
|
||||||
|
|
||||||
@ -321,7 +322,8 @@ export default function EmbeddingAuthoringDocumentEditPage() {
|
|||||||
|
|
||||||
<ConfigureFieldsView
|
<ConfigureFieldsView
|
||||||
configData={configuration!}
|
configData={configuration!}
|
||||||
documentData={document.documentData}
|
presignToken={token}
|
||||||
|
envelopeItem={document.envelopeItems[0]}
|
||||||
defaultValues={fields ?? undefined}
|
defaultValues={fields ?? undefined}
|
||||||
onBack={canGoBack ? handleBackToConfig : undefined}
|
onBack={canGoBack ? handleBackToConfig : undefined}
|
||||||
onSubmit={handleConfigureFieldsSubmit}
|
onSubmit={handleConfigureFieldsSubmit}
|
||||||
|
|||||||
@ -75,6 +75,7 @@ export const loader = async ({ request, params }: Route.LoaderArgs) => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
token,
|
||||||
template: {
|
template: {
|
||||||
...template,
|
...template,
|
||||||
fields,
|
fields,
|
||||||
@ -86,7 +87,7 @@ export default function EmbeddingAuthoringTemplateEditPage() {
|
|||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const { template } = useLoaderData<typeof loader>();
|
const { template, token } = useLoaderData<typeof loader>();
|
||||||
|
|
||||||
const [hasFinishedInit, setHasFinishedInit] = useState(false);
|
const [hasFinishedInit, setHasFinishedInit] = useState(false);
|
||||||
|
|
||||||
@ -321,7 +322,8 @@ export default function EmbeddingAuthoringTemplateEditPage() {
|
|||||||
|
|
||||||
<ConfigureFieldsView
|
<ConfigureFieldsView
|
||||||
configData={configuration!}
|
configData={configuration!}
|
||||||
documentData={template.templateDocumentData}
|
presignToken={token}
|
||||||
|
envelopeItem={template.envelopeItems[0]}
|
||||||
defaultValues={fields ?? undefined}
|
defaultValues={fields ?? undefined}
|
||||||
onBack={canGoBack ? handleBackToConfig : undefined}
|
onBack={canGoBack ? handleBackToConfig : undefined}
|
||||||
onSubmit={handleConfigureFieldsSubmit}
|
onSubmit={handleConfigureFieldsSubmit}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { Hono } from 'hono';
|
|||||||
import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session';
|
import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session';
|
||||||
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
|
import { verifyEmbeddingPresignToken } from '@documenso/lib/server-only/embedding-presign/verify-embedding-presign-token';
|
||||||
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
|
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
|
||||||
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
||||||
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
|
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
|
||||||
@ -16,6 +17,7 @@ import {
|
|||||||
type TGetPresignedPostUrlResponse,
|
type TGetPresignedPostUrlResponse,
|
||||||
ZGetEnvelopeItemFileDownloadRequestParamsSchema,
|
ZGetEnvelopeItemFileDownloadRequestParamsSchema,
|
||||||
ZGetEnvelopeItemFileRequestParamsSchema,
|
ZGetEnvelopeItemFileRequestParamsSchema,
|
||||||
|
ZGetEnvelopeItemFileRequestQuerySchema,
|
||||||
ZGetEnvelopeItemFileTokenDownloadRequestParamsSchema,
|
ZGetEnvelopeItemFileTokenDownloadRequestParamsSchema,
|
||||||
ZGetEnvelopeItemFileTokenRequestParamsSchema,
|
ZGetEnvelopeItemFileTokenRequestParamsSchema,
|
||||||
ZGetPresignedPostUrlRequestSchema,
|
ZGetPresignedPostUrlRequestSchema,
|
||||||
@ -68,12 +70,24 @@ export const filesRoute = new Hono<HonoEnv>()
|
|||||||
.get(
|
.get(
|
||||||
'/envelope/:envelopeId/envelopeItem/:envelopeItemId',
|
'/envelope/:envelopeId/envelopeItem/:envelopeItemId',
|
||||||
sValidator('param', ZGetEnvelopeItemFileRequestParamsSchema),
|
sValidator('param', ZGetEnvelopeItemFileRequestParamsSchema),
|
||||||
|
sValidator('query', ZGetEnvelopeItemFileRequestQuerySchema),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const { envelopeId, envelopeItemId } = c.req.valid('param');
|
const { envelopeId, envelopeItemId } = c.req.valid('param');
|
||||||
|
const { token } = c.req.query();
|
||||||
|
|
||||||
const session = await getOptionalSession(c);
|
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);
|
return c.json({ error: 'Unauthorized' }, 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +118,7 @@ export const filesRoute = new Hono<HonoEnv>()
|
|||||||
}
|
}
|
||||||
|
|
||||||
const team = await getTeamById({
|
const team = await getTeamById({
|
||||||
userId: session.user.id,
|
userId: userId,
|
||||||
teamId: envelope.teamId,
|
teamId: envelope.teamId,
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
@ -36,6 +36,14 @@ export type TGetEnvelopeItemFileRequestParams = z.infer<
|
|||||||
typeof ZGetEnvelopeItemFileRequestParamsSchema
|
typeof ZGetEnvelopeItemFileRequestParamsSchema
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export const ZGetEnvelopeItemFileRequestQuerySchema = z.object({
|
||||||
|
token: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TGetEnvelopeItemFileRequestQuery = z.infer<
|
||||||
|
typeof ZGetEnvelopeItemFileRequestQuerySchema
|
||||||
|
>;
|
||||||
|
|
||||||
export const ZGetEnvelopeItemFileTokenRequestParamsSchema = z.object({
|
export const ZGetEnvelopeItemFileTokenRequestParamsSchema = z.object({
|
||||||
token: z.string().min(1),
|
token: z.string().min(1),
|
||||||
envelopeItemId: z.string().min(1),
|
envelopeItemId: z.string().min(1),
|
||||||
|
|||||||
@ -8,15 +8,17 @@ export type EnvelopeItemPdfUrlOptions =
|
|||||||
envelopeItem: Pick<EnvelopeItem, 'id' | 'envelopeId'>;
|
envelopeItem: Pick<EnvelopeItem, 'id' | 'envelopeId'>;
|
||||||
token: string | undefined;
|
token: string | undefined;
|
||||||
version: 'original' | 'signed';
|
version: 'original' | 'signed';
|
||||||
|
presignToken?: undefined;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'view';
|
type: 'view';
|
||||||
envelopeItem: Pick<EnvelopeItem, 'id' | 'envelopeId'>;
|
envelopeItem: Pick<EnvelopeItem, 'id' | 'envelopeId'>;
|
||||||
token: string | undefined;
|
token: string | undefined;
|
||||||
|
presignToken?: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getEnvelopeItemPdfUrl = (options: EnvelopeItemPdfUrlOptions) => {
|
export const getEnvelopeItemPdfUrl = (options: EnvelopeItemPdfUrlOptions) => {
|
||||||
const { envelopeItem, token, type } = options;
|
const { envelopeItem, token, type, presignToken } = options;
|
||||||
|
|
||||||
const { id, envelopeId } = envelopeItem;
|
const { id, envelopeId } = envelopeItem;
|
||||||
|
|
||||||
@ -24,11 +26,11 @@ export const getEnvelopeItemPdfUrl = (options: EnvelopeItemPdfUrlOptions) => {
|
|||||||
const version = options.version;
|
const version = options.version;
|
||||||
|
|
||||||
return token
|
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}`;
|
: `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/envelope/${envelopeId}/envelopeItem/${id}/download/${version}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return token
|
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}`;
|
: `${NEXT_PUBLIC_WEBAPP_URL()}/api/files/envelope/${envelopeId}/envelopeItem/${id}`;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -56,6 +56,7 @@ export type PDFViewerProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
envelopeItem: Pick<EnvelopeItem, 'id' | 'envelopeId'>;
|
envelopeItem: Pick<EnvelopeItem, 'id' | 'envelopeId'>;
|
||||||
token: string | undefined;
|
token: string | undefined;
|
||||||
|
presignToken?: string | undefined;
|
||||||
version: 'original' | 'signed';
|
version: 'original' | 'signed';
|
||||||
onDocumentLoad?: (_doc: LoadedPDFDocument) => void;
|
onDocumentLoad?: (_doc: LoadedPDFDocument) => void;
|
||||||
onPageClick?: OnPDFViewerPageClick;
|
onPageClick?: OnPDFViewerPageClick;
|
||||||
@ -67,6 +68,7 @@ export const PDFViewer = ({
|
|||||||
className,
|
className,
|
||||||
envelopeItem,
|
envelopeItem,
|
||||||
token,
|
token,
|
||||||
|
presignToken,
|
||||||
version,
|
version,
|
||||||
onDocumentLoad,
|
onDocumentLoad,
|
||||||
onPageClick,
|
onPageClick,
|
||||||
@ -166,6 +168,7 @@ export const PDFViewer = ({
|
|||||||
type: 'view',
|
type: 'view',
|
||||||
envelopeItem: envelopeItem,
|
envelopeItem: envelopeItem,
|
||||||
token,
|
token,
|
||||||
|
presignToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
const bytes = await fetch(documentUrl).then(async (res) => await res.arrayBuffer());
|
const bytes = await fetch(documentUrl).then(async (res) => await res.arrayBuffer());
|
||||||
|
|||||||
Reference in New Issue
Block a user