feat: stepper refactor example

This commit is contained in:
mikezzb
2023-12-02 22:42:59 -05:00
parent c46a69f865
commit a98b429052
7 changed files with 80 additions and 82 deletions

View File

@@ -5,7 +5,6 @@ import { useState } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import type { DocumentData, Field, Recipient, User } from '@documenso/prisma/client'; import type { DocumentData, Field, Recipient, User } from '@documenso/prisma/client';
import { DocumentStatus } from '@documenso/prisma/client';
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data'; import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
import { trpc } from '@documenso/trpc/react'; import { trpc } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils'; import { cn } from '@documenso/ui/lib/utils';
@@ -24,6 +23,7 @@ import {
} from '@documenso/ui/primitives/document-flow/document-flow-root'; } from '@documenso/ui/primitives/document-flow/document-flow-root';
import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types'; import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types';
import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer'; import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
import { Stepper } from '@documenso/ui/primitives/stepper';
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
export type EditDocumentFormProps = { export type EditDocumentFormProps = {
@@ -35,7 +35,12 @@ export type EditDocumentFormProps = {
documentData: DocumentData; documentData: DocumentData;
}; };
type EditDocumentStep = 'title' | 'signers' | 'fields' | 'subject'; enum EditDocumentStepEnum {
TITLE,
SIGNERS,
FIELDS,
SUBJECT,
}
export const EditDocumentForm = ({ export const EditDocumentForm = ({
className, className,
@@ -48,42 +53,35 @@ export const EditDocumentForm = ({
const { toast } = useToast(); const { toast } = useToast();
const router = useRouter(); const router = useRouter();
const [step, setStep] = useState<EditDocumentStep>( // controlled stepper state
document.status === DocumentStatus.DRAFT ? 'title' : 'signers', const [stepIdx, setStepIdx] = useState<EditDocumentStepEnum>(0);
);
const { mutateAsync: addTitle } = trpc.document.setTitleForDocument.useMutation(); const { mutateAsync: addTitle } = trpc.document.setTitleForDocument.useMutation();
const { mutateAsync: addFields } = trpc.field.addFields.useMutation(); const { mutateAsync: addFields } = trpc.field.addFields.useMutation();
const { mutateAsync: addSigners } = trpc.recipient.addSigners.useMutation(); const { mutateAsync: addSigners } = trpc.recipient.addSigners.useMutation();
const { mutateAsync: sendDocument } = trpc.document.sendDocument.useMutation(); const { mutateAsync: sendDocument } = trpc.document.sendDocument.useMutation();
const documentFlow: Record<EditDocumentStep, DocumentFlowStep> = { // controlled stepper next
title: { const nextStep = () => setStepIdx(stepIdx + 1);
const documentFlow: DocumentFlowStep[] = [
{
title: 'Add Title', title: 'Add Title',
description: 'Add the title to the document.', description: 'Add the title to the document.',
stepIndex: 1,
}, },
signers: { {
title: 'Add Signers', title: 'Add Signers',
description: 'Add the people who will sign the document.', description: 'Add the people who will sign the document.',
stepIndex: 2,
onBackStep: () => document.status === DocumentStatus.DRAFT && setStep('title'),
}, },
fields: { {
title: 'Add Fields', title: 'Add Fields',
description: 'Add all relevant fields for each recipient.', description: 'Add all relevant fields for each recipient.',
stepIndex: 3,
onBackStep: () => setStep('signers'),
}, },
subject: { {
title: 'Add Subject', title: 'Add Subject',
description: 'Add the subject and message you wish to send to signers.', description: 'Add the subject and message you wish to send to signers.',
stepIndex: 4,
onBackStep: () => setStep('fields'),
}, },
}; ];
const currentDocumentFlow = documentFlow[step];
const onAddTitleFormSubmit = async (data: TAddTitleFormSchema) => { const onAddTitleFormSubmit = async (data: TAddTitleFormSchema) => {
try { try {
@@ -95,7 +93,7 @@ export const EditDocumentForm = ({
router.refresh(); router.refresh();
setStep('signers'); nextStep();
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@@ -116,8 +114,7 @@ export const EditDocumentForm = ({
}); });
router.refresh(); router.refresh();
nextStep();
setStep('fields');
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@@ -138,8 +135,7 @@ export const EditDocumentForm = ({
}); });
router.refresh(); router.refresh();
nextStep();
setStep('subject');
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@@ -181,6 +177,8 @@ export const EditDocumentForm = ({
} }
}; };
const currentDocumentFlow = documentFlow[stepIdx];
return ( return (
<div className={cn('grid w-full grid-cols-12 gap-8', className)}> <div className={cn('grid w-full grid-cols-12 gap-8', className)}>
<Card <Card
@@ -201,52 +199,39 @@ export const EditDocumentForm = ({
title={currentDocumentFlow.title} title={currentDocumentFlow.title}
description={currentDocumentFlow.description} description={currentDocumentFlow.description}
/> />
<Stepper currentStep={stepIdx + 1} setCurrentStep={(step) => setStepIdx(step - 1)}>
{step === 'title' && (
<AddTitleFormPartial <AddTitleFormPartial
key={recipients.length} key={recipients.length}
documentFlow={documentFlow.title} documentFlow={documentFlow[EditDocumentStepEnum.TITLE]}
recipients={recipients} recipients={recipients}
fields={fields} fields={fields}
document={document} document={document}
numberOfSteps={Object.keys(documentFlow).length}
onSubmit={onAddTitleFormSubmit} onSubmit={onAddTitleFormSubmit}
/> />
)}
{step === 'signers' && (
<AddSignersFormPartial <AddSignersFormPartial
key={recipients.length} key={recipients.length}
documentFlow={documentFlow.signers} documentFlow={documentFlow[EditDocumentStepEnum.SIGNERS]}
document={document} document={document}
recipients={recipients} recipients={recipients}
fields={fields} fields={fields}
numberOfSteps={Object.keys(documentFlow).length}
onSubmit={onAddSignersFormSubmit} onSubmit={onAddSignersFormSubmit}
/> />
)}
{step === 'fields' && (
<AddFieldsFormPartial <AddFieldsFormPartial
key={fields.length} key={fields.length}
documentFlow={documentFlow.fields} documentFlow={documentFlow[EditDocumentStepEnum.FIELDS]}
recipients={recipients} recipients={recipients}
fields={fields} fields={fields}
numberOfSteps={Object.keys(documentFlow).length}
onSubmit={onAddFieldsFormSubmit} onSubmit={onAddFieldsFormSubmit}
/> />
)}
{step === 'subject' && (
<AddSubjectFormPartial <AddSubjectFormPartial
documentFlow={documentFlow.subject} key={recipients.length}
documentFlow={documentFlow[EditDocumentStepEnum.SUBJECT]}
document={document} document={document}
recipients={recipients} recipients={recipients}
fields={fields} fields={fields}
numberOfSteps={Object.keys(documentFlow).length}
onSubmit={onAddSubjectFormSubmit} onSubmit={onAddSubjectFormSubmit}
/> />
)} </Stepper>
</DocumentFlowFormContainer> </DocumentFlowFormContainer>
</div> </div>
</div> </div>

View File

@@ -11,7 +11,8 @@ import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-c
import { useDocumentElement } from '@documenso/lib/client-only/hooks/use-document-element'; import { useDocumentElement } from '@documenso/lib/client-only/hooks/use-document-element';
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer'; import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import { nanoid } from '@documenso/lib/universal/id'; import { nanoid } from '@documenso/lib/universal/id';
import { Field, FieldType, Recipient, SendStatus } from '@documenso/prisma/client'; import type { Field, Recipient } from '@documenso/prisma/client';
import { FieldType, SendStatus } from '@documenso/prisma/client';
import { cn } from '@documenso/ui/lib/utils'; import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { Card, CardContent } from '@documenso/ui/primitives/card'; import { Card, CardContent } from '@documenso/ui/primitives/card';
@@ -25,7 +26,8 @@ import {
import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover'; import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
import { TAddFieldsFormSchema } from './add-fields.types'; import type { WithStep } from '../stepper';
import type { TAddFieldsFormSchema } from './add-fields.types';
import { import {
DocumentFlowFormContainerActions, DocumentFlowFormContainerActions,
DocumentFlowFormContainerContent, DocumentFlowFormContainerContent,
@@ -33,7 +35,8 @@ import {
DocumentFlowFormContainerStep, DocumentFlowFormContainerStep,
} from './document-flow-root'; } from './document-flow-root';
import { FieldItem } from './field-item'; import { FieldItem } from './field-item';
import { DocumentFlowStep, FRIENDLY_FIELD_TYPE } from './types'; import type { DocumentFlowStep } from './types';
import { FRIENDLY_FIELD_TYPE } from './types';
const fontCaveat = Caveat({ const fontCaveat = Caveat({
weight: ['500'], weight: ['500'],
@@ -53,7 +56,6 @@ export type AddFieldsFormProps = {
hideRecipients?: boolean; hideRecipients?: boolean;
recipients: Recipient[]; recipients: Recipient[];
fields: Field[]; fields: Field[];
numberOfSteps: number;
onSubmit: (_data: TAddFieldsFormSchema) => void; onSubmit: (_data: TAddFieldsFormSchema) => void;
}; };
@@ -62,10 +64,11 @@ export const AddFieldsFormPartial = ({
hideRecipients = false, hideRecipients = false,
recipients, recipients,
fields, fields,
numberOfSteps,
onSubmit, onSubmit,
}: AddFieldsFormProps) => { useStep, // Stepper
}: WithStep<AddFieldsFormProps>) => {
const { isWithinPageBounds, getFieldPosition, getPage } = useDocumentElement(); const { isWithinPageBounds, getFieldPosition, getPage } = useDocumentElement();
const { currentStep, totalSteps, nextStep, previousStep } = useStep();
const { const {
control, control,
@@ -513,15 +516,15 @@ export const AddFieldsFormPartial = ({
<DocumentFlowFormContainerFooter> <DocumentFlowFormContainerFooter>
<DocumentFlowFormContainerStep <DocumentFlowFormContainerStep
title={documentFlow.title} title={documentFlow.title}
step={documentFlow.stepIndex} step={currentStep}
maxStep={numberOfSteps} maxStep={totalSteps}
/> />
<DocumentFlowFormContainerActions <DocumentFlowFormContainerActions
loading={isSubmitting} loading={isSubmitting}
disabled={isSubmitting} disabled={isSubmitting}
onGoBackClick={() => { onGoBackClick={() => {
documentFlow.onBackStep?.(); previousStep();
remove(); remove();
}} }}
onGoNextClick={() => void onFormSubmit()} onGoNextClick={() => void onFormSubmit()}

View File

@@ -9,45 +9,49 @@ import { Controller, useFieldArray, useForm } from 'react-hook-form';
import { useLimits } from '@documenso/ee/server-only/limits/provider/client'; import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
import { nanoid } from '@documenso/lib/universal/id'; import { nanoid } from '@documenso/lib/universal/id';
import { DocumentStatus, Field, Recipient, SendStatus } from '@documenso/prisma/client'; import type { Field, Recipient } from '@documenso/prisma/client';
import { DocumentWithData } from '@documenso/prisma/types/document-with-data'; import { DocumentStatus, SendStatus } from '@documenso/prisma/client';
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message'; import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message';
import { Input } from '@documenso/ui/primitives/input'; import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label'; import { Label } from '@documenso/ui/primitives/label';
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
import { TAddSignersFormSchema, ZAddSignersFormSchema } from './add-signers.types'; import type { WithStep } from '../stepper';
import type { TAddSignersFormSchema } from './add-signers.types';
import { ZAddSignersFormSchema } from './add-signers.types';
import { import {
DocumentFlowFormContainerActions, DocumentFlowFormContainerActions,
DocumentFlowFormContainerContent, DocumentFlowFormContainerContent,
DocumentFlowFormContainerFooter, DocumentFlowFormContainerFooter,
DocumentFlowFormContainerStep, DocumentFlowFormContainerStep,
} from './document-flow-root'; } from './document-flow-root';
import { DocumentFlowStep } from './types'; import type { DocumentFlowStep } from './types';
export type AddSignersFormProps = { export type AddSignersFormProps = {
documentFlow: DocumentFlowStep; documentFlow: DocumentFlowStep;
recipients: Recipient[]; recipients: Recipient[];
fields: Field[]; fields: Field[];
document: DocumentWithData; document: DocumentWithData;
numberOfSteps: number;
onSubmit: (_data: TAddSignersFormSchema) => void; onSubmit: (_data: TAddSignersFormSchema) => void;
}; };
export const AddSignersFormPartial = ({ export const AddSignersFormPartial = ({
documentFlow, documentFlow,
numberOfSteps,
recipients, recipients,
document, document,
fields: _fields, fields: _fields,
onSubmit, onSubmit,
}: AddSignersFormProps) => { useStep, // Stepper
}: WithStep<AddSignersFormProps>) => {
const { toast } = useToast(); const { toast } = useToast();
const { remaining } = useLimits(); const { remaining } = useLimits();
const initialId = useId(); const initialId = useId();
const { currentStep, totalSteps, nextStep, previousStep } = useStep();
const { const {
control, control,
handleSubmit, handleSubmit,
@@ -221,15 +225,15 @@ export const AddSignersFormPartial = ({
<DocumentFlowFormContainerFooter> <DocumentFlowFormContainerFooter>
<DocumentFlowFormContainerStep <DocumentFlowFormContainerStep
title={documentFlow.title} title={documentFlow.title}
step={documentFlow.stepIndex} step={currentStep}
maxStep={numberOfSteps} maxStep={totalSteps}
/> />
<DocumentFlowFormContainerActions <DocumentFlowFormContainerActions
canGoBack={document.status === DocumentStatus.DRAFT} canGoBack={document.status === DocumentStatus.DRAFT}
loading={isSubmitting} loading={isSubmitting}
disabled={isSubmitting} disabled={isSubmitting}
onGoBackClick={documentFlow.onBackStep} onGoBackClick={previousStep}
onGoNextClick={() => void onFormSubmit()} onGoNextClick={() => void onFormSubmit()}
/> />
</DocumentFlowFormContainerFooter> </DocumentFlowFormContainerFooter>

View File

@@ -2,28 +2,29 @@
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { DocumentStatus, Field, Recipient } from '@documenso/prisma/client'; import type { Field, Recipient } from '@documenso/prisma/client';
import { DocumentWithData } from '@documenso/prisma/types/document-with-data'; import { DocumentStatus } from '@documenso/prisma/client';
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message'; import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message';
import { Input } from '@documenso/ui/primitives/input'; import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label'; import { Label } from '@documenso/ui/primitives/label';
import { Textarea } from '@documenso/ui/primitives/textarea'; import { Textarea } from '@documenso/ui/primitives/textarea';
import { TAddSubjectFormSchema } from './add-subject.types'; import type { WithStep } from '../stepper';
import type { TAddSubjectFormSchema } from './add-subject.types';
import { import {
DocumentFlowFormContainerActions, DocumentFlowFormContainerActions,
DocumentFlowFormContainerContent, DocumentFlowFormContainerContent,
DocumentFlowFormContainerFooter, DocumentFlowFormContainerFooter,
DocumentFlowFormContainerStep, DocumentFlowFormContainerStep,
} from './document-flow-root'; } from './document-flow-root';
import { DocumentFlowStep } from './types'; import type { DocumentFlowStep } from './types';
export type AddSubjectFormProps = { export type AddSubjectFormProps = {
documentFlow: DocumentFlowStep; documentFlow: DocumentFlowStep;
recipients: Recipient[]; recipients: Recipient[];
fields: Field[]; fields: Field[];
document: DocumentWithData; document: DocumentWithData;
numberOfSteps: number;
onSubmit: (_data: TAddSubjectFormSchema) => void; onSubmit: (_data: TAddSubjectFormSchema) => void;
}; };
@@ -32,9 +33,9 @@ export const AddSubjectFormPartial = ({
recipients: _recipients, recipients: _recipients,
fields: _fields, fields: _fields,
document, document,
numberOfSteps,
onSubmit, onSubmit,
}: AddSubjectFormProps) => { useStep,
}: WithStep<AddSubjectFormProps>) => {
const { const {
register, register,
handleSubmit, handleSubmit,
@@ -49,6 +50,7 @@ export const AddSubjectFormPartial = ({
}); });
const onFormSubmit = handleSubmit(onSubmit); const onFormSubmit = handleSubmit(onSubmit);
const { currentStep, totalSteps, nextStep, previousStep } = useStep();
return ( return (
<> <>
@@ -124,15 +126,15 @@ export const AddSubjectFormPartial = ({
<DocumentFlowFormContainerFooter> <DocumentFlowFormContainerFooter>
<DocumentFlowFormContainerStep <DocumentFlowFormContainerStep
title={documentFlow.title} title={documentFlow.title}
step={documentFlow.stepIndex} step={currentStep}
maxStep={numberOfSteps} maxStep={totalSteps}
/> />
<DocumentFlowFormContainerActions <DocumentFlowFormContainerActions
loading={isSubmitting} loading={isSubmitting}
disabled={isSubmitting} disabled={isSubmitting}
goNextLabel={document.status === DocumentStatus.DRAFT ? 'Send' : 'Update'} goNextLabel={document.status === DocumentStatus.DRAFT ? 'Send' : 'Update'}
onGoBackClick={documentFlow.onBackStep} onGoBackClick={previousStep}
onGoNextClick={() => void onFormSubmit()} onGoNextClick={() => void onFormSubmit()}
/> />
</DocumentFlowFormContainerFooter> </DocumentFlowFormContainerFooter>

View File

@@ -8,6 +8,7 @@ import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-messa
import { Input } from '@documenso/ui/primitives/input'; import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label'; import { Label } from '@documenso/ui/primitives/label';
import type { WithStep } from '../stepper';
import type { TAddTitleFormSchema } from './add-title.types'; import type { TAddTitleFormSchema } from './add-title.types';
import { import {
DocumentFlowFormContainerActions, DocumentFlowFormContainerActions,
@@ -22,7 +23,6 @@ export type AddTitleFormProps = {
recipients: Recipient[]; recipients: Recipient[];
fields: Field[]; fields: Field[];
document: DocumentWithData; document: DocumentWithData;
numberOfSteps: number;
onSubmit: (_data: TAddTitleFormSchema) => void; onSubmit: (_data: TAddTitleFormSchema) => void;
}; };
@@ -31,9 +31,9 @@ export const AddTitleFormPartial = ({
recipients: _recipients, recipients: _recipients,
fields: _fields, fields: _fields,
document, document,
numberOfSteps,
onSubmit, onSubmit,
}: AddTitleFormProps) => { useStep,
}: WithStep<AddTitleFormProps>) => {
const { const {
register, register,
handleSubmit, handleSubmit,
@@ -46,6 +46,8 @@ export const AddTitleFormPartial = ({
const onFormSubmit = handleSubmit(onSubmit); const onFormSubmit = handleSubmit(onSubmit);
const { stepIndex, currentStep, totalSteps, previousStep } = useStep();
return ( return (
<> <>
<DocumentFlowFormContainerContent> <DocumentFlowFormContainerContent>
@@ -72,14 +74,15 @@ export const AddTitleFormPartial = ({
<DocumentFlowFormContainerFooter> <DocumentFlowFormContainerFooter>
<DocumentFlowFormContainerStep <DocumentFlowFormContainerStep
title={documentFlow.title} title={documentFlow.title}
step={documentFlow.stepIndex} step={currentStep}
maxStep={numberOfSteps} maxStep={totalSteps}
/> />
<DocumentFlowFormContainerActions <DocumentFlowFormContainerActions
loading={isSubmitting} loading={isSubmitting}
disabled={isSubmitting} disabled={isSubmitting}
onGoBackClick={documentFlow.onBackStep} canGoBack={stepIndex !== 0}
onGoBackClick={previousStep}
onGoNextClick={() => void onFormSubmit()} onGoNextClick={() => void onFormSubmit()}
/> />
</DocumentFlowFormContainerFooter> </DocumentFlowFormContainerFooter>

View File

@@ -1,6 +1,7 @@
'use client'; 'use client';
import React, { HTMLAttributes } from 'react'; import type { HTMLAttributes } from 'react';
import React from 'react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';

View File

@@ -53,7 +53,7 @@ export const FRIENDLY_FIELD_TYPE: Record<FieldType, string> = {
export interface DocumentFlowStep { export interface DocumentFlowStep {
title: string; title: string;
description: string; description: string;
stepIndex: number; stepIndex?: number;
onBackStep?: () => unknown; onBackStep?: () => unknown;
onNextStep?: () => unknown; onNextStep?: () => unknown;
} }