4
0
forked from crowetic/commerce

Add Next.js ESLint (#425)

* Added Next.js eslint

* added eslint to lint-staged

* Added eslint config for prettier

* Fixed eslint issues in multiple files

* Fixed error in linter
This commit is contained in:
Luis Alvarez D 2021-08-02 21:54:58 -05:00 committed by GitHub
parent 0603b342be
commit 0e7e7b7d5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1119 additions and 153 deletions

6
.eslintrc Normal file
View File

@ -0,0 +1,6 @@
{
"extends": ["next", "prettier"],
"rules": {
"react/no-unescaped-entities": "off"
}
}

View File

@ -70,6 +70,9 @@ const CartItem = ({
if (item.quantity !== Number(quantity)) { if (item.quantity !== Number(quantity)) {
setQuantity(item.quantity) setQuantity(item.quantity)
} }
// TODO: currently not including quantity in deps is intended, but we should
// do this differently as it could break easily
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [item.quantity]) }, [item.quantity])
return ( return (

View File

@ -73,7 +73,7 @@ const Footer: FC<Props> = ({ className, pages }) => {
<div className="flex items-center text-primary text-sm"> <div className="flex items-center text-primary text-sm">
<span className="text-primary">Created by</span> <span className="text-primary">Created by</span>
<a <a
rel="noopener" rel="noopener noreferrer"
href="https://vercel.com" href="https://vercel.com"
aria-label="Vercel.com Link" aria-label="Vercel.com Link"
target="_blank" target="_blank"

View File

@ -24,7 +24,7 @@ const Loading = () => (
) )
const dynamicProps = { const dynamicProps = {
loading: () => <Loading />, loading: Loading,
} }
const SignUpView = dynamic( const SignUpView = dynamic(

View File

@ -1,4 +1,4 @@
import { FC, InputHTMLAttributes, useEffect, useMemo } from 'react' import { FC, memo, useEffect } from 'react'
import cn from 'classnames' import cn from 'classnames'
import s from './Searchbar.module.css' import s from './Searchbar.module.css'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
@ -13,7 +13,7 @@ const Searchbar: FC<Props> = ({ className, id = 'search' }) => {
useEffect(() => { useEffect(() => {
router.prefetch('/search') router.prefetch('/search')
}, []) }, [router])
const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => { const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
e.preventDefault() e.preventDefault()
@ -32,32 +32,29 @@ const Searchbar: FC<Props> = ({ className, id = 'search' }) => {
} }
} }
return useMemo( return (
() => ( <div className={cn(s.root, className)}>
<div className={cn(s.root, className)}> <label className="hidden" htmlFor={id}>
<label className="hidden" htmlFor={id}> Search
Search </label>
</label> <input
<input id={id}
id={id} className={s.input}
className={s.input} placeholder="Search for products..."
placeholder="Search for products..." defaultValue={router.query.q}
defaultValue={router.query.q} onKeyUp={handleKeyUp}
onKeyUp={handleKeyUp} />
/> <div className={s.iconContainer}>
<div className={s.iconContainer}> <svg className={s.icon} fill="currentColor" viewBox="0 0 20 20">
<svg className={s.icon} fill="currentColor" viewBox="0 0 20 20"> <path
<path fillRule="evenodd"
fillRule="evenodd" clipRule="evenodd"
clipRule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" />
/> </svg>
</svg>
</div>
</div> </div>
), </div>
[]
) )
} }
export default Searchbar export default memo(Searchbar)

View File

@ -1,50 +1,52 @@
import { memo } from 'react'
import { Swatch } from '@components/product' import { Swatch } from '@components/product'
import type { ProductOption } from '@commerce/types/product' import type { ProductOption } from '@commerce/types/product'
import { SelectedOptions } from '../helpers' import { SelectedOptions } from '../helpers'
import React from 'react'
interface ProductOptionsProps { interface ProductOptionsProps {
options: ProductOption[] options: ProductOption[]
selectedOptions: SelectedOptions selectedOptions: SelectedOptions
setSelectedOptions: React.Dispatch<React.SetStateAction<SelectedOptions>> setSelectedOptions: React.Dispatch<React.SetStateAction<SelectedOptions>>
} }
const ProductOptions: React.FC<ProductOptionsProps> = React.memo( const ProductOptions: React.FC<ProductOptionsProps> = ({
({ options, selectedOptions, setSelectedOptions }) => { options,
return ( selectedOptions,
<div> setSelectedOptions,
{options.map((opt) => ( }) => {
<div className="pb-4" key={opt.displayName}> return (
<h2 className="uppercase font-medium text-sm tracking-wide"> <div>
{opt.displayName} {options.map((opt) => (
</h2> <div className="pb-4" key={opt.displayName}>
<div className="flex flex-row py-4"> <h2 className="uppercase font-medium text-sm tracking-wide">
{opt.values.map((v, i: number) => { {opt.displayName}
const active = selectedOptions[opt.displayName.toLowerCase()] </h2>
return ( <div className="flex flex-row py-4">
<Swatch {opt.values.map((v, i: number) => {
key={`${opt.id}-${i}`} const active = selectedOptions[opt.displayName.toLowerCase()]
active={v.label.toLowerCase() === active} return (
variant={opt.displayName} <Swatch
color={v.hexColors ? v.hexColors[0] : ''} key={`${opt.id}-${i}`}
label={v.label} active={v.label.toLowerCase() === active}
onClick={() => { variant={opt.displayName}
setSelectedOptions((selectedOptions) => { color={v.hexColors ? v.hexColors[0] : ''}
return { label={v.label}
...selectedOptions, onClick={() => {
[opt.displayName.toLowerCase()]: setSelectedOptions((selectedOptions) => {
v.label.toLowerCase(), return {
} ...selectedOptions,
}) [opt.displayName.toLowerCase()]: v.label.toLowerCase(),
}} }
/> })
) }}
})} />
</div> )
})}
</div> </div>
))} </div>
</div> ))}
) </div>
} )
) }
export default ProductOptions export default memo(ProductOptions)

View File

@ -23,7 +23,7 @@ const ProductSidebar: FC<ProductSidebarProps> = ({ product, className }) => {
useEffect(() => { useEffect(() => {
selectDefaultOptionFromProduct(product, setSelectedOptions) selectDefaultOptionFromProduct(product, setSelectedOptions)
}, []) }, [product])
const variant = getProductVariant(product, selectedOptions) const variant = getProductVariant(product, selectedOptions)
const addToCart = async () => { const addToCart = async () => {

View File

@ -66,17 +66,13 @@ const ProductSlider: React.FC<ProductSliderProps> = ({
event.preventDefault() event.preventDefault()
} }
sliderContainerRef.current!.addEventListener( const slider = sliderContainerRef.current!
'touchstart',
preventNavigation slider.addEventListener('touchstart', preventNavigation)
)
return () => { return () => {
if (sliderContainerRef.current) { if (slider) {
sliderContainerRef.current!.removeEventListener( slider.removeEventListener('touchstart', preventNavigation)
'touchstart',
preventNavigation
)
} }
} }
}, []) }, [])

