mirror of
https://github.com/vercel/commerce.git
synced 2025-03-14 14:42:31 +00:00
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:
parent
0603b342be
commit
0e7e7b7d5f
6
.eslintrc
Normal file
6
.eslintrc
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": ["next", "prettier"],
|
||||
"rules": {
|
||||
"react/no-unescaped-entities": "off"
|
||||
}
|
||||
}
|
@ -70,6 +70,9 @@ const CartItem = ({
|
||||
if (item.quantity !== Number(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])
|
||||
|
||||
return (
|
||||
|
@ -73,7 +73,7 @@ const Footer: FC<Props> = ({ className, pages }) => {
|
||||
<div className="flex items-center text-primary text-sm">
|
||||
<span className="text-primary">Created by</span>
|
||||
<a
|
||||
rel="noopener"
|
||||
rel="noopener noreferrer"
|
||||
href="https://vercel.com"
|
||||
aria-label="Vercel.com Link"
|
||||
target="_blank"
|
||||
|
@ -24,7 +24,7 @@ const Loading = () => (
|
||||
)
|
||||
|
||||
const dynamicProps = {
|
||||
loading: () => <Loading />,
|
||||
loading: Loading,
|
||||
}
|
||||
|
||||
const SignUpView = dynamic(
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FC, InputHTMLAttributes, useEffect, useMemo } from 'react'
|
||||
import { FC, memo, useEffect } from 'react'
|
||||
import cn from 'classnames'
|
||||
import s from './Searchbar.module.css'
|
||||
import { useRouter } from 'next/router'
|
||||
@ -13,7 +13,7 @@ const Searchbar: FC<Props> = ({ className, id = 'search' }) => {
|
||||
|
||||
useEffect(() => {
|
||||
router.prefetch('/search')
|
||||
}, [])
|
||||
}, [router])
|
||||
|
||||
const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
e.preventDefault()
|
||||
@ -32,32 +32,29 @@ const Searchbar: FC<Props> = ({ className, id = 'search' }) => {
|
||||
}
|
||||
}
|
||||
|
||||
return useMemo(
|
||||
() => (
|
||||
<div className={cn(s.root, className)}>
|
||||
<label className="hidden" htmlFor={id}>
|
||||
Search
|
||||
</label>
|
||||
<input
|
||||
id={id}
|
||||
className={s.input}
|
||||
placeholder="Search for products..."
|
||||
defaultValue={router.query.q}
|
||||
onKeyUp={handleKeyUp}
|
||||
/>
|
||||
<div className={s.iconContainer}>
|
||||
<svg className={s.icon} fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fillRule="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"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
return (
|
||||
<div className={cn(s.root, className)}>
|
||||
<label className="hidden" htmlFor={id}>
|
||||
Search
|
||||
</label>
|
||||
<input
|
||||
id={id}
|
||||
className={s.input}
|
||||
placeholder="Search for products..."
|
||||
defaultValue={router.query.q}
|
||||
onKeyUp={handleKeyUp}
|
||||
/>
|
||||
<div className={s.iconContainer}>
|
||||
<svg className={s.icon} fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fillRule="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"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
),
|
||||
[]
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Searchbar
|
||||
export default memo(Searchbar)
|
||||
|
@ -1,50 +1,52 @@
|
||||
import { memo } from 'react'
|
||||
import { Swatch } from '@components/product'
|
||||
import type { ProductOption } from '@commerce/types/product'
|
||||
import { SelectedOptions } from '../helpers'
|
||||
import React from 'react'
|
||||
|
||||
interface ProductOptionsProps {
|
||||
options: ProductOption[]
|
||||
selectedOptions: SelectedOptions
|
||||
setSelectedOptions: React.Dispatch<React.SetStateAction<SelectedOptions>>
|
||||
}
|
||||
|
||||
const ProductOptions: React.FC<ProductOptionsProps> = React.memo(
|
||||
({ options, selectedOptions, setSelectedOptions }) => {
|
||||
return (
|
||||
<div>
|
||||
{options.map((opt) => (
|
||||
<div className="pb-4" key={opt.displayName}>
|
||||
<h2 className="uppercase font-medium text-sm tracking-wide">
|
||||
{opt.displayName}
|
||||
</h2>
|
||||
<div className="flex flex-row py-4">
|
||||
{opt.values.map((v, i: number) => {
|
||||
const active = selectedOptions[opt.displayName.toLowerCase()]
|
||||
return (
|
||||
<Swatch
|
||||
key={`${opt.id}-${i}`}
|
||||
active={v.label.toLowerCase() === active}
|
||||
variant={opt.displayName}
|
||||
color={v.hexColors ? v.hexColors[0] : ''}
|
||||
label={v.label}
|
||||
onClick={() => {
|
||||
setSelectedOptions((selectedOptions) => {
|
||||
return {
|
||||
...selectedOptions,
|
||||
[opt.displayName.toLowerCase()]:
|
||||
v.label.toLowerCase(),
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
const ProductOptions: React.FC<ProductOptionsProps> = ({
|
||||
options,
|
||||
selectedOptions,
|
||||
setSelectedOptions,
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
{options.map((opt) => (
|
||||
<div className="pb-4" key={opt.displayName}>
|
||||
<h2 className="uppercase font-medium text-sm tracking-wide">
|
||||
{opt.displayName}
|
||||
</h2>
|
||||
<div className="flex flex-row py-4">
|
||||
{opt.values.map((v, i: number) => {
|
||||
const active = selectedOptions[opt.displayName.toLowerCase()]
|
||||
return (
|
||||
<Swatch
|
||||
key={`${opt.id}-${i}`}
|
||||
active={v.label.toLowerCase() === active}
|
||||
variant={opt.displayName}
|
||||
color={v.hexColors ? v.hexColors[0] : ''}
|
||||
label={v.label}
|
||||
onClick={() => {
|
||||
setSelectedOptions((selectedOptions) => {
|
||||
return {
|
||||
...selectedOptions,
|
||||
[opt.displayName.toLowerCase()]: v.label.toLowerCase(),
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProductOptions
|
||||
export default memo(ProductOptions)
|
||||
|
@ -23,7 +23,7 @@ const ProductSidebar: FC<ProductSidebarProps> = ({ product, className }) => {
|
||||
|
||||
useEffect(() => {
|
||||
selectDefaultOptionFromProduct(product, setSelectedOptions)
|
||||
}, [])
|
||||
}, [product])
|
||||
|
||||
const variant = getProductVariant(product, selectedOptions)
|
||||
const addToCart = async () => {
|
||||
|
@ -66,17 +66,13 @@ const ProductSlider: React.FC<ProductSliderProps> = ({
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
sliderContainerRef.current!.addEventListener(
|
||||
'touchstart',
|
||||
preventNavigation
|
||||
)
|
||||
const slider = sliderContainerRef.current!
|
||||
|
||||
slider.addEventListener('touchstart', preventNavigation)
|
||||
|
||||
return () => {
|
||||
if (sliderContainerRef.current) {
|
||||
sliderContainerRef.current!.removeEventListener(
|
||||
'touchstart',
|
||||
preventNavigation
|
||||
)
|
||||
if (slider) {
|
||||
slider.removeEventListener('touchstart', preventNavigation)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
@ -1,31 +1,30 @@
|
||||
import { FC, MouseEventHandler, memo } from 'react'
|
||||
import cn from 'classnames'
|
||||
import React from 'react'
|
||||
import s from './ProductSliderControl.module.css'
|
||||
import { ArrowLeft, ArrowRight } from '@components/icons'
|
||||
|
||||
interface ProductSliderControl {
|
||||
onPrev: React.MouseEventHandler<HTMLButtonElement>
|
||||
onNext: React.MouseEventHandler<HTMLButtonElement>
|
||||
onPrev: MouseEventHandler<HTMLButtonElement>
|
||||
onNext: MouseEventHandler<HTMLButtonElement>
|
||||
}
|
||||
|
||||
const ProductSliderControl: React.FC<ProductSliderControl> = React.memo(
|
||||
({ onPrev, onNext }) => (
|
||||
<div className={s.control}>
|
||||
<button
|
||||
className={cn(s.leftControl)}
|
||||
onClick={onPrev}
|
||||
aria-label="Previous Product Image"
|
||||
>
|
||||
<ArrowLeft />
|
||||
</button>
|
||||
<button
|
||||
className={cn(s.rightControl)}
|
||||
onClick={onNext}
|
||||
aria-label="Next Product Image"
|
||||
>
|
||||
<ArrowRight />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
const ProductSliderControl: FC<ProductSliderControl> = ({ onPrev, onNext }) => (
|
||||
<div className={s.control}>
|
||||
<button
|
||||
className={cn(s.leftControl)}
|
||||
onClick={onPrev}
|
||||
aria-label="Previous Product Image"
|
||||
>
|
||||
<ArrowLeft />
|
||||
</button>
|
||||
<button
|
||||
className={cn(s.rightControl)}
|
||||
onClick={onNext}
|
||||
aria-label="Next Product Image"
|
||||
>
|
||||
<ArrowRight />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
export default ProductSliderControl
|
||||
|
||||
export default memo(ProductSliderControl)
|
||||
|
@ -7,7 +7,7 @@ import { useRouter } from 'next/router'
|
||||
import { Layout } from '@components/common'
|
||||
import { ProductCard } from '@components/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'
|
||||
|
||||
|
@ -27,13 +27,15 @@ const Modal: FC<ModalProps> = ({ children, onClose }) => {
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
disableBodyScroll(ref.current, { reserveScrollBarGap: true })
|
||||
const modal = ref.current
|
||||
|
||||
if (modal) {
|
||||
disableBodyScroll(modal, { reserveScrollBarGap: true })
|
||||
window.addEventListener('keydown', handleKey)
|
||||
}
|
||||
return () => {
|
||||
if (ref && ref.current) {
|
||||
enableBodyScroll(ref.current)
|
||||
if (modal) {
|
||||
enableBodyScroll(modal)
|
||||
}
|
||||
clearAllBodyScrollLocks()
|
||||
window.removeEventListener('keydown', handleKey)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { FC } from 'react'
|
||||
import { FC, memo } from 'react'
|
||||
import rangeMap from '@lib/range-map'
|
||||
import { Star } from '@components/icons'
|
||||
import cn from 'classnames'
|
||||
@ -7,21 +7,19 @@ export interface RatingProps {
|
||||
value: number
|
||||
}
|
||||
|
||||
const Quantity: React.FC<RatingProps> = React.memo(({ value = 5 }) => {
|
||||
return (
|
||||
<div className="flex flex-row py-6 text-accent-9">
|
||||
{rangeMap(5, (i) => (
|
||||
<span
|
||||
key={`star_${i}`}
|
||||
className={cn('inline-block ml-1 ', {
|
||||
'text-accent-5': i >= Math.floor(value),
|
||||
})}
|
||||
>
|
||||
<Star />
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
const Quantity: FC<RatingProps> = ({ value = 5 }) => (
|
||||
<div className="flex flex-row py-6 text-accent-9">
|
||||
{rangeMap(5, (i) => (
|
||||
<span
|
||||
key={`star_${i}`}
|
||||
className={cn('inline-block ml-1 ', {
|
||||
'text-accent-5': i >= Math.floor(value),
|
||||
})}
|
||||
>
|
||||
<Star />
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
||||
export default Quantity
|
||||
export default memo(Quantity)
|
||||
|
@ -16,13 +16,14 @@ const Sidebar: FC<SidebarProps> = ({ children, onClose }) => {
|
||||
const ref = useRef() as React.MutableRefObject<HTMLDivElement>
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
disableBodyScroll(ref.current, { reserveScrollBarGap: true })
|
||||
const sidebar = ref.current
|
||||
|
||||
if (sidebar) {
|
||||
disableBodyScroll(sidebar, { reserveScrollBarGap: true })
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (ref && ref.current) {
|
||||
enableBodyScroll(ref.current)
|
||||
}
|
||||
if (sidebar) enableBodyScroll(sidebar)
|
||||
clearAllBodyScrollLocks()
|
||||
}
|
||||
}, [])
|
||||
|
@ -55,10 +55,13 @@ export default function FocusTrap({ children, focusFirst = false }: Props) {
|
||||
}
|
||||
}, [root, children])
|
||||
|
||||
return React.createElement('div', {
|
||||
ref: root,
|
||||
children,
|
||||
className: 'outline-none focus-trap',
|
||||
tabIndex: -1,
|
||||
})
|
||||
return React.createElement(
|
||||
'div',
|
||||
{
|
||||
ref: root,
|
||||
className: 'outline-none focus-trap',
|
||||
tabIndex: -1,
|
||||
},
|
||||
children
|
||||
)
|
||||
}
|
||||
|
@ -18,10 +18,10 @@ export function useSearchMeta(asPath: string) {
|
||||
c = parts[4]
|
||||
}
|
||||
|
||||
setPathname(path)
|
||||
if (path !== pathname) setPathname(path)
|
||||
if (c !== category) setCategory(c)
|
||||
if (b !== brand) setBrand(b)
|
||||
}, [asPath])
|
||||
}, [asPath, pathname, category, brand])
|
||||
|
||||
return { pathname, category, brand }
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"analyze": "BUNDLE_ANALYZE=both yarn build",
|
||||
"lint": "next lint",
|
||||
"prettier-fix": "prettier --write .",
|
||||
"find:unused": "npx next-unused",
|
||||
"generate": "graphql-codegen",
|
||||
@ -63,6 +64,9 @@
|
||||
"@types/node": "^15.12.4",
|
||||
"@types/react": "^17.0.8",
|
||||
"deepmerge": "^4.2.2",
|
||||
"eslint": "^7.31.0",
|
||||
"eslint-config-next": "^11.0.1",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"graphql": "^15.5.1",
|
||||
"husky": "^6.0.0",
|
||||
"lint-staged": "^11.0.0",
|
||||
@ -78,6 +82,7 @@
|
||||
},
|
||||
"lint-staged": {
|
||||
"**/*.{js,jsx,ts,tsx}": [
|
||||
"eslint",
|
||||
"prettier --write",
|
||||
"git add"
|
||||
],
|
||||
|
Loading…
x
Reference in New Issue
Block a user