import type { EnvelopeEditorStep } from '@documenso/lib/client-only/providers/envelope-editor-provider'; import { useCurrentEnvelopeEditor } from '@documenso/lib/client-only/providers/envelope-editor-provider'; import { mapSecondaryIdToTemplateId } from '@documenso/lib/utils/envelope'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; import { Separator } from '@documenso/ui/primitives/separator'; import { SpinnerBox } from '@documenso/ui/primitives/spinner'; import type { MessageDescriptor } from '@lingui/core'; import { msg } from '@lingui/core/macro'; import { Trans, useLingui } from '@lingui/react/macro'; import { EnvelopeType } from '@prisma/client'; import { motion } from 'framer-motion'; import { ArrowLeftIcon, CopyPlusIcon, DownloadCloudIcon, EyeIcon, FileOutputIcon, LinkIcon, type LucideIcon, MousePointerIcon, SendIcon, SettingsIcon, Trash2Icon, UploadIcon, } from 'lucide-react'; import { useEffect, useMemo, useRef, useState } from 'react'; import { Link, useNavigate, useSearchParams } from 'react-router'; import { match } from 'ts-pattern'; import { EnvelopeDeleteDialog } from '~/components/dialogs/envelope-delete-dialog'; import { EnvelopeDistributeDialog } from '~/components/dialogs/envelope-distribute-dialog'; import { EnvelopeDownloadDialog } from '~/components/dialogs/envelope-download-dialog'; import { EnvelopeDuplicateDialog } from '~/components/dialogs/envelope-duplicate-dialog'; import { EnvelopeRedistributeDialog } from '~/components/dialogs/envelope-redistribute-dialog'; import { EnvelopeSaveAsTemplateDialog } from '~/components/dialogs/envelope-save-as-template-dialog'; import { TemplateDirectLinkDialog } from '~/components/dialogs/template-direct-link-dialog'; import { EnvelopeEditorSettingsDialog } from '~/components/general/envelope-editor/envelope-editor-settings-dialog'; import { EnvelopeEditorFieldsPage } from './envelope-editor-fields-page'; import EnvelopeEditorHeader from './envelope-editor-header'; import { EnvelopeEditorPreviewPage } from './envelope-editor-preview-page'; import { EnvelopeEditorUploadPage } from './envelope-editor-upload-page'; type EnvelopeEditorStepData = { id: string; title: MessageDescriptor; icon: LucideIcon; description: MessageDescriptor; }; const UPLOAD_STEP = { id: 'upload', title: msg`Document & Recipients`, icon: UploadIcon, description: msg`Upload documents and add recipients`, }; const ADD_FIELDS_STEP = { id: 'addFields', title: msg`Add Fields`, icon: MousePointerIcon, description: msg`Place and configure form fields in the document`, }; const PREVIEW_STEP = { id: 'preview', title: msg`Preview`, icon: EyeIcon, description: msg`Preview the document before sending`, }; export const EnvelopeEditor = () => { const { t } = useLingui(); const navigate = useNavigate(); const { envelope, editorConfig, isDocument, isTemplate, relativePath, navigateToStep, syncEnvelope, flushAutosave, resetForms, } = useCurrentEnvelopeEditor(); const [searchParams, setSearchParams] = useSearchParams(); const { general: { minimizeLeftSidebar, allowUploadAndRecipientStep, allowAddFieldsStep, allowPreviewStep }, actions: { allowDistributing, allowDirectLink, allowDuplication, allowSaveAsTemplate, allowDownloadPDF, allowDeletion, }, } = editorConfig; const envelopeEditorSteps = useMemo(() => { const steps: EnvelopeEditorStepData[] = []; if (allowUploadAndRecipientStep) { steps.push(UPLOAD_STEP); } if (allowAddFieldsStep) { steps.push(ADD_FIELDS_STEP); } if (allowPreviewStep) { steps.push(PREVIEW_STEP); } return steps.map((step, index) => ({ ...step, order: index + 1, })); }, [editorConfig]); const searchParamsStep = useMemo(() => { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const searchParamStep = searchParams.get('step') as EnvelopeEditorStep | undefined; // Empty URL param equals upload, otherwise use the step URL param if (!searchParamStep) { return 'upload'; } const validSteps: EnvelopeEditorStep[] = ['upload', 'addFields', 'preview']; if (validSteps.includes(searchParamStep)) { return searchParamStep; } return 'upload'; }, [searchParams]); const [pageToRender, setPageToRender] = useState(searchParamsStep); const latestStepChangeTime = useRef(0); const handleStepChange = async (step: EnvelopeEditorStep) => { setPageToRender('loading'); const currentTime = Date.now(); latestStepChangeTime.current = currentTime; await flushAutosave().then(() => { if (currentTime !== latestStepChangeTime.current) { return; } resetForms(); setPageToRender(step); }); }; // Watch the URL params and setStep if the step changes. useEffect(() => { const stepParam = searchParams.get('step') || envelopeEditorSteps[0].id; const foundStep = envelopeEditorSteps.find((step) => step.id === stepParam); if (foundStep && foundStep.id !== pageToRender) { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions void handleStepChange(foundStep.id as EnvelopeEditorStep); } }, [searchParams]); const currentStepData = envelopeEditorSteps.find((step) => step.id === searchParamsStep) || envelopeEditorSteps[0]; return (
{/* Main Content Area */}
{/* Left Section - Step Navigation */}
{/* Left section step selector. */} {minimizeLeftSidebar ? (
{/* Track circle */} {/* Progress arc */} {currentStepData.order}/{envelopeEditorSteps.length}
) : (

{isDocument ? Document Editor : Template Editor} Step {currentStepData.order}/{envelopeEditorSteps.length}

)}
{envelopeEditorSteps.map((step) => { const Icon = step.icon; const isActive = searchParamsStep === step.id; return ( ); })}
{/* Quick Actions. */}
{!minimizeLeftSidebar && (

Quick Actions

)} {editorConfig.settings && ( {!minimizeLeftSidebar && ( {isDocument ? Document Settings : Template Settings} )} } /> )} {isDocument && allowDistributing && ( <> {!minimizeLeftSidebar && ( Send Document )} } /> {!minimizeLeftSidebar && ( Resend Document )} } /> )} {isTemplate && allowDirectLink && ( await syncEnvelope()} onDeleteSuccess={async () => await syncEnvelope()} trigger={ } /> )} {allowDuplication && ( {!minimizeLeftSidebar && ( {isDocument ? Duplicate Document : Duplicate Template} )} } /> )} {allowSaveAsTemplate && isDocument && ( {!minimizeLeftSidebar && ( Save as Template )} } /> )} {allowDownloadPDF && ( {!minimizeLeftSidebar && ( Download PDF )} } /> )} {/* Check envelope ID since it can be in embedded create mode. */} {allowDeletion && envelope.id && ( {!minimizeLeftSidebar && ( {isDocument ? Delete Document : Delete Template} )} } onDelete={async () => { await navigate( envelope.type === EnvelopeType.DOCUMENT ? relativePath.documentRootPath : relativePath.templateRootPath, ); }} /> )}
{/* Footer of left sidebar. */} {!editorConfig.embedded && (
)}
{/* Main Content - Changes based on current step */}
{match({ pageToRender, allowUploadAndRecipientStep, allowAddFieldsStep, allowPreviewStep, }) .with({ pageToRender: 'loading' }, () => ) .with({ pageToRender: 'upload', allowUploadAndRecipientStep: true }, () => ) .with({ pageToRender: 'addFields', allowAddFieldsStep: true }, () => ) .with({ pageToRender: 'preview', allowPreviewStep: true }, () => ) .otherwise(() => null)}
); };