mirror of
https://github.com/vercel/commerce.git
synced 2025-05-18 15:36:58 +00:00
Merge branch 'custom-checkout' into improvements
This commit is contained in:
commit
46bb71cca1
@ -6,23 +6,6 @@
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.actions {
|
||||
@apply flex p-1 border-accent-2 border items-center justify-center w-12 text-accent-7;
|
||||
transition-property: border-color, background, color, transform, box-shadow;
|
||||
transition-duration: 0.15s;
|
||||
transition-timing-function: ease;
|
||||
}
|
||||
|
||||
.actions:hover {
|
||||
@apply bg-accent-1 border-accent-3 text-accent-9;
|
||||
transition: border-color;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.actions:focus {
|
||||
@apply bg-accent-2 outline-none;
|
||||
}
|
||||
|
||||
.quantity {
|
||||
appearance: textfield;
|
||||
@apply w-8 border-accent-2 border mx-3 rounded text-center text-sm text-black;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ChangeEvent, useEffect, useState } from 'react'
|
||||
import { ChangeEvent, FocusEventHandler, useEffect, useState } from 'react'
|
||||
import cn from 'classnames'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
@ -9,6 +9,7 @@ import type { LineItem } from '@commerce/types/cart'
|
||||
import usePrice from '@framework/product/use-price'
|
||||
import useUpdateItem from '@framework/cart/use-update-item'
|
||||
import useRemoveItem from '@framework/cart/use-remove-item'
|
||||
import Quantity from '@components/ui/Quantity'
|
||||
|
||||
type ItemOption = {
|
||||
name: string
|
||||
@ -28,6 +29,10 @@ const CartItem = ({
|
||||
currencyCode: string
|
||||
}) => {
|
||||
const { closeSidebarIfPresent } = useUI()
|
||||
const [removing, setRemoving] = useState(false)
|
||||
const [quantity, setQuantity] = useState<number>(item.quantity)
|
||||
const removeItem = useRemoveItem()
|
||||
const updateItem = useUpdateItem({ item })
|
||||
|
||||
const { price } = usePrice({
|
||||
amount: item.variant.price * item.quantity,
|
||||
@ -35,43 +40,22 @@ const CartItem = ({
|
||||
currencyCode,
|
||||
})
|
||||
|
||||
const updateItem = useUpdateItem({ item })
|
||||
const removeItem = useRemoveItem()
|
||||
const [quantity, setQuantity] = useState<number | ''>(item.quantity)
|
||||
const [removing, setRemoving] = useState(false)
|
||||
|
||||
const updateQuantity = async (val: number) => {
|
||||
await updateItem({ quantity: val })
|
||||
const handleChange = async ({
|
||||
target: { value },
|
||||
}: ChangeEvent<HTMLInputElement>) => {
|
||||
setQuantity(Number(value))
|
||||
await updateItem({ quantity: Number(value) })
|
||||
}
|
||||
|
||||
const handleQuantity = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const val = !e.target.value ? '' : Number(e.target.value)
|
||||
|
||||
if (!val || (Number.isInteger(val) && val >= 0)) {
|
||||
setQuantity(val)
|
||||
}
|
||||
}
|
||||
|
||||
const handleBlur = () => {
|
||||
const val = Number(quantity)
|
||||
if (val !== item.quantity) {
|
||||
updateQuantity(val)
|
||||
}
|
||||
}
|
||||
|
||||
const increaseQuantity = (n = 1) => {
|
||||
const increaseQuantity = async (n = 1) => {
|
||||
const val = Number(quantity) + n
|
||||
if (Number.isInteger(val) && val >= 0) {
|
||||
setQuantity(val)
|
||||
updateQuantity(val)
|
||||
}
|
||||
setQuantity(val)
|
||||
await updateItem({ quantity: val })
|
||||
}
|
||||
|
||||
const handleRemove = async () => {
|
||||
setRemoving(true)
|
||||
try {
|
||||
// If this action succeeds then there's no need to do `setRemoving(true)`
|
||||
// because the component will be removed from the view
|
||||
await removeItem(item)
|
||||
} catch (error) {
|
||||
setRemoving(false)
|
||||
@ -152,38 +136,13 @@ const CartItem = ({
|
||||
</div>
|
||||
</div>
|
||||
{variant === 'default' && (
|
||||
<div className="flex flex-row h-9">
|
||||
<button className={s.actions} onClick={handleRemove}>
|
||||
<Cross width={20} height={20} />
|
||||
</button>
|
||||
<label className="w-full border-accent-2 border ml-2">
|
||||
<input
|
||||
type="number"
|
||||
max={99}
|
||||
min={0}
|
||||
className="bg-transparent px-4 w-full h-full focus:outline-none"
|
||||
value={quantity}
|
||||
onChange={handleQuantity}
|
||||
onBlur={handleBlur}
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => increaseQuantity(-1)}
|
||||
className={s.actions}
|
||||
style={{ marginLeft: '-1px' }}
|
||||
>
|
||||
<Minus width={18} height={18} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => increaseQuantity(1)}
|
||||
className={cn(s.actions)}
|
||||
style={{ marginLeft: '-1px' }}
|
||||
>
|
||||
<Plus width={18} height={18} />
|
||||
</button>
|
||||
</div>
|
||||
<Quantity
|
||||
value={quantity}
|
||||
handleRemove={handleRemove}
|
||||
handleChange={handleChange}
|
||||
increase={() => increaseQuantity(1)}
|
||||
decrease={() => increaseQuantity(-1)}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
|
@ -89,7 +89,7 @@ const CartSidebarView: FC = () => {
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0 px-6 py-6 sm:px-6 sticky z-20 bottom-0 w-full right-0 left-0 bg-accent-0 shadow-outline-normal text-sm">
|
||||
<div className="flex-shrink-0 px-6 py-6 sm:px-6 sticky z-20 bottom-0 w-full right-0 left-0 bg-accent-0 border-t text-sm">
|
||||
<ul className="pb-2">
|
||||
<li className="flex justify-between py-1">
|
||||
<span>Subtotal</span>
|
||||
|
@ -50,7 +50,7 @@ const CheckoutSidebarView: FC = () => {
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0 px-6 py-6 sm:px-6 sticky z-20 bottom-0 w-full right-0 left-0 bg-accent-0 shadow-outline-normal text-sm">
|
||||
<div className="flex-shrink-0 px-6 py-6 sm:px-6 sticky z-20 bottom-0 w-full right-0 left-0 bg-accent-0 border-t text-sm">
|
||||
<ul className="pb-2">
|
||||
<li className="flex justify-between py-1">
|
||||
<span>Subtotal</span>
|
||||
|
@ -3,11 +3,11 @@
|
||||
}
|
||||
|
||||
.button {
|
||||
@apply h-10 px-2 rounded-md border border-accent-2 flex items-center justify-center;
|
||||
@apply h-10 px-2 rounded-md border border-accent-2 flex items-center justify-center transition-colors ease-linear;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
@apply border-accent-4 shadow-sm;
|
||||
@apply border-accent-3 shadow-sm;
|
||||
}
|
||||
|
||||
.button:focus {
|
||||
@ -38,5 +38,9 @@
|
||||
}
|
||||
|
||||
.icon {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.icon.active {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ const I18nWidget: FC = () => {
|
||||
/>
|
||||
{options && (
|
||||
<span className="cursor-pointer">
|
||||
<ChevronUp className={cn({ [s.icon]: display })} />
|
||||
<ChevronUp className={cn(s.icon, { [s.active]: display })} />
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
|
@ -25,7 +25,7 @@ const SidebarLayout: FC<ComponentProps> = ({
|
||||
className="hover:text-gray-500 transition ease-in-out duration-150 flex items-center focus:outline-none"
|
||||
>
|
||||
<Cross className="h-6 w-6" />
|
||||
<span className="ml-2 text-accent-7 text-xs hover:text-gray-500">
|
||||
<span className="ml-2 text-accent-7 text-sm hover:text-gray-500">
|
||||
Close
|
||||
</span>
|
||||
</button>
|
||||
|
@ -1,6 +1,6 @@
|
||||
.root {
|
||||
@apply relative w-full h-full;
|
||||
overflow-y: hidden;
|
||||
@apply relative w-full h-full select-none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.slider {
|
||||
@ -14,7 +14,7 @@
|
||||
|
||||
.control {
|
||||
@apply bg-violet absolute bottom-10 right-10 flex flex-row
|
||||
border-accent-0 border text-accent-0 z-30 shadow-xl;
|
||||
border-accent-0 border text-accent-0 z-30 shadow-xl select-none;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
@ -59,11 +59,16 @@
|
||||
}
|
||||
|
||||
.album {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@apply bg-violet-dark;
|
||||
box-sizing: content-box;
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
height: 125px;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: -10px;
|
||||
}
|
||||
|
||||
@screen md {
|
||||
|
@ -9,11 +9,11 @@ import { getVariant, SelectedOptions } from '../helpers'
|
||||
import { Swatch, ProductSlider } from '@components/product'
|
||||
import { Button, Container, Text, useUI } from '@components/ui'
|
||||
import { useAddItem } from '@framework/cart'
|
||||
import Rating from '@components/ui/Rating'
|
||||
import Collapse from '@components/ui/Collapse'
|
||||
import ProductCard from '@components/product/ProductCard'
|
||||
import WishlistButton from '@components/wishlist/WishlistButton'
|
||||
import rangeMap from '@lib/range-map'
|
||||
import { Star } from '@components/icons'
|
||||
|
||||
interface Props {
|
||||
children?: any
|
||||
product: Product
|
||||
@ -154,20 +154,10 @@ const ProductView: FC<Props> = ({ product, relatedProducts }) => {
|
||||
</div>
|
||||
</section>
|
||||
<div className="flex flex-row justify-between items-center">
|
||||
{/**
|
||||
* TODO make component Rate stars={}
|
||||
*/}
|
||||
<div className="flex flex-row py-6">
|
||||
{rangeMap(4, (i) => (
|
||||
<span className="inline-block ml-1" key={i}>
|
||||
<Star />
|
||||
</span>
|
||||
))}
|
||||
<span className="inline-block ml-1 text-accent-5">
|
||||
<Star />
|
||||
</span>
|
||||
<Rating value={2} />
|
||||
<div className="text-accent-6 pr-1 font-medium select-none">
|
||||
36 reviews
|
||||
</div>
|
||||
<div className="text-accent-6 pr-1">36 reviews</div>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
|
@ -3,7 +3,7 @@
|
||||
composes: root from '@components/ui/Button/Button.module.css';
|
||||
@apply h-10 w-10 bg-primary text-primary rounded-full mr-3 inline-flex
|
||||
items-center justify-center cursor-pointer transition duration-150 ease-in-out
|
||||
p-0 shadow-none border-gray-200 border box-border;
|
||||
p-0 shadow-none border-gray-200 border box-border select-none;
|
||||
margin-right: calc(0.75rem - 1px);
|
||||
overflow: hidden;
|
||||
width: 48px;
|
||||
|
@ -1,14 +1,19 @@
|
||||
import cn from 'classnames'
|
||||
import React, { FC } from 'react'
|
||||
|
||||
interface Props {
|
||||
interface ContainerProps {
|
||||
className?: string
|
||||
children?: any
|
||||
el?: HTMLElement
|
||||
clean?: boolean
|
||||
}
|
||||
|
||||
const Container: FC<Props> = ({ children, className, el = 'div', clean }) => {
|
||||
const Container: FC<ContainerProps> = ({
|
||||
children,
|
||||
className,
|
||||
el = 'div',
|
||||
clean,
|
||||
}) => {
|
||||
const rootClassName = cn(className, {
|
||||
'mx-auto max-w-8xl px-6': !clean,
|
||||
})
|
||||
|
@ -2,14 +2,14 @@ import cn from 'classnames'
|
||||
import { FC, ReactNode, Component } from 'react'
|
||||
import s from './Grid.module.css'
|
||||
|
||||
interface Props {
|
||||
interface GridProps {
|
||||
className?: string
|
||||
children?: ReactNode[] | Component[] | any[]
|
||||
layout?: 'A' | 'B' | 'C' | 'D' | 'normal'
|
||||
variant?: 'default' | 'filled'
|
||||
}
|
||||
|
||||
const Grid: FC<Props> = ({
|
||||
const Grid: FC<GridProps> = ({
|
||||
className,
|
||||
layout = 'A',
|
||||
children,
|
||||
|
@ -3,13 +3,13 @@ import { Container } from '@components/ui'
|
||||
import { RightArrow } from '@components/icons'
|
||||
import s from './Hero.module.css'
|
||||
import Link from 'next/link'
|
||||
interface Props {
|
||||
interface HeroProps {
|
||||
className?: string
|
||||
headline: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const Hero: FC<Props> = ({ headline, description }) => {
|
||||
const Hero: FC<HeroProps> = ({ headline, description }) => {
|
||||
return (
|
||||
<div className="bg-black">
|
||||
<Container>
|
||||
|
@ -2,12 +2,12 @@ import cn from 'classnames'
|
||||
import s from './Input.module.css'
|
||||
import React, { InputHTMLAttributes } from 'react'
|
||||
|
||||
export interface Props extends InputHTMLAttributes<HTMLInputElement> {
|
||||
export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||
className?: string
|
||||
onChange?: (...args: any[]) => any
|
||||
}
|
||||
|
||||
const Input: React.FC<Props> = (props) => {
|
||||
const Input: React.FC<InputProps> = (props) => {
|
||||
const { className, children, onChange, ...rest } = props
|
||||
|
||||
const rootClassName = cn(s.root, {}, className)
|
||||
|
@ -3,13 +3,13 @@ import s from './Marquee.module.css'
|
||||
import { FC, ReactNode, Component } from 'react'
|
||||
import Ticker from 'react-ticker'
|
||||
|
||||
interface Props {
|
||||
interface MarqueeProps {
|
||||
className?: string
|
||||
children?: ReactNode[] | Component[] | any[]
|
||||
variant?: 'primary' | 'secondary'
|
||||
}
|
||||
|
||||
const Marquee: FC<Props> = ({
|
||||
const Marquee: FC<MarqueeProps> = ({
|
||||
className = '',
|
||||
children,
|
||||
variant = 'primary',
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
clearAllBodyScrollLocks,
|
||||
} from 'body-scroll-lock'
|
||||
import FocusTrap from '@lib/focus-trap'
|
||||
interface Props {
|
||||
interface ModalProps {
|
||||
className?: string
|
||||
children?: any
|
||||
open?: boolean
|
||||
@ -16,7 +16,7 @@ interface Props {
|
||||
onEnter?: () => void | null
|
||||
}
|
||||
|
||||
const Modal: FC<Props> = ({ children, open, onClose, onEnter = null }) => {
|
||||
const Modal: FC<ModalProps> = ({ children, open, onClose, onEnter = null }) => {
|
||||
const ref = useRef() as React.MutableRefObject<HTMLDivElement>
|
||||
|
||||
const handleKey = useCallback(
|
||||
|
22
components/ui/Quantity/Quantity.module.css
Normal file
22
components/ui/Quantity/Quantity.module.css
Normal file
@ -0,0 +1,22 @@
|
||||
.actions {
|
||||
@apply flex p-1 border-accent-2 border items-center justify-center w-12 text-accent-7;
|
||||
transition-property: border-color, background, color, transform, box-shadow;
|
||||
transition-duration: 0.15s;
|
||||
transition-timing-function: ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.actions:hover {
|
||||
@apply border bg-accent-1 border-accent-3 text-accent-9;
|
||||
transition: border-color;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.actions:focus {
|
||||
@apply outline-none;
|
||||
}
|
||||
|
||||
.input {
|
||||
@apply bg-transparent px-4 w-full h-full focus:outline-none;
|
||||
user-select: none;
|
||||
}
|
62
components/ui/Quantity/Quantity.tsx
Normal file
62
components/ui/Quantity/Quantity.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import React, { FC } from 'react'
|
||||
import s from './Quantity.module.css'
|
||||
import { Cross, Plus, Minus } from '@components/icons'
|
||||
import cn from 'classnames'
|
||||
export interface QuantityProps {
|
||||
value: number
|
||||
increase: () => any
|
||||
decrease: () => any
|
||||
handleRemove: React.MouseEventHandler<HTMLButtonElement>
|
||||
handleChange: React.ChangeEventHandler<HTMLInputElement>
|
||||
max?: number
|
||||
}
|
||||
|
||||
const Quantity: FC<QuantityProps> = ({
|
||||
value,
|
||||
increase,
|
||||
decrease,
|
||||
handleChange,
|
||||
handleRemove,
|
||||
max = 6,
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-row h-9">
|
||||
<button className={s.actions} onClick={handleRemove}>
|
||||
<Cross width={20} height={20} />
|
||||
</button>
|
||||
<label className="w-full border-accent-2 border ml-2">
|
||||
<input
|
||||
className={s.input}
|
||||
onChange={(e) =>
|
||||
Number(e.target.value) < max + 1 ? handleChange(e) : () => {}
|
||||
}
|
||||
value={value}
|
||||
type="number"
|
||||
max={max}
|
||||
min="0"
|
||||
readOnly
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={decrease}
|
||||
className={s.actions}
|
||||
style={{ marginLeft: '-1px' }}
|
||||
disabled={value <= 1}
|
||||
>
|
||||
<Minus width={18} height={18} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={increase}
|
||||
className={cn(s.actions)}
|
||||
style={{ marginLeft: '-1px' }}
|
||||
disabled={value < 1 || value >= max}
|
||||
>
|
||||
<Plus width={18} height={18} />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Quantity
|
2
components/ui/Quantity/index.ts
Normal file
2
components/ui/Quantity/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { default } from './Quantity'
|
||||
export * from './Quantity'
|
0
components/ui/Rating/Rating.module.css
Normal file
0
components/ui/Rating/Rating.module.css
Normal file
27
components/ui/Rating/Rating.tsx
Normal file
27
components/ui/Rating/Rating.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import React, { FC } from 'react'
|
||||
import rangeMap from '@lib/range-map'
|
||||
import { Star } from '@components/icons'
|
||||
import cn from 'classnames'
|
||||
|
||||
export interface RatingProps {
|
||||
value: number
|
||||
}
|
||||
|
||||
const Quantity: FC<RatingProps> = ({ 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>
|
||||
)
|
||||
}
|
||||
|
||||
export default Quantity
|
2
components/ui/Rating/index.ts
Normal file
2
components/ui/Rating/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { default } from './Rating'
|
||||
export * from './Rating'
|
@ -7,13 +7,13 @@ import {
|
||||
clearAllBodyScrollLocks,
|
||||
} from 'body-scroll-lock'
|
||||
|
||||
interface Props {
|
||||
interface SidebarProps {
|
||||
children: any
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const Sidebar: FC<Props> = ({ children, open = false, onClose }) => {
|
||||
const Sidebar: FC<SidebarProps> = ({ children, open = false, onClose }) => {
|
||||
const ref = useRef() as React.MutableRefObject<HTMLDivElement>
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -3,7 +3,7 @@ import cn from 'classnames'
|
||||
import px from '@lib/to-pixels'
|
||||
import s from './Skeleton.module.css'
|
||||
|
||||
interface Props {
|
||||
interface SkeletonProps {
|
||||
width?: string | number
|
||||
height?: string | number
|
||||
boxHeight?: string | number
|
||||
@ -13,7 +13,7 @@ interface Props {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const Skeleton: React.FC<Props> = ({
|
||||
const Skeleton: React.FC<SkeletonProps> = ({
|
||||
style,
|
||||
width,
|
||||
height,
|
||||
|
@ -6,7 +6,7 @@ import React, {
|
||||
import cn from 'classnames'
|
||||
import s from './Text.module.css'
|
||||
|
||||
interface Props {
|
||||
interface TextProps {
|
||||
variant?: Variant
|
||||
className?: string
|
||||
style?: CSSProperties
|
||||
@ -17,7 +17,7 @@ interface Props {
|
||||
|
||||
type Variant = 'heading' | 'body' | 'pageHeading' | 'sectionHeading'
|
||||
|
||||
const Text: FunctionComponent<Props> = ({
|
||||
const Text: FunctionComponent<TextProps> = ({
|
||||
style,
|
||||
className = '',
|
||||
variant = 'body',
|
||||
|
@ -11,4 +11,6 @@ export { default as Modal } from './Modal'
|
||||
export { default as Text } from './Text'
|
||||
export { default as Input } from './Input'
|
||||
export { default as Collapse } from './Collapse'
|
||||
export { default as Quantity } from './Quantity'
|
||||
export { default as Rating } from './Rating'
|
||||
export { useUI } from './context'
|
||||
|
@ -1,42 +0,0 @@
|
||||
import { Product } from '@commerce/types'
|
||||
import { getConfig, ShopifyConfig } from '../api'
|
||||
import fetchAllProducts from '../api/utils/fetch-all-products'
|
||||
import { ProductEdge } from '../schema'
|
||||
import getAllProductsPathsQuery from '../utils/queries/get-all-products-paths-query'
|
||||
|
||||
type ProductPath = {
|
||||
path: string
|
||||
}
|
||||
|
||||
export type ProductPathNode = {
|
||||
node: ProductPath
|
||||
}
|
||||
|
||||
type ReturnType = {
|
||||
products: ProductPathNode[]
|
||||
}
|
||||
|
||||
const getAllProductPaths = async (options?: {
|
||||
variables?: any
|
||||
config?: ShopifyConfig
|
||||
preview?: boolean
|
||||
}): Promise<ReturnType> => {
|
||||
let { config, variables = { first: 250 } } = options ?? {}
|
||||
config = getConfig(config)
|
||||
|
||||
const products = await fetchAllProducts({
|
||||
config,
|
||||
query: getAllProductsPathsQuery,
|
||||
variables,
|
||||
})
|
||||
|
||||
return {
|
||||
products: products?.map(({ node: { handle } }: ProductEdge) => ({
|
||||
node: {
|
||||
path: `/${handle}`,
|
||||
},
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
export default getAllProductPaths
|
@ -1,40 +0,0 @@
|
||||
import { GraphQLFetcherResult } from '@commerce/api'
|
||||
import { getConfig, ShopifyConfig } from '../api'
|
||||
import { ProductEdge } from '../schema'
|
||||
import { getAllProductsQuery } from '../utils/queries'
|
||||
import { normalizeProduct } from '../utils/normalize'
|
||||
import { Product } from '@commerce/types'
|
||||
|
||||
type Variables = {
|
||||
first?: number
|
||||
field?: string
|
||||
}
|
||||
|
||||
type ReturnType = {
|
||||
products: Product[]
|
||||
}
|
||||
|
||||
const getAllProducts = async (options: {
|
||||
variables?: Variables
|
||||
config?: ShopifyConfig
|
||||
preview?: boolean
|
||||
}): Promise<ReturnType> => {
|
||||
let { config, variables = { first: 250 } } = options ?? {}
|
||||
config = getConfig(config)
|
||||
|
||||
const { data }: GraphQLFetcherResult = await config.fetch(
|
||||
getAllProductsQuery,
|
||||
{ variables }
|
||||
)
|
||||
|
||||
const products =
|
||||
data.products?.edges?.map(({ node: p }: ProductEdge) =>
|
||||
normalizeProduct(p)
|
||||
) ?? []
|
||||
|
||||
return {
|
||||
products,
|
||||
}
|
||||
}
|
||||
|
||||
export default getAllProducts
|
@ -1,32 +0,0 @@
|
||||
import { GraphQLFetcherResult } from '@commerce/api'
|
||||
import { getConfig, ShopifyConfig } from '../api'
|
||||
import { normalizeProduct, getProductQuery } from '../utils'
|
||||
|
||||
type Variables = {
|
||||
slug: string
|
||||
}
|
||||
|
||||
type ReturnType = {
|
||||
product: any
|
||||
}
|
||||
|
||||
const getProduct = async (options: {
|
||||
variables: Variables
|
||||
config: ShopifyConfig
|
||||
preview?: boolean
|
||||
}): Promise<ReturnType> => {
|
||||
let { config, variables } = options ?? {}
|
||||
config = getConfig(config)
|
||||
|
||||
const { data }: GraphQLFetcherResult = await config.fetch(getProductQuery, {
|
||||
variables,
|
||||
})
|
||||
|
||||
const { productByHandle } = data
|
||||
|
||||
return {
|
||||
product: productByHandle ? normalizeProduct(productByHandle) : null,
|
||||
}
|
||||
}
|
||||
|
||||
export default getProduct
|
@ -55,14 +55,8 @@ export default function Home({
|
||||
))}
|
||||
</Marquee>
|
||||
<Hero
|
||||
headline="Release Details: The Yeezy BOOST 350 V2 ‘Natural'"
|
||||
description="
|
||||
The Yeezy BOOST 350 V2 lineup continues to grow. We recently had the
|
||||
‘Carbon’ iteration, and now release details have been locked in for
|
||||
this ‘Natural’ joint. Revealed by Yeezy Mafia earlier this year, the
|
||||
shoe was originally called ‘Abez’, which translated to ‘Tin’ in
|
||||
Hebrew. It’s now undergone a name change, and will be referred to as
|
||||
‘Natural’."
|
||||
headline=" Dessert dragée halvah croissant."
|
||||
description="Cupcake ipsum dolor sit amet lemon drops pastry cotton candy. Sweet carrot cake macaroon bonbon croissant fruitcake jujubes macaroon oat cake. Soufflé bonbon caramels jelly beans. Tiramisu sweet roll cheesecake pie carrot cake. "
|
||||
/>
|
||||
<Grid layout="B" variant="filled">
|
||||
{products.slice(0, 3).map((product, i) => (
|
||||
|
Loading…
x
Reference in New Issue
Block a user