View File

@ -1,31 +1,30 @@
import { FC, MouseEventHandler, memo } from 'react'
import cn from 'classnames' import cn from 'classnames'
import React from 'react'
import s from './ProductSliderControl.module.css' import s from './ProductSliderControl.module.css'
import { ArrowLeft, ArrowRight } from '@components/icons' import { ArrowLeft, ArrowRight } from '@components/icons'
interface ProductSliderControl { interface ProductSliderControl {
onPrev: React.MouseEventHandler<HTMLButtonElement> onPrev: MouseEventHandler<HTMLButtonElement>
onNext: React.MouseEventHandler<HTMLButtonElement> onNext: MouseEventHandler<HTMLButtonElement>
} }
const ProductSliderControl: React.FC<ProductSliderControl> = React.memo( const ProductSliderControl: FC<ProductSliderControl> = ({ onPrev, onNext }) => (
({ onPrev, onNext }) => ( <div className={s.control}>
<div className={s.control}> <button
<button className={cn(s.leftControl)}
className={cn(s.leftControl)} onClick={onPrev}
onClick={onPrev} aria-label="Previous Product Image"
aria-label="Previous Product Image" >
> <ArrowLeft />
<ArrowLeft /> </button>
</button> <button
<button className={cn(s.rightControl)}
className={cn(s.rightControl)} onClick={onNext}
onClick={onNext} aria-label="Next Product Image"
aria-label="Next Product Image" >
> <ArrowRight />
<ArrowRight /> </button>
</button> </div>
</div>
)
) )
export default ProductSliderControl
export default memo(ProductSliderControl)

View File

@ -7,7 +7,7 @@ import { useRouter } from 'next/router'
import { Layout } from '@components/common' import { Layout } from '@components/common'
import { ProductCard } from '@components/product' import { ProductCard } from '@components/product'
import type { Product } from '@commerce/types/product' import type { Product } from '@commerce/types/product'
import { Container, Grid, Skeleton } from '@components/ui' import { Container, Skeleton } from '@components/ui'
import useSearch from '@framework/product/use-search' import useSearch from '@framework/product/use-search'

View File

@ -27,13 +27,15 @@ const Modal: FC<ModalProps> = ({ children, onClose }) => {
) )
useEffect(() => { useEffect(() => {
if (ref.current) { const modal = ref.current
disableBodyScroll(ref.current, { reserveScrollBarGap: true })
if (modal) {
disableBodyScroll(modal, { reserveScrollBarGap: true })
window.addEventListener('keydown', handleKey) window.addEventListener('keydown', handleKey)
} }
return () => { return () => {
if (ref && ref.current) { if (modal) {
enableBodyScroll(ref.current) enableBodyScroll(modal)
} }
clearAllBodyScrollLocks() clearAllBodyScrollLocks()
window.removeEventListener('keydown', handleKey) window.removeEventListener('keydown', handleKey)

View File

@ -1,4 +1,4 @@
import React, { FC } from 'react' import { FC, memo } from 'react'
import rangeMap from '@lib/range-map' import rangeMap from '@lib/range-map'
import { Star } from '@components/icons' import { Star } from '@components/icons'
import cn from 'classnames' import cn from 'classnames'
@ -7,21 +7,19 @@ export interface RatingProps {
value: number value: number
} }
const Quantity: React.FC<RatingProps> = React.memo(({ value = 5 }) => { const Quantity: FC<RatingProps> = ({ value = 5 }) => (
return ( <div className="flex flex-row py-6 text-accent-9">
<div className="flex flex-row py-6 text-accent-9"> {rangeMap(5, (i) => (
{rangeMap(5, (i) => ( <span
<span key={`star_${i}`}
key={`star_${i}`} className={cn('inline-block ml-1 ', {
className={cn('inline-block ml-1 ', { 'text-accent-5': i >= Math.floor(value),
'text-accent-5': i >= Math.floor(value), })}
})} >
> <Star />
<Star /> </span>
</span> ))}
))} </div>
</div> )
)
})
export default Quantity export default memo(Quantity)

View File

@ -16,13 +16,14 @@ const Sidebar: FC<SidebarProps> = ({ children, onClose }) => {
const ref = useRef() as React.MutableRefObject<HTMLDivElement> const ref = useRef() as React.MutableRefObject<HTMLDivElement>
useEffect(() => { useEffect(() => {
if (ref.current) { const sidebar = ref.current
disableBodyScroll(ref.current, { reserveScrollBarGap: true })
if (sidebar) {
disableBodyScroll(sidebar, { reserveScrollBarGap: true })
} }
return () => { return () => {
if (ref && ref.current) { if (sidebar) enableBodyScroll(sidebar)
enableBodyScroll(ref.current)
}
clearAllBodyScrollLocks() clearAllBodyScrollLocks()
} }
}, []) }, [])

View File

@ -55,10 +55,13 @@ export default function FocusTrap({ children, focusFirst = false }: Props) {
} }
}, [root, children]) }, [root, children])
return React.createElement('div', { return React.createElement(
ref: root, 'div',
children, {
className: 'outline-none focus-trap', ref: root,
tabIndex: -1, className: 'outline-none focus-trap',
}) tabIndex: -1,
},
children
)
} }

View File

@ -18,10 +18,10 @@ export function useSearchMeta(asPath: string) {
c = parts[4] c = parts[4]
} }
setPathname(path) if (path !== pathname) setPathname(path)
if (c !== category) setCategory(c) if (c !== category) setCategory(c)
if (b !== brand) setBrand(b) if (b !== brand) setBrand(b)
}, [asPath]) }, [asPath, pathname, category, brand])
return { pathname, category, brand } return { pathname, category, brand }
} }

View File

@ -6,6 +6,7 @@
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"analyze": "BUNDLE_ANALYZE=both yarn build", "analyze": "BUNDLE_ANALYZE=both yarn build",
"lint": "next lint",
"prettier-fix": "prettier --write .", "prettier-fix": "prettier --write .",
"find:unused": "npx next-unused", "find:unused": "npx next-unused",
"generate": "graphql-codegen", "generate": "graphql-codegen",
@ -63,6 +64,9 @@
"@types/node": "^15.12.4", "@types/node": "^15.12.4",
"@types/react": "^17.0.8", "@types/react": "^17.0.8",
"deepmerge": "^4.2.2", "deepmerge": "^4.2.2",
"eslint": "^7.31.0",
"eslint-config-next": "^11.0.1",
"eslint-config-prettier": "^8.3.0",
"graphql": "^15.5.1", "graphql": "^15.5.1",
"husky": "^6.0.0", "husky": "^6.0.0",
"lint-staged": "^11.0.0", "lint-staged": "^11.0.0",
@ -78,6 +82,7 @@
}, },
"lint-staged": { "lint-staged": {
"**/*.{js,jsx,ts,tsx}": [ "**/*.{js,jsx,ts,tsx}": [
"eslint",
"prettier --write", "prettier --write",
"git add" "git add"
], ],

986
yarn.lock

File diff suppressed because it is too large Load Diff