mirror of
https://github.com/vercel/commerce.git
synced 2025-07-04 12:11:22 +00:00
Merge remote-tracking branch 'origin-vercel/main' into spree-framework-poc
This commit is contained in:
commit
0ad4361369
@ -20,3 +20,6 @@ NEXT_PUBLIC_SWELL_PUBLIC_KEY=
|
|||||||
|
|
||||||
NEXT_PUBLIC_SALEOR_API_URL=
|
NEXT_PUBLIC_SALEOR_API_URL=
|
||||||
NEXT_PUBLIC_SALEOR_CHANNEL=
|
NEXT_PUBLIC_SALEOR_CHANNEL=
|
||||||
|
|
||||||
|
NEXT_PUBLIC_VENDURE_SHOP_API_URL=
|
||||||
|
NEXT_PUBLIC_VENDURE_LOCAL_URL=
|
||||||
|
6
.eslintrc
Normal file
6
.eslintrc
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": ["next", "prettier"],
|
||||||
|
"rules": {
|
||||||
|
"react/no-unescaped-entities": "off"
|
||||||
|
}
|
||||||
|
}
|
@ -151,5 +151,5 @@ Next, you're free to customize the starter. More updates coming soon. Stay tuned
|
|||||||
After Email confirmation, Checkout should be manually enabled through BigCommerce platform. Look for "Review & test your store" section through BigCommerce's dashboard.
|
After Email confirmation, Checkout should be manually enabled through BigCommerce platform. Look for "Review & test your store" section through BigCommerce's dashboard.
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
BigCommerce team has been notified and they plan to add more detailed about this subject.
|
BigCommerce team has been notified and they plan to add more details about this subject.
|
||||||
</details>
|
</details>
|
||||||
|
@ -77,7 +77,6 @@ html {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
font-feature-settings: 'case' 1, 'rlig' 1, 'calt' 0;
|
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
@ -38,6 +38,7 @@ const LoginView: FC<Props> = () => {
|
|||||||
} catch ({ errors }) {
|
} catch ({ errors }) {
|
||||||
setMessage(errors[0].message)
|
setMessage(errors[0].message)
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
setDisabled(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 (
|
||||||
|
@ -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"
|
||||||
|
@ -24,7 +24,7 @@ const Loading = () => (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const dynamicProps = {
|
const dynamicProps = {
|
||||||
loading: () => <Loading />,
|
loading: Loading,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SignUpView = dynamic(
|
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 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,8 +32,7 @@ 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
|
||||||
@ -55,9 +54,7 @@ const Searchbar: FC<Props> = ({ className, id = 'search' }) => {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
|
||||||
[]
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Searchbar
|
export default memo(Searchbar)
|
||||||
|
@ -7,6 +7,7 @@ import Image, { ImageProps } from 'next/image'
|
|||||||
import WishlistButton from '@components/wishlist/WishlistButton'
|
import WishlistButton from '@components/wishlist/WishlistButton'
|
||||||
import usePrice from '@framework/product/use-price'
|
import usePrice from '@framework/product/use-price'
|
||||||
import ProductTag from '../ProductTag'
|
import ProductTag from '../ProductTag'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string
|
className?: string
|
||||||
product: Product
|
product: Product
|
||||||
@ -23,7 +24,6 @@ const ProductCard: FC<Props> = ({
|
|||||||
className,
|
className,
|
||||||
noNameTag = false,
|
noNameTag = false,
|
||||||
variant = 'default',
|
variant = 'default',
|
||||||
...props
|
|
||||||
}) => {
|
}) => {
|
||||||
const { price } = usePrice({
|
const { price } = usePrice({
|
||||||
amount: product.price.value,
|
amount: product.price.value,
|
||||||
@ -38,7 +38,7 @@ const ProductCard: FC<Props> = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link href={`/product/${product.slug}`} {...props}>
|
<Link href={`/product/${product.slug}`}>
|
||||||
<a className={rootClassName}>
|
<a className={rootClassName}>
|
||||||
{variant === 'slim' && (
|
{variant === 'slim' && (
|
||||||
<>
|
<>
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
|
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,
|
||||||
|
selectedOptions,
|
||||||
|
setSelectedOptions,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{options.map((opt) => (
|
{options.map((opt) => (
|
||||||
@ -31,8 +35,7 @@ const ProductOptions: React.FC<ProductOptionsProps> = React.memo(
|
|||||||
setSelectedOptions((selectedOptions) => {
|
setSelectedOptions((selectedOptions) => {
|
||||||
return {
|
return {
|
||||||
...selectedOptions,
|
...selectedOptions,
|
||||||
[opt.displayName.toLowerCase()]:
|
[opt.displayName.toLowerCase()]: v.label.toLowerCase(),
|
||||||
v.label.toLowerCase(),
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
@ -44,7 +47,6 @@ const ProductOptions: React.FC<ProductOptionsProps> = React.memo(
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
export default ProductOptions
|
export default memo(ProductOptions)
|
||||||
|
@ -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 () => {
|
||||||
|
@ -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
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
|
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)}
|
||||||
@ -26,6 +25,6 @@ const ProductSliderControl: React.FC<ProductSliderControl> = React.memo(
|
|||||||
<ArrowRight />
|
<ArrowRight />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
|
||||||
)
|
)
|
||||||
export default ProductSliderControl
|
|
||||||
|
export default memo(ProductSliderControl)
|
||||||
|
@ -7,19 +7,19 @@ 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'
|
||||||
|
|
||||||
import getSlug from '@lib/get-slug'
|
import getSlug from '@lib/get-slug'
|
||||||
import rangeMap from '@lib/range-map'
|
import rangeMap from '@lib/range-map'
|
||||||
|
|
||||||
const SORT = Object.entries({
|
const SORT = {
|
||||||
'trending-desc': 'Trending',
|
'trending-desc': 'Trending',
|
||||||
'latest-desc': 'Latest arrivals',
|
'latest-desc': 'Latest arrivals',
|
||||||
'price-asc': 'Price: Low to high',
|
'price-asc': 'Price: Low to high',
|
||||||
'price-desc': 'Price: High to low',
|
'price-desc': 'Price: High to low',
|
||||||
})
|
}
|
||||||
|
|
||||||
import {
|
import {
|
||||||
filterQuery,
|
filterQuery,
|
||||||
@ -351,7 +351,7 @@ export default function Search({ categories, brands }: SearchPropsType) {
|
|||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
aria-expanded="true"
|
aria-expanded="true"
|
||||||
>
|
>
|
||||||
{sort ? `Sort: ${sort}` : 'Relevance'}
|
{sort ? SORT[sort as keyof typeof SORT] : 'Relevance'}
|
||||||
<svg
|
<svg
|
||||||
className="-mr-1 ml-2 h-5 w-5"
|
className="-mr-1 ml-2 h-5 w-5"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -398,7 +398,7 @@ export default function Search({ categories, brands }: SearchPropsType) {
|
|||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
{SORT.map(([key, text]) => (
|
{Object.entries(SORT).map(([key, text]) => (
|
||||||
<li
|
<li
|
||||||
key={key}
|
key={key}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
@ -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)
|
||||||
|
@ -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,8 +7,7 @@ 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
|
||||||
@ -21,7 +20,6 @@ const Quantity: React.FC<RatingProps> = React.memo(({ value = 5 }) => {
|
|||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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>
|
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()
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
@ -10,7 +10,7 @@ type BCCartItemBody = {
|
|||||||
product_id: number
|
product_id: number
|
||||||
variant_id: number
|
variant_id: number
|
||||||
quantity?: number
|
quantity?: number
|
||||||
option_selections?: OptionSelections
|
option_selections?: OptionSelections[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const parseWishlistItem = (
|
export const parseWishlistItem = (
|
||||||
|
@ -16,7 +16,7 @@ export const handler: MutationHook<LoginHook> = {
|
|||||||
if (!(email && password)) {
|
if (!(email && password)) {
|
||||||
throw new CommerceError({
|
throw new CommerceError({
|
||||||
message:
|
message:
|
||||||
'A first name, last name, email and password are required to login',
|
'An email and password are required to login',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ function normalizeProductOption(productOption: any) {
|
|||||||
const {
|
const {
|
||||||
node: {
|
node: {
|
||||||
entityId,
|
entityId,
|
||||||
values: { edges },
|
values: { edges = [] } = {},
|
||||||
...rest
|
...rest
|
||||||
},
|
},
|
||||||
} = productOption
|
} = productOption
|
||||||
|
@ -22,7 +22,7 @@ export const handler: SWRHook<SearchProductsHook> = {
|
|||||||
const url = new URL(options.url!, 'http://a')
|
const url = new URL(options.url!, 'http://a')
|
||||||
|
|
||||||
if (search) url.searchParams.set('search', search)
|
if (search) url.searchParams.set('search', search)
|
||||||
if (Number.isInteger(categoryId))
|
if (Number.isInteger(Number(categoryId)))
|
||||||
url.searchParams.set('categoryId', String(categoryId))
|
url.searchParams.set('categoryId', String(categoryId))
|
||||||
if (Number.isInteger(brandId))
|
if (Number.isInteger(brandId))
|
||||||
url.searchParams.set('brandId', String(brandId))
|
url.searchParams.set('brandId', String(brandId))
|
||||||
|
@ -40,7 +40,7 @@ export type OptionSelections = {
|
|||||||
|
|
||||||
export type CartItemBody = Core.CartItemBody & {
|
export type CartItemBody = Core.CartItemBody & {
|
||||||
productId: string // The product id is always required for BC
|
productId: string // The product id is always required for BC
|
||||||
optionSelections?: OptionSelections
|
optionSelections?: OptionSelections[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CartTypes = {
|
export type CartTypes = {
|
||||||
|
@ -1,19 +1,22 @@
|
|||||||
## Saleor Provider
|
## Saleor Provider
|
||||||
|
|
||||||
**Demo:** TBD
|
**Demo:** https://saleor.vercel.store/
|
||||||
|
|
||||||
Before getting starter, a [Saleor](https://saleor.io/) account and store is required before using the provider.
|
You need a [Saleor](https://saleor.io/) instance, either in the cloud or self-hosted.
|
||||||
|
|
||||||
Next, copy the `.env.template` file in this directory to `.env.local` in the main directory (which will be ignored by Git):
|
This provider requires Saleor **3.x** or higher.
|
||||||
|
|
||||||
|
Copy the `.env.template` file in this directory to `.env.local` in the main directory (which will be ignored by Git):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp framework/saleor/.env.template .env.local
|
cp framework/saleor/.env.template .env.local
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, set the environment variables in `.env.local` to match the ones from your store.
|
Then, set the environment following variables in your `.env.local`. Both, `NEXT_PUBLIC_SALEOR_API_URL` and `COMMERCE_IMAGE_HOST` must point to your own Saleor instance.
|
||||||
|
|
||||||
## Contribute
|
```
|
||||||
|
COMMERCE_PROVIDER=saleor
|
||||||
Our commitment to Open Source can be found [here](https://vercel.com/oss).
|
NEXT_PUBLIC_SALEOR_API_URL=https://vercel.saleor.cloud/graphql/
|
||||||
|
NEXT_PUBLIC_SALEOR_CHANNEL=default-channel
|
||||||
If you find an issue with the provider or want a new feature, feel free to open a PR or [create a new issue](https://github.com/vercel/commerce/issues).
|
COMMERCE_IMAGE_HOST=vercel.saleor.cloud
|
||||||
|
```
|
||||||
|
@ -22,7 +22,7 @@ export const handler: MutationHook<LoginHook> = {
|
|||||||
if (!(email && password)) {
|
if (!(email && password)) {
|
||||||
throw new CommerceError({
|
throw new CommerceError({
|
||||||
message:
|
message:
|
||||||
'A first name, last name, email and password are required to login',
|
'An email and password are required to login',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@ UI hooks and data fetching methods built from the ground up for e-commerce appli
|
|||||||
```
|
```
|
||||||
3. With the Vendure server running, start this project using `yarn dev` or `npm run dev`.
|
3. With the Vendure server running, start this project using `yarn dev` or `npm run dev`.
|
||||||
|
|
||||||
|
**Note:** The Vendure server needs to be configured to use the "cookie" tokenMethod rather than "bearer" to work with this provider. For more information see the [Managing Sessions docs](https://www.vendure.io/docs/storefront/managing-sessions/).
|
||||||
|
|
||||||
## Known Limitations
|
## Known Limitations
|
||||||
|
|
||||||
1. Vendure does not ship with built-in wishlist functionality.
|
1. Vendure does not ship with built-in wishlist functionality.
|
||||||
|
@ -55,10 +55,13 @@ export default function FocusTrap({ children, focusFirst = false }: Props) {
|
|||||||
}
|
}
|
||||||
}, [root, children])
|
}, [root, children])
|
||||||
|
|
||||||
return React.createElement('div', {
|
return React.createElement(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
ref: root,
|
ref: root,
|
||||||
children,
|
|
||||||
className: 'outline-none focus-trap',
|
className: 'outline-none focus-trap',
|
||||||
tabIndex: -1,
|
tabIndex: -1,
|
||||||
})
|
},
|
||||||
|
children
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
@ -67,6 +68,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",
|
||||||
@ -82,6 +86,7 @@
|
|||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"**/*.{js,jsx,ts,tsx}": [
|
"**/*.{js,jsx,ts,tsx}": [
|
||||||
|
"eslint",
|
||||||
"prettier --write",
|
"prettier --write",
|
||||||
"git add"
|
"git add"
|
||||||
],
|
],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user