mirror of
https://github.com/vercel/commerce.git
synced 2025-03-14 22:42:33 +00:00
Merge branch 'master' of github.com:okbel/e-comm-example
This commit is contained in:
commit
26a2090b11
@ -1,30 +1,24 @@
|
||||
import cn from 'classnames'
|
||||
import s from './ProductCard.module.css'
|
||||
import { FC, ReactNode, Component } from 'react'
|
||||
import type { ProductNode } from '@lib/bigcommerce/api/operations/get-all-products'
|
||||
import { Heart } from '@components/icon'
|
||||
import Link from 'next/link'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
children?: ReactNode[] | Component[] | any[]
|
||||
node: ProductData
|
||||
product: ProductNode
|
||||
variant?: 'slim' | 'simple'
|
||||
}
|
||||
|
||||
interface ProductData {
|
||||
name: string
|
||||
images: any
|
||||
prices: any
|
||||
path: string
|
||||
}
|
||||
|
||||
const ProductCard: FC<Props> = ({ className, node: p, variant }) => {
|
||||
const ProductCard: FC<Props> = ({ className, product: p, variant }) => {
|
||||
if (variant === 'slim') {
|
||||
return (
|
||||
<div className="relative overflow-hidden box-border">
|
||||
<img
|
||||
className="object-scale-down h-48"
|
||||
src={p.images.edges[0].node.urlSmall}
|
||||
src={p.images.edges?.[0]?.node.urlSmall}
|
||||
/>
|
||||
<div className="absolute inset-0 flex items-center justify-end mr-8">
|
||||
<span className="bg-black text-white inline-block p-3 font-bold text-xl break-words">
|
||||
@ -41,7 +35,7 @@ const ProductCard: FC<Props> = ({ className, node: p, variant }) => {
|
||||
<div className="absolute z-10 inset-0 flex items-center justify-center">
|
||||
<img
|
||||
className="w-full object-cover"
|
||||
src={p.images.edges[0].node.urlXL}
|
||||
src={p.images.edges?.[0]?.node.urlXL}
|
||||
/>
|
||||
</div>
|
||||
<div className={cn(s.squareBg, { [s.gray]: variant === 'simple' })} />
|
||||
@ -50,7 +44,7 @@ const ProductCard: FC<Props> = ({ className, node: p, variant }) => {
|
||||
<p className={s.productTitle}>
|
||||
<span>{p.name}</span>
|
||||
</p>
|
||||
<span className={s.productPrice}>${p.prices.price.value}</span>
|
||||
<span className={s.productPrice}>${p.prices?.price.value}</span>
|
||||
</div>
|
||||
<div className={s.wishlistButton}>
|
||||
<Heart />
|
||||
|
@ -1,17 +1,17 @@
|
||||
import { NextSeo } from 'next-seo'
|
||||
import { FC, useState } from 'react'
|
||||
import s from './ProductView.module.css'
|
||||
import type { ProductNode } from '@lib/bigcommerce/api/operations/get-product'
|
||||
import { useUI } from '@components/ui/context'
|
||||
import { Button, Container } from '@components/ui'
|
||||
import { Swatch, ProductSlider } from '@components/product'
|
||||
import useAddItem from '@lib/bigcommerce/cart/use-add-item'
|
||||
import type { Product } from '@lib/bigcommerce/api/operations/get-product'
|
||||
import { getProductOptions } from '../helpers'
|
||||
import s from './ProductView.module.css'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
children?: any
|
||||
product: Product
|
||||
product: ProductNode
|
||||
}
|
||||
|
||||
const ProductView: FC<Props> = ({ product, className }) => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { Product } from '@lib/bigcommerce/api/operations/get-product'
|
||||
import type { ProductNode } from '@lib/bigcommerce/api/operations/get-product'
|
||||
|
||||
export function getProductOptions(product: Product) {
|
||||
export function getProductOptions(product: ProductNode) {
|
||||
// console.log(product)
|
||||
const options = product.productOptions.edges?.map(({ node }: any) => ({
|
||||
displayName: node.displayName.toLowerCase(),
|
||||
|
@ -1,7 +1,4 @@
|
||||
import getAllProducts, {
|
||||
Products,
|
||||
Product,
|
||||
} from '../../operations/get-all-products'
|
||||
import getAllProducts, { ProductEdge } from '../../operations/get-all-products'
|
||||
import type { ProductsHandlers } from '../products'
|
||||
|
||||
const SORT: { [key: string]: string | undefined } = {
|
||||
@ -54,14 +51,13 @@ const getProducts: ProductsHandlers['getProducts'] = async ({
|
||||
variables: { first: LIMIT, entityIds },
|
||||
})
|
||||
// Put the products in an object that we can use to get them by id
|
||||
const productsById = graphqlData.products.reduce<{ [k: number]: Product }>(
|
||||
(prods, p) => {
|
||||
prods[p.node.entityId] = p
|
||||
return prods
|
||||
},
|
||||
{}
|
||||
)
|
||||
const products: Products = found ? [] : graphqlData.products
|
||||
const productsById = graphqlData.products.reduce<{
|
||||
[k: number]: ProductEdge
|
||||
}>((prods, p) => {
|
||||
prods[p.node.entityId] = p
|
||||
return prods
|
||||
}, {})
|
||||
const products: ProductEdge[] = found ? [] : graphqlData.products
|
||||
|
||||
// Populate the products array with the graphql products, in the order
|
||||
// assigned by the list of entity ids
|
||||
|
@ -4,11 +4,11 @@ import createApiHandler, {
|
||||
BigcommerceHandler,
|
||||
} from '../utils/create-api-handler'
|
||||
import { BigcommerceApiError } from '../utils/errors'
|
||||
import type { Products } from '../operations/get-all-products'
|
||||
import type { ProductEdge } from '../operations/get-all-products'
|
||||
import getProducts from './handlers/get-products'
|
||||
|
||||
export type SearchProductsData = {
|
||||
products: Products
|
||||
products: ProductEdge[]
|
||||
found: boolean
|
||||
}
|
||||
|
||||
|
@ -1,56 +0,0 @@
|
||||
import createApiHandler, {
|
||||
BigcommerceApiHandler,
|
||||
BigcommerceHandler,
|
||||
} from './utils/create-api-handler'
|
||||
import isAllowedMethod from './utils/is-allowed-method'
|
||||
import { BigcommerceApiError } from './utils/errors'
|
||||
|
||||
type Body<T> = Partial<T> | undefined
|
||||
|
||||
export type Customer = any
|
||||
|
||||
export type AddCustomerBody = { item: any }
|
||||
|
||||
export type CartHandlers = {
|
||||
addItem: BigcommerceHandler<Customer, { cartId?: string } & Body<any>>
|
||||
}
|
||||
|
||||
const METHODS = ['POST']
|
||||
|
||||
const customersApi: BigcommerceApiHandler<Customer> = async (
|
||||
req,
|
||||
res,
|
||||
config
|
||||
) => {
|
||||
if (!isAllowedMethod(req, res, METHODS)) return
|
||||
|
||||
try {
|
||||
if (req.method === 'POST') {
|
||||
// let result = {} as any
|
||||
// const
|
||||
// result = await config.storeApiFetch('/v3/customers')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof BigcommerceApiError
|
||||
? 'An unexpected error ocurred with the Bigcommerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
}
|
||||
}
|
||||
|
||||
const createCustomer: BigcommerceHandler<Customer> = ({
|
||||
req,
|
||||
res,
|
||||
body,
|
||||
config,
|
||||
}) => {}
|
||||
|
||||
const handlers = {
|
||||
createCustomer,
|
||||
}
|
||||
|
||||
export default createApiHandler(customersApi, handlers, {})
|
58
lib/bigcommerce/api/customers/handlers/create-customer.ts
Normal file
58
lib/bigcommerce/api/customers/handlers/create-customer.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { BigcommerceApiError } from '../../utils/errors'
|
||||
import { CustomersHandlers } from '..'
|
||||
|
||||
const createCustomer: CustomersHandlers['createCustomer'] = async ({
|
||||
res,
|
||||
body: { firstName, lastName, email, password },
|
||||
config,
|
||||
}) => {
|
||||
// TODO: Add proper validations with something like Ajv
|
||||
if (!(firstName && lastName && email && password)) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Invalid request' }],
|
||||
})
|
||||
}
|
||||
// TODO: validate the password and email
|
||||
// Passwords must be at least 7 characters and contain both alphabetic
|
||||
// and numeric characters.
|
||||
|
||||
try {
|
||||
const { data } = await config.storeApiFetch('/v3/customers', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify([
|
||||
{
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
email,
|
||||
authentication: {
|
||||
new_password: password,
|
||||
},
|
||||
},
|
||||
]),
|
||||
})
|
||||
|
||||
res.status(200).json({ data })
|
||||
} catch (error) {
|
||||
if (error instanceof BigcommerceApiError && error.status === 422) {
|
||||
const hasEmailError = '0.email' in error.data?.errors
|
||||
|
||||
// If there's an error with the email, it most likely means it's duplicated
|
||||
if (hasEmailError) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
message: 'The email is already in use',
|
||||
code: 'duplicated_email',
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export default createCustomer
|
59
lib/bigcommerce/api/customers/index.ts
Normal file
59
lib/bigcommerce/api/customers/index.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import createApiHandler, {
|
||||
BigcommerceApiHandler,
|
||||
BigcommerceHandler,
|
||||
} from '../utils/create-api-handler'
|
||||
import isAllowedMethod from '../utils/is-allowed-method'
|
||||
import { BigcommerceApiError } from '../utils/errors'
|
||||
import createCustomer from './handlers/create-customer'
|
||||
|
||||
type Body<T> = Partial<T> | undefined
|
||||
|
||||
export type Customer = any
|
||||
|
||||
export type CreateCustomerBody = {
|
||||
firstName: string
|
||||
lastName: string
|
||||
email: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export type CustomersHandlers = {
|
||||
createCustomer: BigcommerceHandler<
|
||||
Customer,
|
||||
{ cartId?: string } & Body<CreateCustomerBody>
|
||||
>
|
||||
}
|
||||
|
||||
const METHODS = ['POST']
|
||||
|
||||
const customersApi: BigcommerceApiHandler<Customer, CustomersHandlers> = async (
|
||||
req,
|
||||
res,
|
||||
config
|
||||
) => {
|
||||
if (!isAllowedMethod(req, res, METHODS)) return
|
||||
|
||||
const { cookies } = req
|
||||
const cartId = cookies[config.cartCookie]
|
||||
|
||||
try {
|
||||
if (req.method === 'POST') {
|
||||
console.log('BODY', req.body)
|
||||
const body = { cartId, ...req.body }
|
||||
return await handlers['createCustomer']({ req, res, config, body })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof BigcommerceApiError
|
||||
? 'An unexpected error ocurred with the Bigcommerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
}
|
||||
}
|
||||
|
||||
const handlers = { createCustomer }
|
||||
|
||||
export default createApiHandler(customersApi, handlers, {})
|
@ -1,7 +1,7 @@
|
||||
import type {
|
||||
GetAllProductsQuery,
|
||||
GetAllProductsQueryVariables,
|
||||
} from 'lib/bigcommerce/schema'
|
||||
} from '@lib/bigcommerce/schema'
|
||||
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
|
||||
import filterEdges from '../utils/filter-edges'
|
||||
import { productConnectionFragment } from '../fragments/product'
|
||||
@ -43,14 +43,16 @@ export const getAllProductsQuery = /* GraphQL */ `
|
||||
${productConnectionFragment}
|
||||
`
|
||||
|
||||
export type Product = NonNullable<
|
||||
export type ProductEdge = NonNullable<
|
||||
NonNullable<GetAllProductsQuery['site']['products']['edges']>[0]
|
||||
>
|
||||
|
||||
export type Products = Product[]
|
||||
export type ProductNode = ProductEdge['node']
|
||||
|
||||
export type GetAllProductsResult<
|
||||
T extends Record<keyof GetAllProductsResult, any[]> = { products: Products }
|
||||
T extends Record<keyof GetAllProductsResult, any[]> = {
|
||||
products: ProductEdge[]
|
||||
}
|
||||
> = T
|
||||
|
||||
const FIELDS = [
|
||||
|
@ -33,13 +33,13 @@ export const getProductQuery = /* GraphQL */ `
|
||||
${productInfoFragment}
|
||||
`
|
||||
|
||||
export type Product = Extract<
|
||||
export type ProductNode = Extract<
|
||||
GetProductQuery['site']['route']['node'],
|
||||
{ __typename: 'Product' }
|
||||
>
|
||||
|
||||
export type GetProductResult<
|
||||
T extends { product?: any } = { product?: Product }
|
||||
T extends { product?: any } = { product?: ProductNode }
|
||||
> = T
|
||||
|
||||
export type ProductVariables = Images &
|
||||
|
@ -27,7 +27,7 @@ export type BigcommerceHandlers<T = any> = {
|
||||
|
||||
export type BigcommerceApiResponse<T> = {
|
||||
data: T | null
|
||||
errors?: { message: string }[]
|
||||
errors?: { message: string; code?: string }[]
|
||||
}
|
||||
|
||||
export default function createApiHandler<
|
||||
|
@ -4,12 +4,14 @@ export class BigcommerceGraphQLError extends Error {}
|
||||
export class BigcommerceApiError extends Error {
|
||||
status: number
|
||||
res: Response
|
||||
data: any
|
||||
|
||||
constructor(msg: string, res: Response) {
|
||||
constructor(msg: string, res: Response, data?: any) {
|
||||
super(msg)
|
||||
this.name = 'BigcommerceApiError'
|
||||
this.status = res.status
|
||||
this.res = res
|
||||
this.data = data
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ export default async function fetchGraphqlApi<Q, V = any>(
|
||||
query: string,
|
||||
{ variables, preview }: CommerceAPIFetchOptions<V> = {}
|
||||
): Promise<Q> {
|
||||
log.warn(query)
|
||||
// log.warn(query)
|
||||
const config = getConfig()
|
||||
const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
|
||||
method: 'POST',
|
||||
@ -23,7 +23,7 @@ export default async function fetchGraphqlApi<Q, V = any>(
|
||||
const json = await res.json()
|
||||
if (json.errors) {
|
||||
console.error(json.errors)
|
||||
throw new Error('Failed to fetch API')
|
||||
throw new Error('Failed to fetch BigCommerce API')
|
||||
}
|
||||
return json.data
|
||||
}
|
||||
|
@ -24,13 +24,22 @@ export default async function fetchStoreApi<T>(
|
||||
)
|
||||
}
|
||||
|
||||
const contentType = res.headers.get('Content-Type')
|
||||
const isJSON = contentType?.includes('application/json')
|
||||
|
||||
if (!res.ok) {
|
||||
throw new BigcommerceApiError(await getErrorText(res), res)
|
||||
const data = isJSON ? await res.json() : await getTextOrNull(res)
|
||||
const headers = getRawHeaders(res)
|
||||
const msg = `Big Commerce API error (${
|
||||
res.status
|
||||
}) \nHeaders: ${JSON.stringify(headers, null, 2)}\n${
|
||||
typeof data === 'string' ? data : JSON.stringify(data, null, 2)
|
||||
}`
|
||||
|
||||
throw new BigcommerceApiError(msg, res, data)
|
||||
}
|
||||
|
||||
const contentType = res.headers.get('Content-Type')
|
||||
|
||||
if (!contentType?.includes('application/json')) {
|
||||
if (!isJSON) {
|
||||
throw new BigcommerceApiError(
|
||||
`Fetch to Bigcommerce API failed, expected JSON content but found: ${contentType}`,
|
||||
res
|
||||
@ -41,12 +50,6 @@ export default async function fetchStoreApi<T>(
|
||||
return res.status === 204 ? null : await res.json()
|
||||
}
|
||||
|
||||
async function getErrorText(res: Response) {
|
||||
return `Big Commerce API error (${res.status}) \n${JSON.stringify(
|
||||
getRawHeaders(res)
|
||||
)}\n ${await getTextOrNull(res)}`
|
||||
}
|
||||
|
||||
function getRawHeaders(res: Response) {
|
||||
const headers: { [key: string]: string } = {}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { random } from 'lodash'
|
||||
import random from 'lodash.random'
|
||||
|
||||
export function getRandomPairOfColors() {
|
||||
const colors = ['#37B679', '#DA3C3C', '#3291FF', '#7928CA', '#79FFE1']
|
||||
|
7
lib/range-map.ts
Normal file
7
lib/range-map.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export default function rangeMap(n: number, fn: (i: number) => any) {
|
||||
const arr = []
|
||||
while (n > arr.length) {
|
||||
arr.push(fn(arr.length))
|
||||
}
|
||||
return arr
|
||||
}
|
14
package.json
14
package.json
@ -21,10 +21,6 @@
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^0.2.0",
|
||||
"@tailwindcss/ui": "^0.6.2",
|
||||
"@types/bunyan": "^1.8.6",
|
||||
"@types/bunyan-prettystream": "^0.1.31",
|
||||
"@types/classnames": "^2.2.10",
|
||||
"@types/react-swipeable-views": "^0.13.0",
|
||||
"animate.css": "^4.1.1",
|
||||
"bunyan": "^1.8.14",
|
||||
"bunyan-prettystream": "^0.1.3",
|
||||
@ -32,7 +28,12 @@
|
||||
"cookie": "^0.4.1",
|
||||
"js-cookie": "^2.2.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
<<<<<<< HEAD
|
||||
"next": "^9.5.5",
|
||||
=======
|
||||
"lodash.random": "^3.2.0",
|
||||
"next": "^9.5.6-canary.4",
|
||||
>>>>>>> 0922c87621a6a34986bf811a1f2e55ed0bc7edf4
|
||||
"next-seo": "^4.11.0",
|
||||
"next-themes": "^0.0.4",
|
||||
"nextjs-progressbar": "^0.0.6",
|
||||
@ -53,11 +54,16 @@
|
||||
"@graphql-codegen/typescript": "^1.17.10",
|
||||
"@graphql-codegen/typescript-operations": "^1.17.8",
|
||||
"@manifoldco/swagger-to-ts": "^2.1.0",
|
||||
"@types/bunyan": "^1.8.6",
|
||||
"@types/bunyan-prettystream": "^0.1.31",
|
||||
"@types/classnames": "^2.2.10",
|
||||
"@types/cookie": "^0.4.0",
|
||||
"@types/js-cookie": "^2.2.6",
|
||||
"@types/lodash.debounce": "^4.0.6",
|
||||
"@types/lodash.random": "^3.2.6",
|
||||
"@types/node": "^14.11.2",
|
||||
"@types/react": "^16.9.49",
|
||||
"@types/react-swipeable-views": "^0.13.0",
|
||||
"graphql": "^15.3.0",
|
||||
"postcss-flexbugs-fixes": "^4.2.1",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
|
3
pages/api/bigcommerce/customers.ts
Normal file
3
pages/api/bigcommerce/customers.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import customersApi from '@lib/bigcommerce/api/customers'
|
||||
|
||||
export default customersApi()
|
@ -1,40 +1,77 @@
|
||||
import { useMemo } from 'react'
|
||||
import { GetStaticPropsContext, InferGetStaticPropsType } from 'next'
|
||||
import getAllProducts from '@lib/bigcommerce/api/operations/get-all-products'
|
||||
import getSiteInfo from '@lib/bigcommerce/api/operations/get-site-info'
|
||||
import getAllPages from '@lib/bigcommerce/api/operations/get-all-pages'
|
||||
import rangeMap from '@lib/range-map'
|
||||
import { Layout } from '@components/core'
|
||||
import { Grid, Marquee, Hero } from '@components/ui'
|
||||
import { ProductCard } from '@components/product'
|
||||
import getSiteInfo from '@lib/bigcommerce/api/operations/get-site-info'
|
||||
import getAllPages from '@lib/bigcommerce/api/operations/get-all-pages'
|
||||
|
||||
export async function getStaticProps({ preview }: GetStaticPropsContext) {
|
||||
const { pages } = await getAllPages()
|
||||
const { products } = await getAllProducts()
|
||||
const { products: featuredProducts } = await getAllProducts({
|
||||
variables: { field: 'featuredProducts', first: 3 },
|
||||
variables: { field: 'featuredProducts', first: 6 },
|
||||
})
|
||||
const { products: bestSellingProducts } = await getAllProducts({
|
||||
variables: { field: 'bestSellingProducts', first: 6 },
|
||||
})
|
||||
const { products: newestProducts } = await getAllProducts({
|
||||
variables: { field: 'newestProducts', first: 12 },
|
||||
})
|
||||
const { categories, brands } = await getSiteInfo()
|
||||
const { pages } = await getAllPages()
|
||||
|
||||
return {
|
||||
props: { pages, products, featuredProducts, categories, brands },
|
||||
props: {
|
||||
featuredProducts,
|
||||
bestSellingProducts,
|
||||
newestProducts,
|
||||
categories,
|
||||
brands,
|
||||
pages,
|
||||
},
|
||||
revalidate: 10,
|
||||
}
|
||||
}
|
||||
|
||||
const nonNullable = (v: any) => v
|
||||
|
||||
export default function Home({
|
||||
products,
|
||||
featuredProducts,
|
||||
bestSellingProducts,
|
||||
newestProducts,
|
||||
categories,
|
||||
brands,
|
||||
}: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||
const { featured, bestSelling } = useMemo(() => {
|
||||
// Create a copy of products that we can mutate
|
||||
const products = [...newestProducts]
|
||||
// If the lists of featured and best selling products don't have enough
|
||||
// products, then fill them with products from the products list, this
|
||||
// is useful for new commerce sites that don't have a lot of products
|
||||
return {
|
||||
featured: rangeMap(
|
||||
6,
|
||||
(i) => featuredProducts[i] ?? products.shift()
|
||||
).filter(nonNullable),
|
||||
bestSelling: rangeMap(
|
||||
6,
|
||||
(i) => bestSellingProducts[i] ?? products.shift()
|
||||
).filter(nonNullable),
|
||||
}
|
||||
// Props from getStaticProps won't change
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="mt-3">
|
||||
<Grid>
|
||||
{featuredProducts.slice(0, 3).map((p: any) => (
|
||||
<ProductCard key={p.id} {...p} />
|
||||
{featured.slice(0, 3).map(({ node }) => (
|
||||
<ProductCard key={node.path} product={node} />
|
||||
))}
|
||||
</Grid>
|
||||
<Marquee variant="secondary">
|
||||
{products.slice(0, 3).map((p: any) => (
|
||||
<ProductCard key={p.id} {...p} variant="slim" />
|
||||
{bestSelling.slice(0, 3).map(({ node }) => (
|
||||
<ProductCard key={node.path} product={node} variant="slim" />
|
||||
))}
|
||||
</Marquee>
|
||||
<Hero
|
||||
@ -48,13 +85,13 @@ export default function Home({
|
||||
‘Natural’."
|
||||
/>
|
||||
<Grid layout="B">
|
||||
{featuredProducts.slice(3, 6).map((p: any) => (
|
||||
<ProductCard key={p.id} {...p} />
|
||||
{featured.slice(3, 6).map(({ node }) => (
|
||||
<ProductCard key={node.path} product={node} />
|
||||
))}
|
||||
</Grid>
|
||||
<Marquee>
|
||||
{products.slice(3, 6).map((p: any) => (
|
||||
<ProductCard key={p.id} {...p} variant="slim" />
|
||||
{bestSelling.slice(3, 6).map(({ node }) => (
|
||||
<ProductCard key={node.path} product={node} variant="slim" />
|
||||
))}
|
||||
</Marquee>
|
||||
<div className="py-12 flex flex-row w-full px-12">
|
||||
@ -84,8 +121,8 @@ export default function Home({
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Grid layout="normal">
|
||||
{products.map((p: any) => (
|
||||
<ProductCard key={p.id} {...p} variant="simple" />
|
||||
{newestProducts.map(({ node }) => (
|
||||
<ProductCard key={node.path} product={node} variant="simple" />
|
||||
))}
|
||||
</Grid>
|
||||
</div>
|
||||
|
@ -1,15 +1,15 @@
|
||||
import cn from 'classnames'
|
||||
import type { GetStaticPropsContext, InferGetStaticPropsType } from 'next'
|
||||
import Link from 'next/link'
|
||||
import { range } from 'lodash'
|
||||
import { useRouter } from 'next/router'
|
||||
import { GetStaticPropsContext, InferGetStaticPropsType } from 'next'
|
||||
import { Layout } from '@components/core'
|
||||
import { ProductCard } from '@components/product'
|
||||
import { Container, Grid, Skeleton } from '@components/ui'
|
||||
import getSlug from '@utils/get-slug'
|
||||
import useSearch from '@lib/bigcommerce/products/use-search'
|
||||
import getAllPages from '@lib/bigcommerce/api/operations/get-all-pages'
|
||||
import getSiteInfo from '@lib/bigcommerce/api/operations/get-site-info'
|
||||
import rangeMap from '@lib/range-map'
|
||||
import getSlug from '@utils/get-slug'
|
||||
import {
|
||||
filterQuery,
|
||||
getCategoryPath,
|
||||
@ -141,17 +141,19 @@ export default function Search({
|
||||
|
||||
{data ? (
|
||||
<Grid layout="normal">
|
||||
{data.products.map((p: any) => (
|
||||
{data.products.map(({ node }) => (
|
||||
<ProductCard
|
||||
key={node.path}
|
||||
className="animate__animated animate__fadeIn"
|
||||
{...p}
|
||||
product={node}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
) : (
|
||||
<Grid layout="normal">
|
||||
{range(12).map(() => (
|
||||
{rangeMap(12, (i) => (
|
||||
<Skeleton
|
||||
key={i}
|
||||
className="w-full animate__animated animate__fadeIn"
|
||||
height={325}
|
||||
/>
|
||||
|
18
yarn.lock
18
yarn.lock
@ -2206,6 +2206,13 @@
|
||||
dependencies:
|
||||
"@types/lodash" "*"
|
||||
|
||||
"@types/lodash.random@^3.2.6":
|
||||
version "3.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash.random/-/lodash.random-3.2.6.tgz#64b08abad168dca39c778ed40cce75b2f9e168eb"
|
||||
integrity sha512-RRr0pKm+3USvG/HTkuRKA8v2EqXu19VXC09j4VL2UQec8Yx8Fn6wYTPGjYdmX4UFd23ykS7SLFkiULS/rv8kTA==
|
||||
dependencies:
|
||||
"@types/lodash" "*"
|
||||
|
||||
"@types/lodash@*":
|
||||
version "4.14.161"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.161.tgz#a21ca0777dabc6e4f44f3d07f37b765f54188b18"
|
||||
@ -2244,9 +2251,9 @@
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*":
|
||||
version "16.9.52"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.52.tgz#c46c72d1a1d8d9d666f4dd2066c0e22600ccfde1"
|
||||
integrity sha512-EHRjmnxiNivwhGdMh9sz1Yw9AUxTSZFxKqdBWAAzyZx3sufWwx6ogqHYh/WB1m/I4ZpjkoZLExF5QTy2ekVi/Q==
|
||||
version "16.9.53"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.53.tgz#40cd4f8b8d6b9528aedd1fff8fcffe7a112a3d23"
|
||||
integrity sha512-4nW60Sd4L7+WMXH1D6jCdVftuW7j4Za6zdp6tJ33Rqv0nk1ZAmQKML9ZLD4H0dehA3FZxXR/GM8gXplf82oNGw==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
csstype "^3.0.2"
|
||||
@ -5511,6 +5518,11 @@ lodash.once@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
|
||||
|
||||
lodash.random@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.random/-/lodash.random-3.2.0.tgz#96e24e763333199130d2c9e2fd57f91703cc262d"
|
||||
integrity sha1-luJOdjMzGZEw0sni/Vf5FwPMJi0=
|
||||
|
||||
lodash.sortby@^4.7.0:
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
||||
|
Loading…
x
Reference in New Issue
Block a user