Created
January 20, 2020 17:39
-
-
Save SvitlanaShepitsena/462fdc99ff7a581a5ae858f55c930bb2 to your computer and use it in GitHub Desktop.
ProductForm written with React hooks, using apollo hooks and graphql codegenerator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as faker from 'faker' | |
import gql from 'graphql-tag' | |
import { SingletonRouter, useRouter } from 'next/router' | |
import pluralize from 'pluralize' | |
import React, { useEffect, useState } from 'react' | |
import { useForm } from 'react-hook-form' | |
import Select from 'react-select' | |
import { | |
Form, | |
Grid, | |
Message, | |
Segment, | |
TextArea, | |
TextAreaProps, | |
} from 'semantic-ui-react' | |
import styled from 'styled-components' | |
// @ts-ignore | |
import urlSlug from 'url-slug' | |
import * as yup from 'yup' | |
import { | |
ProductUpdateInput, | |
useBrandsQuery, | |
useCategoriesQuery, | |
useCreateProductMutation, | |
useDepartmentsQuery, | |
useMaterialsQuery, | |
useProductTypesQuery, | |
useUpdateProductMutation, | |
} from '../../../generated/apollo-components' | |
import withUser from '../../../lib/withUser' | |
import CancelSubmitButtons from '../../common/buttons/CancelSubmitButtons' | |
require('dotenv').config() | |
const ProductSchema = yup.object().shape({ | |
name: yup.string().required(), | |
description: yup.string().required(), | |
key: yup.string().required(), | |
}) | |
type FormData = { | |
brand: { id: string; value: string } | |
department: { id: string; value: string } | |
productType: { id: string; value: string } | |
category: { id: string; value: string } | |
material: { id: string; value: string } | |
upc: string | |
style: string | |
key: string | |
tags: string | |
features: string | |
name: string | |
description: string | |
} | |
type Props = { | |
id: String | |
product?: any | |
onCancel?: () => void | |
router: SingletonRouter | |
} | |
type SelectProps = { | |
label: string | |
error: any | |
loading: boolean | |
pluralKey: string | |
preSelectedValue?: string | |
name: string | |
setValue?: any | |
data: any | |
register?: any | |
handleChange?: any | |
} | |
function getDatum(data: any, pluralKey: string) { | |
try { | |
return data[`${pluralKey}`] | |
} catch (error) { | |
let singleKey = pluralize.singular(pluralKey) | |
return data[`${singleKey}`] | |
} | |
} | |
const SvSelect = (props: SelectProps) => { | |
const router = useRouter() | |
const { | |
label, | |
error, | |
loading, | |
data, | |
preSelectedValue, | |
pluralKey, | |
name, | |
register, | |
setValue, | |
} = props | |
const [value, setReactSelect] = useState({ | |
selectedOption: [], | |
}) | |
useEffect(() => { | |
if (!data) { | |
return | |
} | |
let newData = getDatum(data, pluralKey).map((e: any) => ({ | |
label: e.value, | |
value: e.id, | |
})) | |
// @ts-ignore | |
if (typeof register === 'function') { | |
register({ name }) | |
} | |
const selectedOption = defaultSelected(newData, preSelectedValue) | |
if (typeof setValue === 'function') { | |
setValue(name, selectedOption) | |
} | |
// @ts-ignore | |
setReactSelect({ selectedOption }) | |
}, [data]) | |
const handleMultiChange = (selectedOption: any) => { | |
// @ts-ignore | |
setReactSelect({ selectedOption }) | |
setValue(name, selectedOption) | |
} | |
return ( | |
<div> | |
<StyledLabel>{label}</StyledLabel> | |
{error ? ( | |
<div>Error Loading {label}</div> | |
) : loading ? ( | |
<div>Loading {label}</div> | |
) : ( | |
data && | |
getDatum(data, pluralKey) && ( | |
<Select | |
placeholder={`Select a ${label}`} | |
options={getDatum(data, pluralKey).map((e: any) => ({ | |
label: e.value, | |
value: e.id, | |
}))} | |
value={value.selectedOption} | |
onChange={handleMultiChange} | |
name={`${name}`} | |
/> | |
) | |
)} | |
</div> | |
) | |
} | |
function defaultSelected(newData: any, preSelected: string | undefined) { | |
if (!preSelected || preSelected.length == 0) { | |
return newData[0] | |
} | |
for (let i = 0; i < newData.length; i++) { | |
let selectedElement = newData[i] | |
if (selectedElement.value && selectedElement.value === preSelected) { | |
return selectedElement | |
} | |
} | |
} | |
function SvInput(props: { | |
defaultValue?: string | |
label: string | |
errors: any | |
handleChange: any | |
placeholder: string | |
name: string | |
}) { | |
const { | |
placeholder, | |
name, | |
errors, | |
label, | |
handleChange, | |
defaultValue, | |
} = props | |
let errorMessage = errors[name] | |
return ( | |
<> | |
<StyledLabel>{label}</StyledLabel> | |
<Form.Input | |
defaultValue={defaultValue ? defaultValue : ''} | |
placeholder={placeholder} | |
name={name} | |
onChange={handleChange} | |
/> | |
{errors && errorMessage && ( | |
<Message negative>{errorMessage.message}</Message> | |
)} | |
</> | |
) | |
} | |
function SvTextArea(props: { | |
defaultValue: string | |
label: string | |
errors: any | |
handleChange: any | |
placeholder: string | |
name: string | |
}) { | |
const { | |
placeholder, | |
name, | |
errors, | |
label, | |
handleChange, | |
defaultValue, | |
} = props | |
const [val, setVal] = useState(defaultValue) | |
const onChange = ( | |
e: React.FormEvent<HTMLTextAreaElement>, | |
data: TextAreaProps | |
) => { | |
handleChange(e, data) | |
// @ts-ignore | |
setVal(data.value) | |
} | |
let errorMessage = errors[name] | |
return ( | |
<> | |
<StyledLabel>{label}</StyledLabel> | |
<TextArea | |
rows={5} | |
value={val} | |
placeholder={placeholder} | |
name={name} | |
onChange={( | |
e: React.FormEvent<HTMLTextAreaElement>, | |
data: TextAreaProps | |
) => { | |
onChange(e, data) | |
}} | |
/> | |
{errors && errorMessage && ( | |
<Message negative>{errorMessage.message}</Message> | |
)} | |
</> | |
) | |
} | |
const ProductForm = (props: Props) => { | |
const router = useRouter() | |
const [createProduct, { data, error, loading }] = useCreateProductMutation() | |
const [ | |
updateProduct, | |
{ data: dataUpdate, error: errorUpdate, loading: loadingUpdate }, | |
] = useUpdateProductMutation() | |
const [values, setReactSelect] = useState({ | |
selectedOption: [], | |
}) | |
const handleSelectChange = (selectedOption: any) => { | |
setValue('reactSelect', selectedOption) | |
setReactSelect({ selectedOption }) | |
} | |
const { | |
data: dataDep, | |
error: errDep, | |
loading: loadDep, | |
} = useDepartmentsQuery() | |
const { | |
data: dataProductType, | |
error: errProductType, | |
loading: loadProductType, | |
} = useProductTypesQuery() | |
const { | |
data: dataCategory, | |
error: errCategory, | |
loading: loadCategory, | |
} = useCategoriesQuery() | |
const { | |
data: dataBrand, | |
error: errBrand, | |
loading: loadBrand, | |
} = useBrandsQuery() | |
const { | |
data: dataMaterial, | |
error: errMaterial, | |
loading: loadMaterial, | |
} = useMaterialsQuery() | |
const { product } = props | |
function generateProduct(product: any) { | |
if (product && product.id) { | |
return { ...product } | |
} | |
const defaultValues = { | |
name: faker.commerce.productName(), | |
description: faker.lorem.sentences(12), | |
upc: faker.random.uuid(), | |
key: faker.random.uuid().substr(0, 4), | |
style: faker.commerce.productAdjective(), | |
features: 'Features', | |
tags: `${faker.hacker.noun()}, ${faker.hacker.noun()}, ${faker.hacker.noun()}`, | |
} | |
return defaultValues | |
} | |
const defaultValues = generateProduct(product) | |
const { register, setValue, handleSubmit, errors } = useForm<FormData>({ | |
validationSchema: ProductSchema, | |
defaultValues: defaultValues, | |
}) | |
const PRODUCTS = gql` | |
query($orderBy: ProductOrderByInput, $where: String) { | |
products(orderBy: $orderBy, where: $where) { | |
id | |
name | |
slug | |
description | |
key | |
upc | |
style | |
tags | |
features | |
createdAt | |
department { | |
id | |
value | |
} | |
category { | |
id | |
value | |
} | |
material { | |
id | |
value | |
} | |
brand { | |
id | |
value | |
} | |
productType { | |
id | |
value | |
} | |
} | |
} | |
` | |
const onSubmit = async (data: FormData) => { | |
console.log('Hello from ProductForm' + 'ProductForm + 363') | |
console.log( | |
'product' + JSON.stringify(product, null, 2) + 'ProductForm + 364' | |
) | |
// @ts-ignore | |
let newData: ProductUpdateInput = { ...data } | |
newData.department = { connect: { id: data.department.value } } | |
newData.brand = { connect: { id: data.brand.value } } | |
newData.productType = { connect: { id: data.productType.value } } | |
newData.category = { connect: { id: data.category.value } } | |
newData.material = { connect: { id: data.material.value } } | |
newData.slug = urlSlug(newData.name) | |
let res | |
try { | |
if (product && product.id) { | |
// Update | |
res = await updateProduct({ | |
variables: { | |
where: { id: product.id }, | |
data: { ...newData }, | |
}, | |
}) | |
} else { | |
// Create | |
// @ts-ignore | |
res = await createProduct({ | |
// @ts-ignore | |
variables: { ...newData }, | |
update: (cache, { data }) => { | |
// @ts-ignore | |
const { products } = cache.readQuery({ | |
query: PRODUCTS, | |
variables: { | |
orderBy: { createdAt: 'desc' }, | |
where: { | |
OR: [ | |
{ name: {} }, | |
{ key: {} }, | |
{ upc: {} }, | |
], | |
}, | |
}, | |
}) | |
const newArray: any = [data?.createProduct, ...products] | |
cache.writeQuery({ | |
query: PRODUCTS, | |
variables: { | |
orderBy: { createdAt: 'desc' }, | |
where: { | |
OR: [ | |
{ name: {} }, | |
{ key: {} }, | |
{ upc: {} }, | |
], | |
}, | |
}, | |
data: { | |
products: [...newArray], | |
}, | |
}) | |
}, | |
}) | |
} | |
await router.push('/ecom/products') | |
} catch (error) { | |
console.log('error', error) | |
} | |
} | |
const handleChange = (e: any, data: any) => { | |
setValue(e.target.name, e.target.value) | |
} | |
useEffect(() => { | |
register({ name: 'name' }) | |
register({ name: 'brand' }) | |
register({ name: 'upc' }) | |
register({ name: 'department' }) | |
register({ name: 'category' }) | |
register({ name: 'style' }) | |
register({ name: 'features' }) | |
register({ name: 'material' }) | |
register({ name: 'productType' }) | |
register({ name: 'key' }) | |
register({ name: 'tags' }) | |
register({ name: 'description' }) | |
}, []) | |
return ( | |
<> | |
<StyledHeader as="h1">Create a Product</StyledHeader> | |
<StyledForm onSubmit={handleSubmit(onSubmit)}> | |
<SegmentStyled raised> | |
<Grid stackable columns={3}> | |
<Grid.Column> | |
<SvInput | |
label="* Name" | |
defaultValue={defaultValues['name']} | |
name="name" | |
placeholder="Enter a Product Name" | |
handleChange={handleChange} | |
errors={errors} | |
/> | |
</Grid.Column> | |
<Grid.Column> | |
<SvSelect | |
preSelectedValue={product?.brand.id} | |
register={register} | |
setValue={setValue} | |
error={errBrand} | |
loading={loadBrand} | |
data={dataBrand} | |
label="Brand" | |
name="brand" | |
pluralKey="brands" | |
/> | |
</Grid.Column> | |
<Grid.Column> | |
<SvInput | |
label="Unique Product Number" | |
name="upc" | |
defaultValue={defaultValues['upc']} | |
placeholder="Enter a UPC number" | |
handleChange={handleChange} | |
errors={errors} | |
/> | |
</Grid.Column> | |
</Grid> | |
</SegmentStyled> | |
<SegmentStyled raised> | |
<Grid stackable columns={3}> | |
<Grid.Column> | |
<SvSelect | |
preSelectedValue={product?.productType.id} | |
register={register} | |
setValue={setValue} | |
error={errProductType} | |
loading={loadProductType} | |
data={dataProductType} | |
label="Product Type (Shoes, Clothes, Jewelry)" | |
pluralKey="productTypes" | |
name="productType" | |
handleChange={handleSelectChange} | |
/> | |
</Grid.Column> | |
<Grid.Column> | |
<SvSelect | |
preSelectedValue={product?.category.id} | |
error={errCategory} | |
loading={loadCategory} | |
data={dataCategory} | |
register={register} | |
setValue={setValue} | |
label="Category (Dress, Coat, Boots etc)" | |
pluralKey="categories" | |
name="category" | |
handleChange={handleSelectChange} | |
/> | |
</Grid.Column> | |
<Grid.Column> | |
<SvSelect | |
preSelectedValue={product?.department.id} | |
error={errDep} | |
data={dataDep} | |
loading={loadDep} | |
register={register} | |
setValue={setValue} | |
label="Department (Baby, Mans Shoes etc)" | |
pluralKey="departments" | |
name="department" | |
handleChange={handleSelectChange} | |
/> | |
</Grid.Column> | |
</Grid> | |
</SegmentStyled> | |
<SegmentStyled raised> | |
<Grid stackable columns={3}> | |
<Grid.Column> | |
<SvInput | |
label="Style (Cocktail dress, classic)" | |
name="style" | |
defaultValue={defaultValues['style']} | |
placeholder="Enter a product style" | |
handleChange={handleChange} | |
errors={errors} | |
/> | |
</Grid.Column> | |
<Grid.Column> | |
<SvInput | |
label="Features (Hooded, 3/4 sleeves)" | |
name="features" | |
defaultValue={defaultValues['features']} | |
placeholder="Enter main product features" | |
handleChange={handleChange} | |
errors={errors} | |
/> | |
</Grid.Column> | |
<Grid.Column> | |
<SvSelect | |
preSelectedValue={product?.material.id} | |
error={errMaterial} | |
data={dataMaterial} | |
loading={loadMaterial} | |
register={register} | |
setValue={setValue} | |
label="Material" | |
pluralKey="materials" | |
name="material" | |
handleChange={handleSelectChange} | |
/> | |
</Grid.Column> | |
</Grid> | |
<br /> | |
<Grid stackable columns={1}> | |
<Grid.Column> | |
<SvTextArea | |
label="* Description" | |
defaultValue={defaultValues['description']} | |
name="description" | |
placeholder="Enter a product description" | |
handleChange={handleChange} | |
errors={errors} | |
/> | |
</Grid.Column> | |
<Grid.Column> | |
<SvInput | |
label="Tags" | |
name="tags" | |
defaultValue={defaultValues['tags']} | |
placeholder="Enter a product feature" | |
handleChange={handleChange} | |
errors={errors} | |
/> | |
</Grid.Column> | |
<Grid.Column> | |
<SvInput | |
label="Key" | |
name="key" | |
defaultValue={defaultValues['key']} | |
placeholder="Enter a product key" | |
handleChange={handleChange} | |
errors={errors} | |
/> | |
</Grid.Column> | |
</Grid> | |
</SegmentStyled> | |
<br /> | |
<CancelSubmitButtons onClick={props.onCancel} /> | |
</StyledForm> | |
</> | |
) | |
} | |
export default withUser(ProductForm) | |
const StyledForm = styled(Form)` | |
&&& { | |
font-size: 16px; | |
} | |
` | |
const StyledLabel = styled.label` | |
&&& { | |
font-size: 16px; | |
display: block; | |
margin-bottom: 5px !important; | |
} | |
` | |
const StyledHeader = styled.label` | |
&&& { | |
color: #595959; | |
} | |
` | |
const FixedNav = styled.div` | |
&&& { | |
position: fixed !important; | |
@media only screen and (min-width: 815px) { | |
// background: rgba(0, 0, 0, 0.87); | |
border-bottom-right-radius: 4px; | |
padding: 7px; | |
top: 0px; | |
left: 600px; | |
z-index: 102 !important; | |
max-width: 220px; | |
} | |
@media (max-width: 814px) { | |
top: 10px; | |
right: 10px; | |
} | |
} | |
` | |
const SegmentStyled = styled(Segment)` | |
&&& { | |
@media only screen and (min-width: 815px) { | |
margin-bottom: 28px; | |
} | |
@media (max-width: 814px) { | |
padding-left: 0 !important; | |
padding-right: 0 !important; | |
} | |
} | |
` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment