mirror of
https://github.com/vercel/commerce.git
synced 2025-04-23 19:37:51 +00:00
Changes
This commit is contained in:
parent
fc34856e50
commit
7f70cfd868
@ -12,7 +12,7 @@ const CartItem = ({
|
|||||||
item,
|
item,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
}: {
|
}: {
|
||||||
item: any
|
item: CartItem
|
||||||
currencyCode: string
|
currencyCode: string
|
||||||
}) => {
|
}) => {
|
||||||
const { price } = usePrice({
|
const { price } = usePrice({
|
||||||
@ -55,7 +55,7 @@ const CartItem = ({
|
|||||||
try {
|
try {
|
||||||
// If this action succeeds then there's no need to do `setRemoving(true)`
|
// If this action succeeds then there's no need to do `setRemoving(true)`
|
||||||
// because the component will be removed from the view
|
// because the component will be removed from the view
|
||||||
await removeItem({ id: item.id })
|
await removeItem({ id: String(item.id) })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setRemoving(false)
|
setRemoving(false)
|
||||||
}
|
}
|
||||||
@ -77,16 +77,14 @@ const CartItem = ({
|
|||||||
<div className="w-16 h-16 bg-violet relative overflow-hidden">
|
<div className="w-16 h-16 bg-violet relative overflow-hidden">
|
||||||
<Image
|
<Image
|
||||||
className={s.productImage}
|
className={s.productImage}
|
||||||
src={item.image_url}
|
|
||||||
width={150}
|
width={150}
|
||||||
height={150}
|
height={150}
|
||||||
alt="Product Image"
|
src={item.images[0].url}
|
||||||
// The cart item image is already optimized and very small in size
|
alt={item.images[0].alt}
|
||||||
unoptimized
|
unoptimized
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 flex flex-col text-base">
|
<div className="flex-1 flex flex-col text-base">
|
||||||
{/** TODO: Replace this. No `path` found at Cart */}
|
|
||||||
<Link href={`/product/${item.url.split('/')[3]}`}>
|
<Link href={`/product/${item.url.split('/')[3]}`}>
|
||||||
<span className="font-bold mb-5 text-lg cursor-pointer">
|
<span className="font-bold mb-5 text-lg cursor-pointer">
|
||||||
{item.name}
|
{item.name}
|
||||||
@ -115,7 +113,10 @@ const CartItem = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col justify-between space-y-2 text-base">
|
<div className="flex flex-col justify-between space-y-2 text-base">
|
||||||
<span>{price}</span>
|
<span>{price}</span>
|
||||||
<button className="flex justify-end" onClick={handleRemove}>
|
<button
|
||||||
|
className="flex justify-end outline-none"
|
||||||
|
onClick={handleRemove}
|
||||||
|
>
|
||||||
<Trash />
|
<Trash />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,19 +16,17 @@ const CartSidebarView: FC = () => {
|
|||||||
const { price: subTotal } = usePrice(
|
const { price: subTotal } = usePrice(
|
||||||
data && {
|
data && {
|
||||||
amount: data.base_amount,
|
amount: data.base_amount,
|
||||||
currencyCode: data.currency.code,
|
currencyCode: data.currency?.code || 'USD',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const { price: total } = usePrice(
|
const { price: total } = usePrice(
|
||||||
data && {
|
data && {
|
||||||
amount: data.cart_amount,
|
amount: data.cart_amount,
|
||||||
currencyCode: data.currency.code,
|
currencyCode: data.currency?.code || 'USD',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const handleClose = () => closeSidebar()
|
const handleClose = () => closeSidebar()
|
||||||
|
|
||||||
const items = data?.line_items.physical_items ?? []
|
|
||||||
|
|
||||||
const error = null
|
const error = null
|
||||||
const success = null
|
const success = null
|
||||||
|
|
||||||
@ -95,7 +93,7 @@ const CartSidebarView: FC = () => {
|
|||||||
My Cart
|
My Cart
|
||||||
</h2>
|
</h2>
|
||||||
<ul className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-accents-3 border-t border-accents-3">
|
<ul className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-accents-3 border-t border-accents-3">
|
||||||
{items.map((item: any) => (
|
{data.products.map((item: any) => (
|
||||||
<CartItem
|
<CartItem
|
||||||
key={item.id}
|
key={item.id}
|
||||||
item={item}
|
item={item}
|
||||||
|
@ -146,7 +146,7 @@ const ProductView: FC<Props> = ({ product }) => {
|
|||||||
className={s.button}
|
className={s.button}
|
||||||
onClick={addToCart}
|
onClick={addToCart}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={!variant}
|
disabled={!variant && product.options.length > 0}
|
||||||
>
|
>
|
||||||
Add to Cart
|
Add to Cart
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { Product } from 'framework/types'
|
||||||
import getAllProducts, { ProductEdge } from '../../operations/get-all-products'
|
import getAllProducts, { ProductEdge } from '../../operations/get-all-products'
|
||||||
import type { ProductsHandlers } from '../products'
|
import type { ProductsHandlers } from '../products'
|
||||||
|
|
||||||
@ -6,6 +7,7 @@ const SORT: { [key: string]: string | undefined } = {
|
|||||||
trending: 'total_sold',
|
trending: 'total_sold',
|
||||||
price: 'price',
|
price: 'price',
|
||||||
}
|
}
|
||||||
|
|
||||||
const LIMIT = 12
|
const LIMIT = 12
|
||||||
|
|
||||||
// Return current cart info
|
// Return current cart info
|
||||||
@ -44,21 +46,25 @@ const getProducts: ProductsHandlers['getProducts'] = async ({
|
|||||||
const { data } = await config.storeApiFetch<{ data: { id: number }[] }>(
|
const { data } = await config.storeApiFetch<{ data: { id: number }[] }>(
|
||||||
url.pathname + url.search
|
url.pathname + url.search
|
||||||
)
|
)
|
||||||
|
|
||||||
const entityIds = data.map((p) => p.id)
|
const entityIds = data.map((p) => p.id)
|
||||||
const found = entityIds.length > 0
|
const found = entityIds.length > 0
|
||||||
|
|
||||||
// We want the GraphQL version of each product
|
// We want the GraphQL version of each product
|
||||||
const graphqlData = await getAllProducts({
|
const graphqlData = await getAllProducts({
|
||||||
variables: { first: LIMIT, entityIds },
|
variables: { first: LIMIT, entityIds },
|
||||||
config,
|
config,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Put the products in an object that we can use to get them by id
|
// Put the products in an object that we can use to get them by id
|
||||||
const productsById = graphqlData.products.reduce<{
|
const productsById = graphqlData.products.reduce<{
|
||||||
[k: number]: ProductEdge
|
[k: number]: Product
|
||||||
}>((prods, p) => {
|
}>((prods, p) => {
|
||||||
prods[p.node.entityId] = p
|
prods[p.id] = p
|
||||||
return prods
|
return prods
|
||||||
}, {})
|
}, {})
|
||||||
const products: ProductEdge[] = found ? [] : graphqlData.products
|
|
||||||
|
const products: Product[] = found ? [] : graphqlData.products
|
||||||
|
|
||||||
// Populate the products array with the graphql products, in the order
|
// Populate the products array with the graphql products, in the order
|
||||||
// assigned by the list of entity ids
|
// assigned by the list of entity ids
|
||||||
|
@ -4,11 +4,11 @@ import createApiHandler, {
|
|||||||
BigcommerceHandler,
|
BigcommerceHandler,
|
||||||
} from '../utils/create-api-handler'
|
} from '../utils/create-api-handler'
|
||||||
import { BigcommerceApiError } from '../utils/errors'
|
import { BigcommerceApiError } from '../utils/errors'
|
||||||
import type { ProductEdge } from '../operations/get-all-products'
|
|
||||||
import getProducts from './handlers/get-products'
|
import getProducts from './handlers/get-products'
|
||||||
|
import { Product } from 'framework/types'
|
||||||
|
|
||||||
export type SearchProductsData = {
|
export type SearchProductsData = {
|
||||||
products: ProductEdge[]
|
products: Product[]
|
||||||
found: boolean
|
found: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ async function getAllProducts({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return { products: products.map(({ node }) => normalizeProduct(node)) }
|
return { products: products.map(({ node }) => normalizeProduct(node as any)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default getAllProducts
|
export default getAllProducts
|
||||||
|
@ -111,7 +111,7 @@ async function getProduct({
|
|||||||
setProductLocaleMeta(product)
|
setProductLocaleMeta(product)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { product: normalizeProduct(product) }
|
return { product: normalizeProduct(product as any) }
|
||||||
}
|
}
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Product as BCProduct } from '@framework/schema'
|
import { Cart, CartItem, Product } from '../../types'
|
||||||
|
import { Product as BigCommerceProduct } from '@framework/schema'
|
||||||
|
|
||||||
function normalizeProductOption({
|
function normalizeProductOption({
|
||||||
node: {
|
node: {
|
||||||
@ -14,7 +15,7 @@ function normalizeProductOption({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeProduct(productNode: BCProduct): Product {
|
export function normalizeProduct(productNode: BigCommerceProduct): Product {
|
||||||
const {
|
const {
|
||||||
entityId: id,
|
entityId: id,
|
||||||
images,
|
images,
|
||||||
@ -67,12 +68,47 @@ export function normalizeProduct(productNode: BCProduct): Product {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeCart({ data, ...rest }: any) {
|
export function normalizeCart({ data, ...rest }: any): Cart {
|
||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
data: {
|
data: {
|
||||||
products: data?.line_items?.physical_items ?? [],
|
products: data?.line_items?.physical_items.map(itemsToProducts) ?? [],
|
||||||
...data,
|
...data,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function itemsToProducts({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
quantity,
|
||||||
|
product_id,
|
||||||
|
variant_id,
|
||||||
|
image_url,
|
||||||
|
list_price,
|
||||||
|
sale_price,
|
||||||
|
extended_list_price,
|
||||||
|
extended_sale_price,
|
||||||
|
...rest
|
||||||
|
}: any): CartItem {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
prices: {
|
||||||
|
listPrice: list_price,
|
||||||
|
salePrice: sale_price,
|
||||||
|
extendedListPrice: extended_list_price,
|
||||||
|
extendedSalePrice: extended_sale_price,
|
||||||
|
},
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
alt: name,
|
||||||
|
url: image_url,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
productId: product_id,
|
||||||
|
variantId: variant_id,
|
||||||
|
quantity,
|
||||||
|
...rest,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
19
framework/types.d.ts
vendored
19
framework/types.d.ts
vendored
@ -1,3 +1,5 @@
|
|||||||
|
import { CartItem } from '@components/cart'
|
||||||
|
|
||||||
interface Entity {
|
interface Entity {
|
||||||
id: string | number
|
id: string | number
|
||||||
[prop: string]: any
|
[prop: string]: any
|
||||||
@ -12,6 +14,7 @@ interface Product extends Entity {
|
|||||||
variants: ProductVariant[]
|
variants: ProductVariant[]
|
||||||
price: ProductPrice
|
price: ProductPrice
|
||||||
options: ProductOption[]
|
options: ProductOption[]
|
||||||
|
sku?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ProductOption extends Entity {
|
interface ProductOption extends Entity {
|
||||||
@ -37,8 +40,11 @@ interface ProductVariant {
|
|||||||
interface ProductPrice {
|
interface ProductPrice {
|
||||||
value: number
|
value: number
|
||||||
currencyCode: 'USD' | 'ARS' | string | undefined
|
currencyCode: 'USD' | 'ARS' | string | undefined
|
||||||
retailValue?: number
|
retailPrice?: number
|
||||||
saleValue?: number
|
salePrice?: number
|
||||||
|
listPrice?: number
|
||||||
|
extendedSalePrice?: number
|
||||||
|
extendedListPrice?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Cart extends Entity {
|
interface Cart extends Entity {
|
||||||
@ -46,7 +52,14 @@ interface Cart extends Entity {
|
|||||||
currency: { code: string }
|
currency: { code: string }
|
||||||
taxIncluded?: boolean
|
taxIncluded?: boolean
|
||||||
totalAmmount: number | string
|
totalAmmount: number | string
|
||||||
products: Pick<Product, 'id' | 'name' | 'prices'>[]
|
products: Pick<Product, 'id' | 'name' | 'prices'> & CartItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CartItem extends Entity {
|
||||||
|
quantity: number
|
||||||
|
productId: Product['id']
|
||||||
|
variantId: ProductVariant['id']
|
||||||
|
images: ProductImage[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Wishlist extends Entity {
|
interface Wishlist extends Entity {
|
||||||
|
@ -60,6 +60,7 @@
|
|||||||
"next": "^10.0.5",
|
"next": "^10.0.5",
|
||||||
"next-seo": "^4.11.0",
|
"next-seo": "^4.11.0",
|
||||||
"next-themes": "^0.0.4",
|
"next-themes": "^0.0.4",
|
||||||
|
"normalizr": "^3.6.1",
|
||||||
"postcss-nesting": "^7.0.1",
|
"postcss-nesting": "^7.0.1",
|
||||||
"react": "^16.14.0",
|
"react": "^16.14.0",
|
||||||
"react-dom": "^16.14.0",
|
"react-dom": "^16.14.0",
|
||||||
|
@ -29,20 +29,18 @@ export default function Cart() {
|
|||||||
return <div>Loading</div>
|
return <div>Loading</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Cart Data', data)
|
const { price: subTotal } = usePrice(
|
||||||
|
data && {
|
||||||
// const { price: subTotal } = usePrice(
|
amount: data.base_amount,
|
||||||
// data && {
|
currencyCode: data.currency.code,
|
||||||
// amount: data.base_amount,
|
}
|
||||||
// currencyCode: data.currency.code,
|
)
|
||||||
// }
|
const { price: total } = usePrice(
|
||||||
// )
|
data && {
|
||||||
// const { price: total } = usePrice(
|
amount: data.cart_amount,
|
||||||
// data && {
|
currencyCode: data.currency.code,
|
||||||
// amount: data.cart_amount,
|
}
|
||||||
// currencyCode: data.currency.code,
|
)
|
||||||
// }
|
|
||||||
// )
|
|
||||||
|
|
||||||
// const items = data?.line_items.physical_items ?? []
|
// const items = data?.line_items.physical_items ?? []
|
||||||
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import { Layout } from '@components/common'
|
|
||||||
|
|
||||||
export default function Home() {
|
|
||||||
return <>Hello</>
|
|
||||||
}
|
|
||||||
|
|
||||||
Home.Layout = Layout
|
|
@ -3,16 +3,29 @@ import type { GetStaticPropsContext, InferGetStaticPropsType } from 'next'
|
|||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { getConfig } from '@framework/api'
|
|
||||||
import getAllPages from '@framework/api/operations/get-all-pages'
|
|
||||||
import getSiteInfo from '@framework/api/operations/get-site-info'
|
|
||||||
import useSearch from '@framework/product/use-search'
|
|
||||||
import { Layout } from '@components/common'
|
import { Layout } from '@components/common'
|
||||||
import { ProductCard } from '@components/product'
|
import { ProductCard } from '@components/product'
|
||||||
import { Container, Grid, Skeleton } from '@components/ui'
|
import { Container, Grid, Skeleton } from '@components/ui'
|
||||||
|
|
||||||
|
import { getConfig } from '@framework/api'
|
||||||
|
import getAllPages from '@framework/api/operations/get-all-pages'
|
||||||
|
import getSiteInfo from '@framework/api/operations/get-site-info'
|
||||||
|
import useSearch from '@framework/product/use-search'
|
||||||
|
|
||||||
import rangeMap from '@lib/range-map'
|
import rangeMap from '@lib/range-map'
|
||||||
|
|
||||||
|
// TODO(bc) Remove this. This should come from the API
|
||||||
import getSlug from '@lib/get-slug'
|
import getSlug from '@lib/get-slug'
|
||||||
|
|
||||||
|
// TODO (bc) : Remove or standarize this.
|
||||||
|
const SORT = Object.entries({
|
||||||
|
'latest-desc': 'Latest arrivals',
|
||||||
|
'trending-desc': 'Trending',
|
||||||
|
'price-asc': 'Price: Low to high',
|
||||||
|
'price-desc': 'Price: High to low',
|
||||||
|
})
|
||||||
|
|
||||||
import {
|
import {
|
||||||
filterQuery,
|
filterQuery,
|
||||||
getCategoryPath,
|
getCategoryPath,
|
||||||
@ -27,19 +40,11 @@ export async function getStaticProps({
|
|||||||
const config = getConfig({ locale })
|
const config = getConfig({ locale })
|
||||||
const { pages } = await getAllPages({ config, preview })
|
const { pages } = await getAllPages({ config, preview })
|
||||||
const { categories, brands } = await getSiteInfo({ config, preview })
|
const { categories, brands } = await getSiteInfo({ config, preview })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: { pages, categories, brands },
|
props: { pages, categories, brands },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SORT = Object.entries({
|
|
||||||
'latest-desc': 'Latest arrivals',
|
|
||||||
'trending-desc': 'Trending',
|
|
||||||
'price-asc': 'Price: Low to high',
|
|
||||||
'price-desc': 'Price: High to low',
|
|
||||||
})
|
|
||||||
|
|
||||||
export default function Search({
|
export default function Search({
|
||||||
categories,
|
categories,
|
||||||
brands,
|
brands,
|
||||||
@ -76,7 +81,6 @@ export default function Search({
|
|||||||
} else {
|
} else {
|
||||||
setToggleFilter(!toggleFilter)
|
setToggleFilter(!toggleFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
setActiveFilter(filter)
|
setActiveFilter(filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,14 +337,16 @@ export default function Search({
|
|||||||
|
|
||||||
{data ? (
|
{data ? (
|
||||||
<Grid layout="normal">
|
<Grid layout="normal">
|
||||||
{data.products.map(({ node }) => (
|
{data.products.map((product) => (
|
||||||
<ProductCard
|
<ProductCard
|
||||||
variant="simple"
|
variant="simple"
|
||||||
key={node.path}
|
key={product.path}
|
||||||
className="animated fadeIn"
|
className="animated fadeIn"
|
||||||
product={node}
|
product={product}
|
||||||
imgWidth={480}
|
imgProps={{
|
||||||
imgHeight={480}
|
width: 480,
|
||||||
|
height: 480,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"next-env.d.ts",
|
"next-env.d.ts",
|
||||||
"./framework/types.d.ts",
|
"framework/types.d.ts",
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
"**/*.js"
|
"**/*.js"
|
||||||
|
@ -5195,6 +5195,11 @@ normalize.css@^8.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3"
|
resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3"
|
||||||
integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==
|
integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==
|
||||||
|
|
||||||
|
normalizr@^3.6.1:
|
||||||
|
version "3.6.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/normalizr/-/normalizr-3.6.1.tgz#d367ab840e031ff382141b8d81ce279292ff69fe"
|
||||||
|
integrity sha512-8iEmqXmPtll8PwbEFrbPoDxVw7MKnNvt3PZzR2Xvq9nggEEOgBlNICPXYzyZ4w4AkHUzCU998mdatER3n2VaMA==
|
||||||
|
|
||||||
npmlog@^4.0.1, npmlog@^4.1.2:
|
npmlog@^4.0.1, npmlog@^4.1.2:
|
||||||
version "4.1.2"
|
version "4.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
|
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user