From 38aa3ff1598b0a2bda5b222047f9813d2a6866d4 Mon Sep 17 00:00:00 2001 From: Tuval Simha Date: Sun, 18 Aug 2024 18:25:23 +0300 Subject: [PATCH] (1) Drop V2: Create Collection Modal (#5310) --- .../laboratory/create-collection-modal.tsx | 301 ++++++++++-------- .../create-collection-modal.stories.tsx | 88 +++++ 2 files changed, 263 insertions(+), 126 deletions(-) create mode 100644 packages/web/app/src/stories/create-collection-modal.stories.tsx diff --git a/packages/web/app/src/components/target/laboratory/create-collection-modal.tsx b/packages/web/app/src/components/target/laboratory/create-collection-modal.tsx index cd2c8bd83..6888e6832 100644 --- a/packages/web/app/src/components/target/laboratory/create-collection-modal.tsx +++ b/packages/web/app/src/components/target/laboratory/create-collection-modal.tsx @@ -1,11 +1,28 @@ import { ReactElement, useEffect } from 'react'; -import { useFormik } from 'formik'; +import { useForm, UseFormReturn } from 'react-hook-form'; import { useMutation, useQuery } from 'urql'; -import * as Yup from 'yup'; +import { z } from 'zod'; import { Button } from '@/components/ui/button'; -import { Heading } from '@/components/ui/heading'; -import { Callout, Input, Modal } from '@/components/v2'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { Callout } from '@/components/v2'; import { graphql } from '@/gql'; +import { zodResolver } from '@hookform/resolvers/zod'; const CollectionQuery = graphql(` query Collection($selector: TargetSelectorInput!, $id: ID!) { @@ -103,6 +120,22 @@ const UpdateCollectionMutation = graphql(` } `); +const createCollectionModalFormSchema = z.object({ + name: z + .string({ + required_error: 'Collection name is required', + }) + .min(2, { + message: 'Collection name must be at least 2 characters long', + }) + .max(50, { + message: 'Collection name must be at most 50 characters long', + }), + description: z.string().optional(), +}); + +export type CreateCollectionModalFormValues = z.infer; + export function CreateCollectionModal(props: { isOpen: boolean; toggleModalOpen: () => void; @@ -128,140 +161,156 @@ export function CreateCollectionModal(props: { pause: !collectionId, }); - const error = mutationCreate.error || collectionError || mutationUpdate.error; + const errorCombined = mutationCreate.error || collectionError || mutationUpdate.error; const fetching = loadingCollection; + const form = useForm({ + mode: 'onChange', + resolver: zodResolver(createCollectionModalFormSchema), + defaultValues: { + name: '', + description: '', + }, + }); + useEffect(() => { if (!collectionId) { - resetForm(); + form.reset(); } else if (data) { const { documentCollection } = data.target!; - if (documentCollection) { - void setValues({ - name: documentCollection.name, - description: documentCollection.description || '', - }); + void form.setValue('name', documentCollection.name); + void form.setValue('description', documentCollection.description || ''); } } }, [data, collectionId]); - const { - handleSubmit, - values, - handleChange, - errors, - touched, - isSubmitting, - setValues, - resetForm, - } = useFormik({ - initialValues: { - name: '', - description: '', - }, - validationSchema: Yup.object().shape({ - name: Yup.string().required(), - description: Yup.string(), - }), - async onSubmit(values) { - const { error } = collectionId - ? await mutateUpdate({ - selector: { - target: props.targetId, - organization: props.organizationId, - project: props.projectId, - }, - input: { - collectionId, - name: values.name, - description: values.description, - }, - }) - : await mutateCreate({ - selector: { - target: props.targetId, - organization: props.organizationId, - project: props.projectId, - }, - input: values, - }); - if (!error) { - resetForm(); - toggleModalOpen(); - } - }, - }); + async function onSubmit(values: CreateCollectionModalFormValues) { + const { error } = collectionId + ? await mutateUpdate({ + selector: { + target: props.targetId, + organization: props.organizationId, + project: props.projectId, + }, + input: { + collectionId, + name: values.name, + description: values.description, + }, + }) + : await mutateCreate({ + selector: { + target: props.targetId, + organization: props.organizationId, + project: props.projectId, + }, + input: values, + }); + if (!error || errorCombined) { + form.reset(); + toggleModalOpen(); + } + } return ( - - {!fetching && ( -
- - {collectionId ? 'Update' : 'Create'} Shared Collection - - -
- - - {touched.name && errors.name && ( -
{errors.name}
- )} -
- -
- - - - {touched.description && errors.description && ( -
{errors.description}
- )} - - This collection will be available to everyone in the organization - -
- - {error &&
{error.message}
} - -
- - -
-
- )} -
+ + ); +} + +export function CreateCollectionModalContent(props: { + isOpen: boolean; + toggleModalOpen: () => void; + onSubmit: (values: CreateCollectionModalFormValues) => void; + form: UseFormReturn; + collectionId?: string; + fetching: boolean; +}) { + return ( + + + {!props.fetching && ( +
+ + + + {props.collectionId ? 'Update' : 'Create'} Shared Collection + + + {props.collectionId + ? 'Update the shared collection name and description' + : 'Create a shared collection that everyone in the organization can access'} + + +
+ { + return ( + + Collection Name + + + + + + ); + }} + /> + { + return ( + + Collection Description + + + + + + ); + }} + /> +
+ + This collection will be available to everyone in the organization + + + + + +
+ + )} +
+
); } diff --git a/packages/web/app/src/stories/create-collection-modal.stories.tsx b/packages/web/app/src/stories/create-collection-modal.stories.tsx new file mode 100644 index 000000000..86dd4383c --- /dev/null +++ b/packages/web/app/src/stories/create-collection-modal.stories.tsx @@ -0,0 +1,88 @@ +import { useEffect } from 'react'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { + CreateCollectionModalContent, + CreateCollectionModalFormValues, +} from '@/components/target/laboratory/create-collection-modal'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { Meta, StoryFn, StoryObj } from '@storybook/react'; + +const createCollectionModalFormSchema = z.object({ + name: z + .string({ + required_error: 'Collection name is required', + }) + .min(2, { + message: 'Collection name must be at least 2 characters long', + }) + .max(50, { + message: 'Collection name must be at most 50 characters long', + }), + description: z.string().optional(), +}); + +const meta: Meta = { + title: 'Modals/Create Collection Modal', + component: CreateCollectionModalContent, + argTypes: { + collectionId: { + control: { + type: 'text', + }, + }, + fetching: { + control: { + type: 'boolean', + }, + }, + isOpen: { + control: { + type: 'boolean', + }, + }, + }, +}; + +export default meta; + +type Story = StoryObj; + +const Template: StoryFn = args => { + const form = useForm({ + mode: 'onChange', + resolver: zodResolver(createCollectionModalFormSchema), + defaultValues: { + name: '', + description: '', + }, + }); + + useEffect(() => { + if (!args.collectionId) { + form.reset(); + } else { + void form.setValue('name', args.collectionId); + } + }, [args.collectionId]); + + return ; +}; + +export const Create: Story = Template.bind({}); +Create.args = { + collectionId: '', + fetching: false, + isOpen: true, + toggleModalOpen: () => console.log('Toggle Modal Open'), + onSubmit: data => console.log('Submit', data), +}; + +export const Update: Story = Template.bind({}); +Update.args = { + collectionId: 'collectionId', + fetching: false, + isOpen: true, + toggleModalOpen: () => console.log('Toggle Modal Open'), + onSubmit: data => console.log('Submit', data), +};