From c46a69f865c8e69a9bf3376574dd5ac2171e64bc Mon Sep 17 00:00:00 2001 From: mikezzb Date: Sat, 2 Dec 2023 22:30:10 -0500 Subject: [PATCH 01/10] feat: stepper component --- packages/ui/primitives/stepper.tsx | 80 ++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 packages/ui/primitives/stepper.tsx diff --git a/packages/ui/primitives/stepper.tsx b/packages/ui/primitives/stepper.tsx new file mode 100644 index 000000000..e4d87a7ba --- /dev/null +++ b/packages/ui/primitives/stepper.tsx @@ -0,0 +1,80 @@ +import React, { 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; + }; +}; + +export type WithStep = T & StepProps; + +type StepperProps = { + children: React.ReactNode; + onComplete?: () => void; + onStepChanged?: (currentStep: number) => void; + currentStep?: number; + setCurrentStep?: (step: number) => void; +}; + +export const Stepper: FC = ({ + children, + onComplete, + onStepChanged, + currentStep: propCurrentStep, + setCurrentStep: propSetCurrentStep, +}) => { + const [stateCurrentStep, stateSetCurrentStep] = useState(1); + + // Determine if props are provided, otherwise use state + const isControlled = propCurrentStep !== undefined && propSetCurrentStep !== undefined; + const currentStep = isControlled ? propCurrentStep : stateCurrentStep; + const setCurrentStep = isControlled ? propSetCurrentStep : stateSetCurrentStep; + + const totalSteps = React.Children.count(children); + + const nextStep = () => { + if (currentStep < totalSteps) { + setCurrentStep(currentStep + 1); + } else { + onComplete && onComplete(); + } + }; + + const previousStep = () => { + if (currentStep > 1) { + setCurrentStep(currentStep - 1); + } + }; + + useEffect(() => { + onStepChanged && onStepChanged(currentStep); + }, [currentStep, onStepChanged]); + + const useStep = (stepIndex: number) => ({ + stepIndex, + currentStep, + totalSteps, + isFirst: currentStep === 1, + isLast: currentStep === totalSteps, + nextStep, + previousStep, + }); + + const renderStep = (child: React.ReactNode, index: number) => { + if (!React.isValidElement(child)) return null; + return index + 1 === currentStep + ? React.cloneElement(child, { + useStep: () => useStep(index), + }) + : null; + }; + + return <>{React.Children.toArray(children).map(renderStep)}; +}; From a98b429052f08accfaaebf249263f14c1160ced9 Mon Sep 17 00:00:00 2001 From: mikezzb Date: Sat, 2 Dec 2023 22:42:59 -0500 Subject: [PATCH 02/10] feat: stepper refactor example --- .../documents/[id]/edit-document.tsx | 75 ++++++++----------- .../primitives/document-flow/add-fields.tsx | 21 +++--- .../primitives/document-flow/add-signers.tsx | 24 +++--- .../primitives/document-flow/add-subject.tsx | 22 +++--- .../ui/primitives/document-flow/add-title.tsx | 15 ++-- .../document-flow/document-flow-root.tsx | 3 +- packages/ui/primitives/document-flow/types.ts | 2 +- 7 files changed, 80 insertions(+), 82 deletions(-) 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 e775bffdc..8aface5a6 100644 --- a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx +++ b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx @@ -5,7 +5,6 @@ import { useState } from 'react'; import { useRouter } from 'next/navigation'; 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 { trpc } from '@documenso/trpc/react'; import { cn } from '@documenso/ui/lib/utils'; @@ -24,6 +23,7 @@ import { } from '@documenso/ui/primitives/document-flow/document-flow-root'; import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types'; import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer'; +import { Stepper } from '@documenso/ui/primitives/stepper'; import { useToast } from '@documenso/ui/primitives/use-toast'; export type EditDocumentFormProps = { @@ -35,7 +35,12 @@ export type EditDocumentFormProps = { documentData: DocumentData; }; -type EditDocumentStep = 'title' | 'signers' | 'fields' | 'subject'; +enum EditDocumentStepEnum { + TITLE, + SIGNERS, + FIELDS, + SUBJECT, +} export const EditDocumentForm = ({ className, @@ -48,42 +53,35 @@ export const EditDocumentForm = ({ const { toast } = useToast(); const router = useRouter(); - const [step, setStep] = useState( - document.status === DocumentStatus.DRAFT ? 'title' : 'signers', - ); + // controlled stepper state + const [stepIdx, setStepIdx] = useState(0); 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(); - const documentFlow: Record = { - title: { + // controlled stepper next + const nextStep = () => setStepIdx(stepIdx + 1); + + const documentFlow: DocumentFlowStep[] = [ + { 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, - onBackStep: () => document.status === DocumentStatus.DRAFT && setStep('title'), }, - fields: { + { title: 'Add Fields', description: 'Add all relevant fields for each recipient.', - stepIndex: 3, - onBackStep: () => setStep('signers'), }, - subject: { + { title: 'Add Subject', 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) => { try { @@ -95,7 +93,7 @@ export const EditDocumentForm = ({ router.refresh(); - setStep('signers'); + nextStep(); } catch (err) { console.error(err); @@ -116,8 +114,7 @@ export const EditDocumentForm = ({ }); router.refresh(); - - setStep('fields'); + nextStep(); } catch (err) { console.error(err); @@ -138,8 +135,7 @@ export const EditDocumentForm = ({ }); router.refresh(); - - setStep('subject'); + nextStep(); } catch (err) { console.error(err); @@ -181,6 +177,8 @@ export const EditDocumentForm = ({ } }; + const currentDocumentFlow = documentFlow[stepIdx]; + return (
- - {step === 'title' && ( + setStepIdx(step - 1)}> - )} - - {step === 'signers' && ( - )} - - {step === 'fields' && ( - )} - - {step === 'subject' && ( - )} +
diff --git a/packages/ui/primitives/document-flow/add-fields.tsx b/packages/ui/primitives/document-flow/add-fields.tsx index f662dca8b..801070d15 100644 --- a/packages/ui/primitives/document-flow/add-fields.tsx +++ b/packages/ui/primitives/document-flow/add-fields.tsx @@ -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 { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer'; 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 { Button } from '@documenso/ui/primitives/button'; import { Card, CardContent } from '@documenso/ui/primitives/card'; @@ -25,7 +26,8 @@ import { import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover'; 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 { DocumentFlowFormContainerActions, DocumentFlowFormContainerContent, @@ -33,7 +35,8 @@ import { DocumentFlowFormContainerStep, } from './document-flow-root'; 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({ weight: ['500'], @@ -53,7 +56,6 @@ export type AddFieldsFormProps = { hideRecipients?: boolean; recipients: Recipient[]; fields: Field[]; - numberOfSteps: number; onSubmit: (_data: TAddFieldsFormSchema) => void; }; @@ -62,10 +64,11 @@ export const AddFieldsFormPartial = ({ hideRecipients = false, recipients, fields, - numberOfSteps, onSubmit, -}: AddFieldsFormProps) => { + useStep, // Stepper +}: WithStep) => { const { isWithinPageBounds, getFieldPosition, getPage } = useDocumentElement(); + const { currentStep, totalSteps, nextStep, previousStep } = useStep(); const { control, @@ -513,15 +516,15 @@ export const AddFieldsFormPartial = ({ { - documentFlow.onBackStep?.(); + previousStep(); remove(); }} onGoNextClick={() => void onFormSubmit()} diff --git a/packages/ui/primitives/document-flow/add-signers.tsx b/packages/ui/primitives/document-flow/add-signers.tsx index b623b0d4e..977f95bdd 100644 --- a/packages/ui/primitives/document-flow/add-signers.tsx +++ b/packages/ui/primitives/document-flow/add-signers.tsx @@ -9,45 +9,49 @@ import { Controller, useFieldArray, useForm } from 'react-hook-form'; import { useLimits } from '@documenso/ee/server-only/limits/provider/client'; import { nanoid } from '@documenso/lib/universal/id'; -import { DocumentStatus, Field, Recipient, SendStatus } from '@documenso/prisma/client'; -import { DocumentWithData } from '@documenso/prisma/types/document-with-data'; +import type { Field, Recipient } from '@documenso/prisma/client'; +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 { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message'; import { Input } from '@documenso/ui/primitives/input'; import { Label } from '@documenso/ui/primitives/label'; 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 { DocumentFlowFormContainerActions, DocumentFlowFormContainerContent, DocumentFlowFormContainerFooter, DocumentFlowFormContainerStep, } from './document-flow-root'; -import { DocumentFlowStep } from './types'; +import type { DocumentFlowStep } from './types'; export type AddSignersFormProps = { documentFlow: DocumentFlowStep; recipients: Recipient[]; fields: Field[]; document: DocumentWithData; - numberOfSteps: number; onSubmit: (_data: TAddSignersFormSchema) => void; }; export const AddSignersFormPartial = ({ documentFlow, - numberOfSteps, recipients, document, fields: _fields, onSubmit, -}: AddSignersFormProps) => { + useStep, // Stepper +}: WithStep) => { const { toast } = useToast(); const { remaining } = useLimits(); const initialId = useId(); + const { currentStep, totalSteps, nextStep, previousStep } = useStep(); + const { control, handleSubmit, @@ -221,15 +225,15 @@ export const AddSignersFormPartial = ({ void onFormSubmit()} /> diff --git a/packages/ui/primitives/document-flow/add-subject.tsx b/packages/ui/primitives/document-flow/add-subject.tsx index 1bf3b2cb4..e2a10afa3 100644 --- a/packages/ui/primitives/document-flow/add-subject.tsx +++ b/packages/ui/primitives/document-flow/add-subject.tsx @@ -2,28 +2,29 @@ import { useForm } from 'react-hook-form'; -import { DocumentStatus, Field, Recipient } from '@documenso/prisma/client'; -import { DocumentWithData } from '@documenso/prisma/types/document-with-data'; +import type { Field, Recipient } from '@documenso/prisma/client'; +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 { Input } from '@documenso/ui/primitives/input'; import { Label } from '@documenso/ui/primitives/label'; 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 { DocumentFlowFormContainerActions, DocumentFlowFormContainerContent, DocumentFlowFormContainerFooter, DocumentFlowFormContainerStep, } from './document-flow-root'; -import { DocumentFlowStep } from './types'; +import type { DocumentFlowStep } from './types'; export type AddSubjectFormProps = { documentFlow: DocumentFlowStep; recipients: Recipient[]; fields: Field[]; document: DocumentWithData; - numberOfSteps: number; onSubmit: (_data: TAddSubjectFormSchema) => void; }; @@ -32,9 +33,9 @@ export const AddSubjectFormPartial = ({ recipients: _recipients, fields: _fields, document, - numberOfSteps, onSubmit, -}: AddSubjectFormProps) => { + useStep, +}: WithStep) => { const { register, handleSubmit, @@ -49,6 +50,7 @@ export const AddSubjectFormPartial = ({ }); const onFormSubmit = handleSubmit(onSubmit); + const { currentStep, totalSteps, nextStep, previousStep } = useStep(); return ( <> @@ -124,15 +126,15 @@ export const AddSubjectFormPartial = ({ void onFormSubmit()} /> diff --git a/packages/ui/primitives/document-flow/add-title.tsx b/packages/ui/primitives/document-flow/add-title.tsx index 3ec44b17d..75cd47e93 100644 --- a/packages/ui/primitives/document-flow/add-title.tsx +++ b/packages/ui/primitives/document-flow/add-title.tsx @@ -8,6 +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 type { TAddTitleFormSchema } from './add-title.types'; import { DocumentFlowFormContainerActions, @@ -22,7 +23,6 @@ export type AddTitleFormProps = { recipients: Recipient[]; fields: Field[]; document: DocumentWithData; - numberOfSteps: number; onSubmit: (_data: TAddTitleFormSchema) => void; }; @@ -31,9 +31,9 @@ export const AddTitleFormPartial = ({ recipients: _recipients, fields: _fields, document, - numberOfSteps, onSubmit, -}: AddTitleFormProps) => { + useStep, +}: WithStep) => { const { register, handleSubmit, @@ -46,6 +46,8 @@ export const AddTitleFormPartial = ({ const onFormSubmit = handleSubmit(onSubmit); + const { stepIndex, currentStep, totalSteps, previousStep } = useStep(); + return ( <> @@ -72,14 +74,15 @@ export const AddTitleFormPartial = ({ void onFormSubmit()} /> diff --git a/packages/ui/primitives/document-flow/document-flow-root.tsx b/packages/ui/primitives/document-flow/document-flow-root.tsx index aec74dd6c..9142f4258 100644 --- a/packages/ui/primitives/document-flow/document-flow-root.tsx +++ b/packages/ui/primitives/document-flow/document-flow-root.tsx @@ -1,6 +1,7 @@ 'use client'; -import React, { HTMLAttributes } from 'react'; +import type { HTMLAttributes } from 'react'; +import React from 'react'; import { motion } from 'framer-motion'; diff --git a/packages/ui/primitives/document-flow/types.ts b/packages/ui/primitives/document-flow/types.ts index c9244ad05..677dc931b 100644 --- a/packages/ui/primitives/document-flow/types.ts +++ b/packages/ui/primitives/document-flow/types.ts @@ -53,7 +53,7 @@ export const FRIENDLY_FIELD_TYPE: Record = { export interface DocumentFlowStep { title: string; description: string; - stepIndex: number; + stepIndex?: number; onBackStep?: () => unknown; onNextStep?: () => unknown; } From eccf63dcfdf22a0b3cd8941ca94207f1223cb0c5 Mon Sep 17 00:00:00 2001 From: mikezzb Date: Sat, 2 Dec 2023 23:56:07 -0500 Subject: [PATCH 03/10] chore: refactor --- packages/ui/primitives/stepper.tsx | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/ui/primitives/stepper.tsx b/packages/ui/primitives/stepper.tsx index e4d87a7ba..f827139e0 100644 --- a/packages/ui/primitives/stepper.tsx +++ b/packages/ui/primitives/stepper.tsx @@ -57,8 +57,8 @@ export const Stepper: FC = ({ onStepChanged && onStepChanged(currentStep); }, [currentStep, onStepChanged]); - const useStep = (stepIndex: number) => ({ - stepIndex, + const useStep = () => ({ + stepIndex: currentStep - 1, currentStep, totalSteps, isFirst: currentStep === 1, @@ -67,14 +67,13 @@ export const Stepper: FC = ({ previousStep, }); - const renderStep = (child: React.ReactNode, index: number) => { - if (!React.isValidElement(child)) return null; - return index + 1 === currentStep - ? React.cloneElement(child, { - useStep: () => useStep(index), - }) - : null; - }; + // empty stepper + if (totalSteps === 0) return null; - return <>{React.Children.toArray(children).map(renderStep)}; + const currentChild = React.Children.toArray(children)[currentStep - 1]; + + // type validation + if (!React.isValidElement(currentChild)) return null; + + return <>{React.cloneElement(currentChild, { useStep })}; }; From 40a4ec4436224b2616a3e2c339bddff90f5675cb Mon Sep 17 00:00:00 2001 From: mikezzb Date: Sun, 3 Dec 2023 01:15:59 -0500 Subject: [PATCH 04/10] refactor: useContext & remove enum --- .../documents/[id]/edit-document.tsx | 53 ++++++++++--------- .../primitives/document-flow/add-fields.tsx | 7 ++- .../primitives/document-flow/add-signers.tsx | 7 ++- .../primitives/document-flow/add-subject.tsx | 7 ++- .../ui/primitives/document-flow/add-title.tsx | 5 +- packages/ui/primitives/stepper.tsx | 52 +++++++++--------- 6 files changed, 66 insertions(+), 65 deletions(-) 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; }; From 43b1a14415850fbe8b3758b86b2edf55506cd6b4 Mon Sep 17 00:00:00 2001 From: mikezzb Date: Sun, 3 Dec 2023 11:21:51 -0500 Subject: [PATCH 05/10] chore: let code breath --- packages/ui/primitives/stepper.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/ui/primitives/stepper.tsx b/packages/ui/primitives/stepper.tsx index 795304e8c..35086ff17 100644 --- a/packages/ui/primitives/stepper.tsx +++ b/packages/ui/primitives/stepper.tsx @@ -56,7 +56,9 @@ export const Stepper: FC = ({ }, [currentStep, onStepChanged]); // Empty stepper - if (totalSteps === 0) return null; + if (totalSteps === 0) { + return null; + } const currentChild = React.Children.toArray(children)[currentStep - 1]; @@ -76,6 +78,8 @@ export const Stepper: FC = ({ /** 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'); + if (!context) { + throw new Error('useStep must be used within a Stepper'); + } return context; }; From 340c9298064bb1f667376ea0ea01142d1c4d52dc Mon Sep 17 00:00:00 2001 From: mikezzb Date: Sun, 3 Dec 2023 11:36:18 -0500 Subject: [PATCH 06/10] refactor: edit doc --- .../src/app/(dashboard)/documents/[id]/edit-document.tsx | 9 +-------- packages/ui/primitives/document-flow/add-fields.tsx | 5 +++++ packages/ui/primitives/document-flow/add-signers.tsx | 5 +++++ packages/ui/primitives/document-flow/add-subject.tsx | 5 +++++ packages/ui/primitives/document-flow/add-title.tsx | 5 +++++ 5 files changed, 21 insertions(+), 8 deletions(-) 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 73ec10a56..53da2d353 100644 --- a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx +++ b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx @@ -18,10 +18,7 @@ import { AddSubjectFormPartial } from '@documenso/ui/primitives/document-flow/ad import type { TAddSubjectFormSchema } from '@documenso/ui/primitives/document-flow/add-subject.types'; import { AddTitleFormPartial } from '@documenso/ui/primitives/document-flow/add-title'; import type { TAddTitleFormSchema } from '@documenso/ui/primitives/document-flow/add-title.types'; -import { - DocumentFlowFormContainer, - DocumentFlowFormContainerHeader, -} from '@documenso/ui/primitives/document-flow/document-flow-root'; +import { DocumentFlowFormContainer } from '@documenso/ui/primitives/document-flow/document-flow-root'; import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types'; import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer'; import { Stepper } from '@documenso/ui/primitives/stepper'; @@ -195,10 +192,6 @@ export const EditDocumentForm = ({ className="lg:h-[calc(100vh-6rem)]" onSubmit={(e) => e.preventDefault()} > - setStep(EditDocumentSteps[step - 1])} diff --git a/packages/ui/primitives/document-flow/add-fields.tsx b/packages/ui/primitives/document-flow/add-fields.tsx index 9ffcd5d80..112f2f849 100644 --- a/packages/ui/primitives/document-flow/add-fields.tsx +++ b/packages/ui/primitives/document-flow/add-fields.tsx @@ -32,6 +32,7 @@ import { DocumentFlowFormContainerActions, DocumentFlowFormContainerContent, DocumentFlowFormContainerFooter, + DocumentFlowFormContainerHeader, DocumentFlowFormContainerStep, } from './document-flow-root'; import { FieldItem } from './field-item'; @@ -289,6 +290,10 @@ export const AddFieldsFormPartial = ({ return ( <> +
{selectedField && ( diff --git a/packages/ui/primitives/document-flow/add-signers.tsx b/packages/ui/primitives/document-flow/add-signers.tsx index f549b7220..13af03d26 100644 --- a/packages/ui/primitives/document-flow/add-signers.tsx +++ b/packages/ui/primitives/document-flow/add-signers.tsx @@ -25,6 +25,7 @@ import { DocumentFlowFormContainerActions, DocumentFlowFormContainerContent, DocumentFlowFormContainerFooter, + DocumentFlowFormContainerHeader, DocumentFlowFormContainerStep, } from './document-flow-root'; import type { DocumentFlowStep } from './types'; @@ -129,6 +130,10 @@ export const AddSignersFormPartial = ({ return ( <> +
diff --git a/packages/ui/primitives/document-flow/add-subject.tsx b/packages/ui/primitives/document-flow/add-subject.tsx index e5456fb43..e9e761af0 100644 --- a/packages/ui/primitives/document-flow/add-subject.tsx +++ b/packages/ui/primitives/document-flow/add-subject.tsx @@ -16,6 +16,7 @@ import { DocumentFlowFormContainerActions, DocumentFlowFormContainerContent, DocumentFlowFormContainerFooter, + DocumentFlowFormContainerHeader, DocumentFlowFormContainerStep, } from './document-flow-root'; import type { DocumentFlowStep } from './types'; @@ -53,6 +54,10 @@ export const AddSubjectFormPartial = ({ return ( <> +
diff --git a/packages/ui/primitives/document-flow/add-title.tsx b/packages/ui/primitives/document-flow/add-title.tsx index 27b6aff03..2b91e1033 100644 --- a/packages/ui/primitives/document-flow/add-title.tsx +++ b/packages/ui/primitives/document-flow/add-title.tsx @@ -14,6 +14,7 @@ import { DocumentFlowFormContainerActions, DocumentFlowFormContainerContent, DocumentFlowFormContainerFooter, + DocumentFlowFormContainerHeader, DocumentFlowFormContainerStep, } from './document-flow-root'; import type { DocumentFlowStep } from './types'; @@ -49,6 +50,10 @@ export const AddTitleFormPartial = ({ return ( <> +
From 859b789018a4a7ff654e6e7f7ac0f2cd9a00c306 Mon Sep 17 00:00:00 2001 From: mikezzb Date: Sun, 3 Dec 2023 12:50:56 -0500 Subject: [PATCH 07/10] feat: isCompleting --- packages/ui/primitives/stepper.tsx | 38 +++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/packages/ui/primitives/stepper.tsx b/packages/ui/primitives/stepper.tsx index 35086ff17..71a4d025c 100644 --- a/packages/ui/primitives/stepper.tsx +++ b/packages/ui/primitives/stepper.tsx @@ -1,7 +1,8 @@ -import React, { createContext, useContext, useEffect, useState } from 'react'; +import React, { createContext, useContext, useState } from 'react'; import type { FC } from 'react'; type StepContextType = { + isCompleting: boolean; stepIndex: number; currentStep: number; totalSteps: number; @@ -15,10 +16,11 @@ const StepContext = createContext(null); type StepperProps = { children: React.ReactNode; - onComplete?: () => void; + onComplete?: () => void | Promise; onStepChanged?: (currentStep: number) => void; currentStep?: number; // external control prop setCurrentStep?: (step: number) => void; // external control function + isAsyncComplete?: boolean; }; export const Stepper: FC = ({ @@ -27,8 +29,10 @@ export const Stepper: FC = ({ onStepChanged, currentStep: propCurrentStep, setCurrentStep: propSetCurrentStep, + isAsyncComplete, }) => { const [stateCurrentStep, stateSetCurrentStep] = useState(1); + const [isCompleting, setIsCompleting] = useState(false); // Determine if props are provided, otherwise use state const isControlled = propCurrentStep !== undefined && propSetCurrentStep !== undefined; @@ -37,24 +41,39 @@ export const Stepper: FC = ({ const totalSteps = React.Children.count(children); + const handleComplete = async () => { + if (!onComplete) { + return; + } + if (!isAsyncComplete) { + void onComplete(); + return; + } + setIsCompleting(true); + await onComplete(); + // handle async complete action + setIsCompleting(false); + }; + + const handleStepChange = (nextStep: number) => { + setCurrentStep(nextStep); + onStepChanged && onStepChanged(nextStep); + }; + const nextStep = () => { if (currentStep < totalSteps) { - setCurrentStep(currentStep + 1); + void handleStepChange(currentStep + 1); } else { - onComplete && onComplete(); + void handleComplete(); } }; const previousStep = () => { if (currentStep > 1) { - setCurrentStep(currentStep - 1); + void handleStepChange(currentStep - 1); } }; - useEffect(() => { - onStepChanged && onStepChanged(currentStep); - }, [currentStep, onStepChanged]); - // Empty stepper if (totalSteps === 0) { return null; @@ -63,6 +82,7 @@ export const Stepper: FC = ({ const currentChild = React.Children.toArray(children)[currentStep - 1]; const stepContextValue: StepContextType = { + isCompleting, stepIndex: currentStep - 1, currentStep, totalSteps, From 3ff7b188d7dc49abce7a5ae31c8f9948dbc13e0a Mon Sep 17 00:00:00 2001 From: Mythie Date: Thu, 7 Dec 2023 15:06:49 +1100 Subject: [PATCH 08/10] fix(ui): tidy stepper code --- packages/ui/primitives/stepper.tsx | 38 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/packages/ui/primitives/stepper.tsx b/packages/ui/primitives/stepper.tsx index 71a4d025c..d38de2eb1 100644 --- a/packages/ui/primitives/stepper.tsx +++ b/packages/ui/primitives/stepper.tsx @@ -1,7 +1,7 @@ import React, { createContext, useContext, useState } from 'react'; import type { FC } from 'react'; -type StepContextType = { +type StepContextValue = { isCompleting: boolean; stepIndex: number; currentStep: number; @@ -12,7 +12,7 @@ type StepContextType = { previousStep: () => void; }; -const StepContext = createContext(null); +const StepContext = createContext(null); type StepperProps = { children: React.ReactNode; @@ -20,7 +20,6 @@ type StepperProps = { onStepChanged?: (currentStep: number) => void; currentStep?: number; // external control prop setCurrentStep?: (step: number) => void; // external control function - isAsyncComplete?: boolean; }; export const Stepper: FC = ({ @@ -29,7 +28,6 @@ export const Stepper: FC = ({ onStepChanged, currentStep: propCurrentStep, setCurrentStep: propSetCurrentStep, - isAsyncComplete, }) => { const [stateCurrentStep, stateSetCurrentStep] = useState(1); const [isCompleting, setIsCompleting] = useState(false); @@ -42,22 +40,26 @@ export const Stepper: FC = ({ const totalSteps = React.Children.count(children); const handleComplete = async () => { - if (!onComplete) { - return; + try { + if (!onComplete) { + return; + } + + setIsCompleting(true); + + await onComplete(); + + setIsCompleting(false); + } catch (error) { + setIsCompleting(false); + + throw error; } - if (!isAsyncComplete) { - void onComplete(); - return; - } - setIsCompleting(true); - await onComplete(); - // handle async complete action - setIsCompleting(false); }; const handleStepChange = (nextStep: number) => { setCurrentStep(nextStep); - onStepChanged && onStepChanged(nextStep); + onStepChanged?.(nextStep); }; const nextStep = () => { @@ -81,7 +83,7 @@ export const Stepper: FC = ({ const currentChild = React.Children.toArray(children)[currentStep - 1]; - const stepContextValue: StepContextType = { + const stepContextValue: StepContextValue = { isCompleting, stepIndex: currentStep - 1, currentStep, @@ -96,10 +98,12 @@ export const Stepper: FC = ({ }; /** Hook for children to use the step context */ -export const useStep = (): StepContextType => { +export const useStep = (): StepContextValue => { const context = useContext(StepContext); + if (!context) { throw new Error('useStep must be used within a Stepper'); } + return context; }; From 1a34f9fa7a728e71e3fa685550568ab1efca88f7 Mon Sep 17 00:00:00 2001 From: Mythie Date: Thu, 7 Dec 2023 15:08:00 +1100 Subject: [PATCH 09/10] fix: import updates and api route body sizes --- apps/marketing/src/app/(marketing)/layout.tsx | 4 +- .../app/(marketing)/singleplayer/client.tsx | 35 ++- apps/marketing/src/pages/api/trpc/[trpc].ts | 5 + .../documents/[id]/edit-document.tsx | 1 + apps/web/src/pages/api/trpc/[trpc].ts | 5 + .../document/document-download-button.tsx | 5 +- .../document/document-share-button.tsx | 9 +- .../ui/components/field/field-tooltip.tsx | 11 +- packages/ui/components/field/field.tsx | 7 +- packages/ui/components/signing-card.tsx | 10 +- packages/ui/primitives/combobox.tsx | 15 +- packages/ui/primitives/document-dropzone.tsx | 8 +- .../primitives/document-flow/add-fields.tsx | 18 +- .../document-flow/add-signature.tsx | 258 +++++++++--------- .../primitives/document-flow/add-signers.tsx | 10 +- .../primitives/document-flow/add-subject.tsx | 8 +- .../ui/primitives/document-flow/add-title.tsx | 6 +- .../document-flow/document-flow-root.tsx | 4 +- .../primitives/document-flow/field-item.tsx | 7 +- .../send-document-action-dialog.tsx | 5 +- .../single-player-mode-fields.tsx | 8 +- .../ui/primitives/form/form-error-message.tsx | 2 +- packages/ui/primitives/form/form.tsx | 15 +- packages/ui/primitives/pdf-viewer.tsx | 6 +- .../signature-pad/signature-pad.tsx | 18 +- 25 files changed, 243 insertions(+), 237 deletions(-) diff --git a/apps/marketing/src/app/(marketing)/layout.tsx b/apps/marketing/src/app/(marketing)/layout.tsx index 365d8a5d1..248414b33 100644 --- a/apps/marketing/src/app/(marketing)/layout.tsx +++ b/apps/marketing/src/app/(marketing)/layout.tsx @@ -29,7 +29,7 @@ export default function MarketingLayout({ children }: MarketingLayoutProps) { return (
@@ -41,7 +41,7 @@ export default function MarketingLayout({ children }: MarketingLayoutProps) {
-
{children}
+
{children}
diff --git a/apps/marketing/src/app/(marketing)/singleplayer/client.tsx b/apps/marketing/src/app/(marketing)/singleplayer/client.tsx index 1dcb2d76b..b7654c7cf 100644 --- a/apps/marketing/src/app/(marketing)/singleplayer/client.tsx +++ b/apps/marketing/src/app/(marketing)/singleplayer/client.tsx @@ -17,15 +17,14 @@ import { AddFieldsFormPartial } from '@documenso/ui/primitives/document-flow/add import type { TAddFieldsFormSchema } from '@documenso/ui/primitives/document-flow/add-fields.types'; import { AddSignatureFormPartial } from '@documenso/ui/primitives/document-flow/add-signature'; import type { TAddSignatureFormSchema } from '@documenso/ui/primitives/document-flow/add-signature.types'; -import { - DocumentFlowFormContainer, - DocumentFlowFormContainerHeader, -} from '@documenso/ui/primitives/document-flow/document-flow-root'; +import { DocumentFlowFormContainer } from '@documenso/ui/primitives/document-flow/document-flow-root'; import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types'; import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer'; +import { Stepper } from '@documenso/ui/primitives/stepper'; import { useToast } from '@documenso/ui/primitives/use-toast'; -type SinglePlayerModeStep = 'fields' | 'sign'; +const SinglePlayerModeSteps = ['fields', 'sign'] as const; +type SinglePlayerModeStep = (typeof SinglePlayerModeSteps)[number]; // !: This entire file is a hack to get around failed prerendering of // !: the Single Player Mode page. This regression was introduced during @@ -226,37 +225,35 @@ export const SinglePlayerClient = () => {
- e.preventDefault()}> - - - {/* Add fields to PDF page. */} - {step === 'fields' && ( + e.preventDefault()} + > + setStep(SinglePlayerModeSteps[step - 1])} + > + {/* Add fields to PDF page. */}
- )} - {/* Enter user details and signature. */} - {step === 'sign' && ( + {/* Enter user details and signature. */} + field.type === 'NAME'))} requireSignature={Boolean(fields.find((field) => field.type === 'SIGNATURE'))} /> - )} +
diff --git a/apps/marketing/src/pages/api/trpc/[trpc].ts b/apps/marketing/src/pages/api/trpc/[trpc].ts index 0bc991a98..c43291ea1 100644 --- a/apps/marketing/src/pages/api/trpc/[trpc].ts +++ b/apps/marketing/src/pages/api/trpc/[trpc].ts @@ -4,6 +4,11 @@ import { appRouter } from '@documenso/trpc/server/router'; export const config = { maxDuration: 60, + api: { + bodyParser: { + sizeLimit: '50mb', + }, + }, }; export default trpcNext.createNextApiHandler({ 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 53da2d353..ffce3bd6c 100644 --- a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx +++ b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx @@ -204,6 +204,7 @@ export const EditDocumentForm = ({ document={document} onSubmit={onAddTitleFormSubmit} /> + & { disabled?: boolean; diff --git a/packages/ui/components/document/document-share-button.tsx b/packages/ui/components/document/document-share-button.tsx index 5b6e9006a..b366123fb 100644 --- a/packages/ui/components/document/document-share-button.tsx +++ b/packages/ui/components/document/document-share-button.tsx @@ -13,8 +13,9 @@ import { } from '@documenso/lib/constants/toast'; import { generateTwitterIntent } from '@documenso/lib/universal/generate-twitter-intent'; import { trpc } from '@documenso/trpc/react'; -import { cn } from '@documenso/ui/lib/utils'; -import { Button } from '@documenso/ui/primitives/button'; + +import { cn } from '../../lib/utils'; +import { Button } from '../../primitives/button'; import { Dialog, DialogContent, @@ -22,8 +23,8 @@ import { DialogHeader, DialogTitle, DialogTrigger, -} from '@documenso/ui/primitives/dialog'; -import { useToast } from '@documenso/ui/primitives/use-toast'; +} from '../../primitives/dialog'; +import { useToast } from '../../primitives/use-toast'; export type DocumentShareButtonProps = HTMLAttributes & { token?: string; diff --git a/packages/ui/components/field/field-tooltip.tsx b/packages/ui/components/field/field-tooltip.tsx index 446b14d2d..3966e9c0c 100644 --- a/packages/ui/components/field/field-tooltip.tsx +++ b/packages/ui/components/field/field-tooltip.tsx @@ -1,17 +1,18 @@ import { TooltipArrow } from '@radix-ui/react-tooltip'; -import { VariantProps, cva } from 'class-variance-authority'; +import type { VariantProps } from 'class-variance-authority'; +import { cva } from 'class-variance-authority'; import { createPortal } from 'react-dom'; import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords'; -import { cn } from '@documenso/ui/lib/utils'; + +import { cn } from '../..//lib/utils'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, -} from '@documenso/ui/primitives/tooltip'; - -import { Field } from '.prisma/client'; +} from '../..//primitives/tooltip'; +import type { Field } from '.prisma/client'; const tooltipVariants = cva('font-semibold', { variants: { diff --git a/packages/ui/components/field/field.tsx b/packages/ui/components/field/field.tsx index 054cc6376..e40b2e3d9 100644 --- a/packages/ui/components/field/field.tsx +++ b/packages/ui/components/field/field.tsx @@ -5,9 +5,10 @@ import React, { useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords'; -import { Field } from '@documenso/prisma/client'; -import { cn } from '@documenso/ui/lib/utils'; -import { Card, CardContent } from '@documenso/ui/primitives/card'; +import type { Field } from '@documenso/prisma/client'; + +import { cn } from '../../lib/utils'; +import { Card, CardContent } from '../../primitives/card'; export type FieldRootContainerProps = { field: Field; diff --git a/packages/ui/components/signing-card.tsx b/packages/ui/components/signing-card.tsx index ab057c4e5..cda0c31c3 100644 --- a/packages/ui/components/signing-card.tsx +++ b/packages/ui/components/signing-card.tsx @@ -2,14 +2,16 @@ import { useCallback, useEffect, useRef, useState } from 'react'; -import Image, { StaticImageData } from 'next/image'; +import type { StaticImageData } from 'next/image'; +import Image from 'next/image'; import { animate, motion, useMotionTemplate, useMotionValue, useTransform } from 'framer-motion'; import { P, match } from 'ts-pattern'; -import { Signature } from '@documenso/prisma/client'; -import { cn } from '@documenso/ui/lib/utils'; -import { Card, CardContent } from '@documenso/ui/primitives/card'; +import type { Signature } from '@documenso/prisma/client'; + +import { cn } from '../lib/utils'; +import { Card, CardContent } from '../primitives/card'; export type SigningCardProps = { className?: string; diff --git a/packages/ui/primitives/combobox.tsx b/packages/ui/primitives/combobox.tsx index 899ccd61d..85f86056d 100644 --- a/packages/ui/primitives/combobox.tsx +++ b/packages/ui/primitives/combobox.tsx @@ -3,16 +3,11 @@ import * as React from 'react'; import { Check, ChevronsUpDown } from 'lucide-react'; import { Role } from '@documenso/prisma/client'; -import { cn } from '@documenso/ui/lib/utils'; -import { Button } from '@documenso/ui/primitives/button'; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, -} from '@documenso/ui/primitives/command'; -import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover'; + +import { cn } from '../lib/utils'; +import { Button } from './button'; +import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from './command'; +import { Popover, PopoverContent, PopoverTrigger } from './popover'; type ComboboxProps = { listValues: string[]; diff --git a/packages/ui/primitives/document-dropzone.tsx b/packages/ui/primitives/document-dropzone.tsx index 6987e9872..d81a3a7de 100644 --- a/packages/ui/primitives/document-dropzone.tsx +++ b/packages/ui/primitives/document-dropzone.tsx @@ -1,12 +1,14 @@ 'use client'; -import { Variants, motion } from 'framer-motion'; +import type { Variants } from 'framer-motion'; +import { motion } from 'framer-motion'; import { Plus } from 'lucide-react'; import { useDropzone } from 'react-dropzone'; import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions'; -import { cn } from '@documenso/ui/lib/utils'; -import { Card, CardContent } from '@documenso/ui/primitives/card'; + +import { cn } from '../lib/utils'; +import { Card, CardContent } from './card'; const DocumentDropzoneContainerVariants: Variants = { initial: { diff --git a/packages/ui/primitives/document-flow/add-fields.tsx b/packages/ui/primitives/document-flow/add-fields.tsx index 112f2f849..a8ae9f0e3 100644 --- a/packages/ui/primitives/document-flow/add-fields.tsx +++ b/packages/ui/primitives/document-flow/add-fields.tsx @@ -13,20 +13,14 @@ import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer'; import { nanoid } from '@documenso/lib/universal/id'; import type { Field, Recipient } from '@documenso/prisma/client'; import { FieldType, SendStatus } from '@documenso/prisma/client'; -import { cn } from '@documenso/ui/lib/utils'; -import { Button } from '@documenso/ui/primitives/button'; -import { Card, CardContent } from '@documenso/ui/primitives/card'; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, -} from '@documenso/ui/primitives/command'; -import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover'; -import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip'; +import { cn } from '../../lib/utils'; +import { Button } from '../button'; +import { Card, CardContent } from '../card'; +import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from '../command'; +import { Popover, PopoverContent, PopoverTrigger } from '../popover'; import { useStep } from '../stepper'; +import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip'; import type { TAddFieldsFormSchema } from './add-fields.types'; import { DocumentFlowFormContainerActions, diff --git a/packages/ui/primitives/document-flow/add-signature.tsx b/packages/ui/primitives/document-flow/add-signature.tsx index aed252083..e4e5d9253 100644 --- a/packages/ui/primitives/document-flow/add-signature.tsx +++ b/packages/ui/primitives/document-flow/add-signature.tsx @@ -9,35 +9,38 @@ import { match } from 'ts-pattern'; import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer'; import { sortFieldsByPosition, validateFieldsInserted } from '@documenso/lib/utils/fields'; -import { Field, FieldType } from '@documenso/prisma/client'; -import { FieldWithSignature } from '@documenso/prisma/types/field-with-signature'; -import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip'; -import { cn } from '@documenso/ui/lib/utils'; -import { Card, CardContent } from '@documenso/ui/primitives/card'; -import { TAddSignatureFormSchema } from '@documenso/ui/primitives/document-flow/add-signature.types'; +import type { Field } from '@documenso/prisma/client'; +import { FieldType } from '@documenso/prisma/client'; +import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature'; + +import { FieldToolTip } from '../../components/field/field-tooltip'; +import { cn } from '../../lib/utils'; +import { Card, CardContent } from '../card'; +import { ElementVisible } from '../element-visible'; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '../form/form'; +import { Input } from '../input'; +import { SignaturePad } from '../signature-pad'; +import { useStep } from '../stepper'; +import type { TAddSignatureFormSchema } from './add-signature.types'; +import { ZAddSignatureFormSchema } from './add-signature.types'; import { DocumentFlowFormContainerActions, DocumentFlowFormContainerContent, DocumentFlowFormContainerFooter, + DocumentFlowFormContainerHeader, DocumentFlowFormContainerStep, -} from '@documenso/ui/primitives/document-flow/document-flow-root'; -import { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types'; -import { ElementVisible } from '@documenso/ui/primitives/element-visible'; -import { Input } from '@documenso/ui/primitives/input'; -import { SignaturePad } from '@documenso/ui/primitives/signature-pad'; - -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '../form/form'; -import { ZAddSignatureFormSchema } from './add-signature.types'; +} from './document-flow-root'; import { SinglePlayerModeCustomTextField, SinglePlayerModeSignatureField, } from './single-player-mode-fields'; +import type { DocumentFlowStep } from './types'; export type AddSignatureFormProps = { defaultValues?: TAddSignatureFormSchema; documentFlow: DocumentFlowStep; fields: FieldWithSignature[]; - numberOfSteps: number; + onSubmit: (_data: TAddSignatureFormSchema) => Promise | void; requireName?: boolean; requireSignature?: boolean; @@ -47,11 +50,13 @@ export const AddSignatureFormPartial = ({ defaultValues, documentFlow, fields, - numberOfSteps, + onSubmit, requireName = false, requireSignature = true, }: AddSignatureFormProps) => { + const { currentStep, totalSteps } = useStep(); + const [validateUninsertedFields, setValidateUninsertedFields] = useState(false); // Refined schema which takes into account whether to allow an empty name or signature. @@ -206,46 +211,30 @@ export const AddSignatureFormPartial = ({ }; return ( -
-
- -
- ( - - Email - - { - onFormValueChange(FieldType.EMAIL); - field.onChange(value); - }} - /> - - - - )} - /> + <> + - {requireName && ( + +
+ +
( - Name + Email { - onFormValueChange(FieldType.NAME); + onFormValueChange(FieldType.EMAIL); field.onChange(value); }} /> @@ -254,91 +243,114 @@ export const AddSignatureFormPartial = ({ )} /> - )} - {requireSignature && ( - ( - - Signature - - - - { - onFormValueChange(FieldType.SIGNATURE); - field.onChange(value); - }} - /> - - - - - - )} - /> - )} -
-
+ {requireName && ( + ( + + Name + + { + onFormValueChange(FieldType.NAME); + field.onChange(value); + }} + /> + + + + )} + /> + )} - - + {requireSignature && ( + ( + + Signature + + + + { + onFormValueChange(FieldType.SIGNATURE); + field.onChange(value); + }} + /> + + + + + + )} + /> + )} +
+
- - -
+ + - {validateUninsertedFields && uninsertedFields[0] && ( - - Click to insert field - - )} + + + - - {localFields.map((field) => - match(field.type) - .with(FieldType.DATE, FieldType.EMAIL, FieldType.NAME, () => { - return ( - + Click to insert field + + )} + + + {localFields.map((field) => + match(field.type) + .with(FieldType.DATE, FieldType.EMAIL, FieldType.NAME, () => { + return ( + + ); + }) + .with(FieldType.SIGNATURE, () => ( + - ); - }) - .with(FieldType.SIGNATURE, () => ( - - )) - .otherwise(() => { - return null; - }), - )} - - + )) + .otherwise(() => { + return null; + }), + )} +
+ + ); }; diff --git a/packages/ui/primitives/document-flow/add-signers.tsx b/packages/ui/primitives/document-flow/add-signers.tsx index 13af03d26..71be1c069 100644 --- a/packages/ui/primitives/document-flow/add-signers.tsx +++ b/packages/ui/primitives/document-flow/add-signers.tsx @@ -12,13 +12,13 @@ import { nanoid } from '@documenso/lib/universal/id'; import type { Field, Recipient } from '@documenso/prisma/client'; 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 { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message'; -import { Input } from '@documenso/ui/primitives/input'; -import { Label } from '@documenso/ui/primitives/label'; -import { useToast } from '@documenso/ui/primitives/use-toast'; +import { Button } from '../button'; +import { FormErrorMessage } from '../form/form-error-message'; +import { Input } from '../input'; +import { Label } from '../label'; import { useStep } from '../stepper'; +import { useToast } from '../use-toast'; import type { TAddSignersFormSchema } from './add-signers.types'; import { ZAddSignersFormSchema } from './add-signers.types'; import { diff --git a/packages/ui/primitives/document-flow/add-subject.tsx b/packages/ui/primitives/document-flow/add-subject.tsx index e9e761af0..881d59c74 100644 --- a/packages/ui/primitives/document-flow/add-subject.tsx +++ b/packages/ui/primitives/document-flow/add-subject.tsx @@ -5,12 +5,12 @@ import { useForm } from 'react-hook-form'; import type { Field, Recipient } from '@documenso/prisma/client'; 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 { Input } from '@documenso/ui/primitives/input'; -import { Label } from '@documenso/ui/primitives/label'; -import { Textarea } from '@documenso/ui/primitives/textarea'; +import { FormErrorMessage } from '../form/form-error-message'; +import { Input } from '../input'; +import { Label } from '../label'; import { useStep } from '../stepper'; +import { Textarea } from '../textarea'; import type { TAddSubjectFormSchema } from './add-subject.types'; import { DocumentFlowFormContainerActions, diff --git a/packages/ui/primitives/document-flow/add-title.tsx b/packages/ui/primitives/document-flow/add-title.tsx index 2b91e1033..8c2a9dc7a 100644 --- a/packages/ui/primitives/document-flow/add-title.tsx +++ b/packages/ui/primitives/document-flow/add-title.tsx @@ -4,10 +4,10 @@ import { useForm } from 'react-hook-form'; import type { Field, Recipient } 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 { Input } from '@documenso/ui/primitives/input'; -import { Label } from '@documenso/ui/primitives/label'; +import { FormErrorMessage } from '../form/form-error-message'; +import { Input } from '../input'; +import { Label } from '../label'; import { useStep } from '../stepper'; import type { TAddTitleFormSchema } from './add-title.types'; import { diff --git a/packages/ui/primitives/document-flow/document-flow-root.tsx b/packages/ui/primitives/document-flow/document-flow-root.tsx index 9142f4258..42b70c58a 100644 --- a/packages/ui/primitives/document-flow/document-flow-root.tsx +++ b/packages/ui/primitives/document-flow/document-flow-root.tsx @@ -5,8 +5,8 @@ import React from 'react'; import { motion } from 'framer-motion'; -import { cn } from '@documenso/ui/lib/utils'; -import { Button } from '@documenso/ui/primitives/button'; +import { cn } from '../../lib/utils'; +import { Button } from '../button'; export type DocumentFlowFormContainerProps = HTMLAttributes & { children?: React.ReactNode; diff --git a/packages/ui/primitives/document-flow/field-item.tsx b/packages/ui/primitives/document-flow/field-item.tsx index 48e52b9a7..7583bd4b9 100644 --- a/packages/ui/primitives/document-flow/field-item.tsx +++ b/packages/ui/primitives/document-flow/field-item.tsx @@ -7,10 +7,11 @@ import { createPortal } from 'react-dom'; import { Rnd } from 'react-rnd'; import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer'; -import { cn } from '@documenso/ui/lib/utils'; -import { Card, CardContent } from '@documenso/ui/primitives/card'; -import { FRIENDLY_FIELD_TYPE, TDocumentFlowFormSchema } from './types'; +import { cn } from '../../lib/utils'; +import { Card, CardContent } from '../card'; +import type { TDocumentFlowFormSchema } from './types'; +import { FRIENDLY_FIELD_TYPE } from './types'; type Field = TDocumentFlowFormSchema['fields'][0]; diff --git a/packages/ui/primitives/document-flow/send-document-action-dialog.tsx b/packages/ui/primitives/document-flow/send-document-action-dialog.tsx index f295dadfc..a70282800 100644 --- a/packages/ui/primitives/document-flow/send-document-action-dialog.tsx +++ b/packages/ui/primitives/document-flow/send-document-action-dialog.tsx @@ -2,7 +2,8 @@ import { useState } from 'react'; import { Loader } from 'lucide-react'; -import { Button, ButtonProps } from '@documenso/ui/primitives/button'; +import type { ButtonProps } from '../button'; +import { Button } from '../button'; import { Dialog, DialogContent, @@ -11,7 +12,7 @@ import { DialogHeader, DialogTitle, DialogTrigger, -} from '@documenso/ui/primitives/dialog'; +} from '../dialog'; export type SendDocumentActionDialogProps = ButtonProps & { loading?: boolean; diff --git a/packages/ui/primitives/document-flow/single-player-mode-fields.tsx b/packages/ui/primitives/document-flow/single-player-mode-fields.tsx index 04c093efc..7cecd7131 100644 --- a/packages/ui/primitives/document-flow/single-player-mode-fields.tsx +++ b/packages/ui/primitives/document-flow/single-player-mode-fields.tsx @@ -13,9 +13,11 @@ import { MIN_HANDWRITING_FONT_SIZE, MIN_STANDARD_FONT_SIZE, } from '@documenso/lib/constants/pdf'; -import { Field, FieldType } from '@documenso/prisma/client'; -import { FieldWithSignature } from '@documenso/prisma/types/field-with-signature'; -import { FieldRootContainer } from '@documenso/ui/components/field/field'; +import type { Field } from '@documenso/prisma/client'; +import { FieldType } from '@documenso/prisma/client'; +import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature'; + +import { FieldRootContainer } from '../../components/field/field'; export type SinglePlayerModeFieldContainerProps = { field: FieldWithSignature; diff --git a/packages/ui/primitives/form/form-error-message.tsx b/packages/ui/primitives/form/form-error-message.tsx index bb555b7f7..e429799da 100644 --- a/packages/ui/primitives/form/form-error-message.tsx +++ b/packages/ui/primitives/form/form-error-message.tsx @@ -1,6 +1,6 @@ import { AnimatePresence, motion } from 'framer-motion'; -import { cn } from '@documenso/ui/lib/utils'; +import { cn } from '../../lib/utils'; export type FormErrorMessageProps = { className?: string; diff --git a/packages/ui/primitives/form/form.tsx b/packages/ui/primitives/form/form.tsx index 9467de3af..f500accae 100644 --- a/packages/ui/primitives/form/form.tsx +++ b/packages/ui/primitives/form/form.tsx @@ -1,19 +1,12 @@ import * as React from 'react'; -import * as LabelPrimitive from '@radix-ui/react-label'; +import type * as LabelPrimitive from '@radix-ui/react-label'; import { Slot } from '@radix-ui/react-slot'; import { AnimatePresence, motion } from 'framer-motion'; -import { - Controller, - ControllerProps, - FieldPath, - FieldValues, - FormProvider, - useFormContext, -} from 'react-hook-form'; - -import { cn } from '@documenso/ui/lib/utils'; +import type { ControllerProps, FieldPath, FieldValues } from 'react-hook-form'; +import { Controller, FormProvider, useFormContext } from 'react-hook-form'; +import { cn } from '../../lib/utils'; import { Label } from '../label'; const Form = FormProvider; diff --git a/packages/ui/primitives/pdf-viewer.tsx b/packages/ui/primitives/pdf-viewer.tsx index 62b08d2f9..07cdaf1e2 100644 --- a/packages/ui/primitives/pdf-viewer.tsx +++ b/packages/ui/primitives/pdf-viewer.tsx @@ -3,16 +3,16 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { Loader } from 'lucide-react'; -import { PDFDocumentProxy } from 'pdfjs-dist'; +import type { PDFDocumentProxy } from 'pdfjs-dist'; import { Document as PDFDocument, Page as PDFPage, pdfjs } from 'react-pdf'; import 'react-pdf/dist/esm/Page/AnnotationLayer.css'; import 'react-pdf/dist/esm/Page/TextLayer.css'; import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer'; import { getFile } from '@documenso/lib/universal/upload/get-file'; -import { DocumentData } from '@documenso/prisma/client'; -import { cn } from '@documenso/ui/lib/utils'; +import type { DocumentData } from '@documenso/prisma/client'; +import { cn } from '../lib/utils'; import { useToast } from './use-toast'; export type LoadedPDFDocument = PDFDocumentProxy; diff --git a/packages/ui/primitives/signature-pad/signature-pad.tsx b/packages/ui/primitives/signature-pad/signature-pad.tsx index 107627240..3497418d7 100644 --- a/packages/ui/primitives/signature-pad/signature-pad.tsx +++ b/packages/ui/primitives/signature-pad/signature-pad.tsx @@ -1,20 +1,12 @@ 'use client'; -import { - HTMLAttributes, - MouseEvent, - PointerEvent, - TouchEvent, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; +import type { HTMLAttributes, MouseEvent, PointerEvent, TouchEvent } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; -import { StrokeOptions, getStroke } from 'perfect-freehand'; - -import { cn } from '@documenso/ui/lib/utils'; +import type { StrokeOptions } from 'perfect-freehand'; +import { getStroke } from 'perfect-freehand'; +import { cn } from '../../lib/utils'; import { getSvgPathFromStroke } from './helper'; import { Point } from './point'; From cd6184406d133af049582fb8008bdba3344bb29b Mon Sep 17 00:00:00 2001 From: Mythie Date: Thu, 7 Dec 2023 15:08:16 +1100 Subject: [PATCH 10/10] chore: add e2e test for stepper --- .../e2e/pr-718-add-stepper-component.spec.ts | 75 +++++++++++++++++++ .../seed/pr-718-add-stepper-component.ts | 28 +++++++ 2 files changed, 103 insertions(+) create mode 100644 packages/app-tests/e2e/pr-718-add-stepper-component.spec.ts create mode 100644 packages/prisma/seed/pr-718-add-stepper-component.ts diff --git a/packages/app-tests/e2e/pr-718-add-stepper-component.spec.ts b/packages/app-tests/e2e/pr-718-add-stepper-component.spec.ts new file mode 100644 index 000000000..6e03979c0 --- /dev/null +++ b/packages/app-tests/e2e/pr-718-add-stepper-component.spec.ts @@ -0,0 +1,75 @@ +import { expect, test } from '@playwright/test'; +import path from 'node:path'; + +import { TEST_USER } from '@documenso/prisma/seed/pr-718-add-stepper-component'; + +test(`[PR-718]: should be able to create a document`, async ({ page }) => { + await page.goto('/signin'); + + const documentTitle = `example-${Date.now()}.pdf`; + + // Sign in + await page.getByLabel('Email').fill(TEST_USER.email); + await page.getByLabel('Password', { exact: true }).fill(TEST_USER.password); + await page.getByRole('button', { name: 'Sign In' }).click(); + + // Upload document + const [fileChooser] = await Promise.all([ + page.waitForEvent('filechooser'), + page.locator('input[type=file]').evaluate((e) => { + if (e instanceof HTMLInputElement) { + e.click(); + } + }), + ]); + + await fileChooser.setFiles(path.join(__dirname, '../../../assets/example.pdf')); + + // Wait to be redirected to the edit page + await page.waitForURL(/\/documents\/\d+/); + + // Set title + await expect(page.getByRole('heading', { name: 'Add Title' })).toBeVisible(); + + await page.getByLabel('Title').fill(documentTitle); + + await page.getByRole('button', { name: 'Continue' }).click(); + + // Add signers + await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible(); + + await page.getByLabel('Email*').fill('user1@example.com'); + await page.getByLabel('Name').fill('User 1'); + + await page.getByRole('button', { name: 'Continue' }).click(); + + // Add fields + await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible(); + + await page.getByRole('button', { name: 'User 1 Signature' }).click(); + await page.locator('canvas').click({ + position: { + x: 100, + y: 100, + }, + }); + + await page.getByRole('button', { name: 'Email Email' }).click(); + await page.locator('canvas').click({ + position: { + x: 100, + y: 200, + }, + }); + + await page.getByRole('button', { name: 'Continue' }).click(); + + // Add subject and send + await expect(page.getByRole('heading', { name: 'Add Subject' })).toBeVisible(); + await page.getByRole('button', { name: 'Send' }).click(); + + await page.waitForURL('/documents'); + + // Assert document was created + await expect(page.getByRole('link', { name: documentTitle })).toBeVisible(); +}); diff --git a/packages/prisma/seed/pr-718-add-stepper-component.ts b/packages/prisma/seed/pr-718-add-stepper-component.ts new file mode 100644 index 000000000..57a0ddc61 --- /dev/null +++ b/packages/prisma/seed/pr-718-add-stepper-component.ts @@ -0,0 +1,28 @@ +import { hashSync } from '@documenso/lib/server-only/auth/hash'; + +import { prisma } from '..'; + +// +// https://github.com/documenso/documenso/pull/713 +// + +const PULL_REQUEST_NUMBER = 718; + +const EMAIL_DOMAIN = `pr-${PULL_REQUEST_NUMBER}.documenso.com`; + +export const TEST_USER = { + name: 'User 1', + email: `user1@${EMAIL_DOMAIN}`, + password: 'Password123', +} as const; + +export const seedDatabase = async () => { + await prisma.user.create({ + data: { + name: TEST_USER.name, + email: TEST_USER.email, + password: hashSync(TEST_USER.password), + emailVerified: new Date(), + }, + }); +};