mirror of
https://github.com/graphql-hive/console
synced 2026-05-23 09:08:34 +00:00
(1) Drop V2: Create Collection Modal (#5310)
This commit is contained in:
parent
4d89c7596e
commit
38aa3ff159
2 changed files with 263 additions and 126 deletions
|
|
@ -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<typeof createCollectionModalFormSchema>;
|
||||
|
||||
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<CreateCollectionModalFormValues>({
|
||||
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 (
|
||||
<Modal open={isOpen} onOpenChange={toggleModalOpen}>
|
||||
{!fetching && (
|
||||
<form className="flex flex-col gap-8" onSubmit={handleSubmit}>
|
||||
<Heading className="text-center">
|
||||
{collectionId ? 'Update' : 'Create'} Shared Collection
|
||||
</Heading>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-sm font-semibold" htmlFor="name">
|
||||
Collection Name
|
||||
</label>
|
||||
<Input
|
||||
data-cy="input.name"
|
||||
name="name"
|
||||
placeholder="My Collection"
|
||||
value={values.name}
|
||||
onChange={handleChange}
|
||||
isInvalid={!!(touched.name && errors.name)}
|
||||
/>
|
||||
{touched.name && errors.name && (
|
||||
<div className="text-sm text-red-500">{errors.name}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-sm font-semibold" htmlFor="description">
|
||||
Collection Description
|
||||
</label>
|
||||
|
||||
<Input
|
||||
data-cy="input.description"
|
||||
name="description"
|
||||
value={values.description}
|
||||
onChange={handleChange}
|
||||
isInvalid={!!(touched.description && errors.description)}
|
||||
/>
|
||||
{touched.description && errors.description && (
|
||||
<div className="text-sm text-red-500">{errors.description}</div>
|
||||
)}
|
||||
<Callout type="info" className="mt-0">
|
||||
This collection will be available to everyone in the organization
|
||||
</Callout>
|
||||
</div>
|
||||
|
||||
{error && <div className="text-sm text-red-500">{error.message}</div>}
|
||||
|
||||
<div className="flex w-full gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
size="lg"
|
||||
className="w-full justify-center"
|
||||
onClick={toggleModalOpen}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
size="lg"
|
||||
className="w-full justify-center"
|
||||
variant="primary"
|
||||
disabled={isSubmitting}
|
||||
data-cy="confirm"
|
||||
>
|
||||
{collectionId ? 'Update' : 'Add'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</Modal>
|
||||
<CreateCollectionModalContent
|
||||
isOpen={isOpen}
|
||||
toggleModalOpen={toggleModalOpen}
|
||||
onSubmit={onSubmit}
|
||||
form={form}
|
||||
collectionId={collectionId}
|
||||
fetching={fetching}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function CreateCollectionModalContent(props: {
|
||||
isOpen: boolean;
|
||||
toggleModalOpen: () => void;
|
||||
onSubmit: (values: CreateCollectionModalFormValues) => void;
|
||||
form: UseFormReturn<CreateCollectionModalFormValues>;
|
||||
collectionId?: string;
|
||||
fetching: boolean;
|
||||
}) {
|
||||
return (
|
||||
<Dialog open={props.isOpen} onOpenChange={props.toggleModalOpen}>
|
||||
<DialogContent className="container w-4/5 max-w-[600px] md:w-3/5">
|
||||
{!props.fetching && (
|
||||
<Form {...props.form}>
|
||||
<form className="space-y-8" onSubmit={props.form.handleSubmit(props.onSubmit)}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{props.collectionId ? 'Update' : 'Create'} Shared Collection
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{props.collectionId
|
||||
? 'Update the shared collection name and description'
|
||||
: 'Create a shared collection that everyone in the organization can access'}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-8">
|
||||
<FormField
|
||||
control={props.form.control}
|
||||
name="name"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>Collection Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="My Collection" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<FormField
|
||||
control={props.form.control}
|
||||
name="description"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>Collection Description</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="My Collection" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Callout type="info" className="mt-0">
|
||||
This collection will be available to everyone in the organization
|
||||
</Callout>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="button"
|
||||
size="lg"
|
||||
className="w-full justify-center"
|
||||
onClick={ev => {
|
||||
ev.preventDefault();
|
||||
props.toggleModalOpen();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
size="lg"
|
||||
className="w-full justify-center"
|
||||
variant="primary"
|
||||
disabled={props.form.formState.isSubmitting || !props.form.formState.isValid}
|
||||
data-cy="confirm"
|
||||
>
|
||||
{props.collectionId ? 'Update' : 'Add'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<typeof CreateCollectionModalContent> = {
|
||||
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<typeof CreateCollectionModalContent>;
|
||||
|
||||
const Template: StoryFn<typeof CreateCollectionModalContent> = args => {
|
||||
const form = useForm<CreateCollectionModalFormValues>({
|
||||
mode: 'onChange',
|
||||
resolver: zodResolver(createCollectionModalFormSchema),
|
||||
defaultValues: {
|
||||
name: '',
|
||||
description: '',
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!args.collectionId) {
|
||||
form.reset();
|
||||
} else {
|
||||
void form.setValue('name', args.collectionId);
|
||||
}
|
||||
}, [args.collectionId]);
|
||||
|
||||
return <CreateCollectionModalContent {...args} form={form} />;
|
||||
};
|
||||
|
||||
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),
|
||||
};
|
||||
Loading…
Reference in a new issue