diff --git a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx index 8aface5a6..73ec10a56 100644 --- a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx +++ b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx @@ -4,6 +4,7 @@ import { useState } from 'react'; import { useRouter } from 'next/navigation'; +import { DocumentStatus } from '@documenso/prisma/client'; import type { DocumentData, Field, Recipient, User } from '@documenso/prisma/client'; import type { DocumentWithData } from '@documenso/prisma/types/document-with-data'; import { trpc } from '@documenso/trpc/react'; @@ -35,12 +36,8 @@ export type EditDocumentFormProps = { documentData: DocumentData; }; -enum EditDocumentStepEnum { - TITLE, - SIGNERS, - FIELDS, - SUBJECT, -} +type EditDocumentStep = 'title' | 'signers' | 'fields' | 'subject'; +const EditDocumentSteps: EditDocumentStep[] = ['title', 'signers', 'fields', 'subject']; export const EditDocumentForm = ({ className, @@ -54,34 +51,37 @@ export const EditDocumentForm = ({ const router = useRouter(); // controlled stepper state - const [stepIdx, setStepIdx] = useState(0); + const [step, setStep] = useState( + document.status === DocumentStatus.DRAFT ? 'title' : 'signers', + ); const { mutateAsync: addTitle } = trpc.document.setTitleForDocument.useMutation(); const { mutateAsync: addFields } = trpc.field.addFields.useMutation(); const { mutateAsync: addSigners } = trpc.recipient.addSigners.useMutation(); const { mutateAsync: sendDocument } = trpc.document.sendDocument.useMutation(); - // controlled stepper next - const nextStep = () => setStepIdx(stepIdx + 1); - - const documentFlow: DocumentFlowStep[] = [ - { + const documentFlow: Record = { + title: { title: 'Add Title', description: 'Add the title to the document.', + stepIndex: 1, }, - { + signers: { title: 'Add Signers', description: 'Add the people who will sign the document.', + stepIndex: 2, }, - { + fields: { title: 'Add Fields', description: 'Add all relevant fields for each recipient.', + stepIndex: 3, }, - { + subject: { title: 'Add Subject', description: 'Add the subject and message you wish to send to signers.', + stepIndex: 4, }, - ]; + }; const onAddTitleFormSubmit = async (data: TAddTitleFormSchema) => { try { @@ -93,7 +93,7 @@ export const EditDocumentForm = ({ router.refresh(); - nextStep(); + setStep('signers'); } catch (err) { console.error(err); @@ -114,7 +114,7 @@ export const EditDocumentForm = ({ }); router.refresh(); - nextStep(); + setStep('fields'); } catch (err) { console.error(err); @@ -135,7 +135,7 @@ export const EditDocumentForm = ({ }); router.refresh(); - nextStep(); + setStep('subject'); } catch (err) { console.error(err); @@ -177,7 +177,7 @@ export const EditDocumentForm = ({ } }; - const currentDocumentFlow = documentFlow[stepIdx]; + const currentDocumentFlow = documentFlow[step]; return (
@@ -199,10 +199,13 @@ export const EditDocumentForm = ({ title={currentDocumentFlow.title} description={currentDocumentFlow.description} /> - setStepIdx(step - 1)}> + setStep(EditDocumentSteps[step - 1])} + > ) => { +}: AddFieldsFormProps) => { const { isWithinPageBounds, getFieldPosition, getPage } = useDocumentElement(); - const { currentStep, totalSteps, nextStep, previousStep } = useStep(); + const { currentStep, totalSteps, previousStep } = useStep(); const { control, diff --git a/packages/ui/primitives/document-flow/add-signers.tsx b/packages/ui/primitives/document-flow/add-signers.tsx index 977f95bdd..f549b7220 100644 --- a/packages/ui/primitives/document-flow/add-signers.tsx +++ b/packages/ui/primitives/document-flow/add-signers.tsx @@ -18,7 +18,7 @@ import { Input } from '@documenso/ui/primitives/input'; import { Label } from '@documenso/ui/primitives/label'; import { useToast } from '@documenso/ui/primitives/use-toast'; -import type { WithStep } from '../stepper'; +import { useStep } from '../stepper'; import type { TAddSignersFormSchema } from './add-signers.types'; import { ZAddSignersFormSchema } from './add-signers.types'; import { @@ -43,14 +43,13 @@ export const AddSignersFormPartial = ({ document, fields: _fields, onSubmit, - useStep, // Stepper -}: WithStep) => { +}: AddSignersFormProps) => { const { toast } = useToast(); const { remaining } = useLimits(); const initialId = useId(); - const { currentStep, totalSteps, nextStep, previousStep } = useStep(); + const { currentStep, totalSteps, previousStep } = useStep(); const { control, diff --git a/packages/ui/primitives/document-flow/add-subject.tsx b/packages/ui/primitives/document-flow/add-subject.tsx index e2a10afa3..e5456fb43 100644 --- a/packages/ui/primitives/document-flow/add-subject.tsx +++ b/packages/ui/primitives/document-flow/add-subject.tsx @@ -10,7 +10,7 @@ import { Input } from '@documenso/ui/primitives/input'; import { Label } from '@documenso/ui/primitives/label'; import { Textarea } from '@documenso/ui/primitives/textarea'; -import type { WithStep } from '../stepper'; +import { useStep } from '../stepper'; import type { TAddSubjectFormSchema } from './add-subject.types'; import { DocumentFlowFormContainerActions, @@ -34,8 +34,7 @@ export const AddSubjectFormPartial = ({ fields: _fields, document, onSubmit, - useStep, -}: WithStep) => { +}: AddSubjectFormProps) => { const { register, handleSubmit, @@ -50,7 +49,7 @@ export const AddSubjectFormPartial = ({ }); const onFormSubmit = handleSubmit(onSubmit); - const { currentStep, totalSteps, nextStep, previousStep } = useStep(); + const { currentStep, totalSteps, previousStep } = useStep(); return ( <> diff --git a/packages/ui/primitives/document-flow/add-title.tsx b/packages/ui/primitives/document-flow/add-title.tsx index 75cd47e93..27b6aff03 100644 --- a/packages/ui/primitives/document-flow/add-title.tsx +++ b/packages/ui/primitives/document-flow/add-title.tsx @@ -8,7 +8,7 @@ import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-messa import { Input } from '@documenso/ui/primitives/input'; import { Label } from '@documenso/ui/primitives/label'; -import type { WithStep } from '../stepper'; +import { useStep } from '../stepper'; import type { TAddTitleFormSchema } from './add-title.types'; import { DocumentFlowFormContainerActions, @@ -32,8 +32,7 @@ export const AddTitleFormPartial = ({ fields: _fields, document, onSubmit, - useStep, -}: WithStep) => { +}: AddTitleFormProps) => { const { register, handleSubmit, diff --git a/packages/ui/primitives/stepper.tsx b/packages/ui/primitives/stepper.tsx index f827139e0..795304e8c 100644 --- a/packages/ui/primitives/stepper.tsx +++ b/packages/ui/primitives/stepper.tsx @@ -1,26 +1,24 @@ -import React, { useEffect, useState } from 'react'; +import React, { createContext, useContext, useEffect, useState } from 'react'; import type { FC } from 'react'; -type StepProps = { - readonly useStep: () => { - stepIndex: number; - currentStep: number; - totalSteps: number; - isFirst: boolean; - isLast: boolean; - nextStep: () => void; - previousStep: () => void; - }; +type StepContextType = { + stepIndex: number; + currentStep: number; + totalSteps: number; + isFirst: boolean; + isLast: boolean; + nextStep: () => void; + previousStep: () => void; }; -export type WithStep = T & StepProps; +const StepContext = createContext(null); type StepperProps = { children: React.ReactNode; onComplete?: () => void; onStepChanged?: (currentStep: number) => void; - currentStep?: number; - setCurrentStep?: (step: number) => void; + currentStep?: number; // external control prop + setCurrentStep?: (step: number) => void; // external control function }; export const Stepper: FC = ({ @@ -57,7 +55,12 @@ export const Stepper: FC = ({ onStepChanged && onStepChanged(currentStep); }, [currentStep, onStepChanged]); - const useStep = () => ({ + // Empty stepper + if (totalSteps === 0) return null; + + const currentChild = React.Children.toArray(children)[currentStep - 1]; + + const stepContextValue: StepContextType = { stepIndex: currentStep - 1, currentStep, totalSteps, @@ -65,15 +68,14 @@ export const Stepper: FC = ({ isLast: currentStep === totalSteps, nextStep, previousStep, - }); + }; - // empty stepper - if (totalSteps === 0) return null; - - const currentChild = React.Children.toArray(children)[currentStep - 1]; - - // type validation - if (!React.isValidElement(currentChild)) return null; - - return <>{React.cloneElement(currentChild, { useStep })}; + return {currentChild}; +}; + +/** Hook for children to use the step context */ +export const useStep = (): StepContextType => { + const context = useContext(StepContext); + if (!context) throw new Error('useStep must be used within a Stepper'); + return context; };