This commit is contained in:
Belen Curcio 2021-01-14 12:58:41 -03:00
parent fc34856e50
commit 7f70cfd868
15 changed files with 124 additions and 67 deletions

View File

@ -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>

View File

@ -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}

View File

@ -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>

View File

@ -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

View File

@ -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
} }

View File

@ -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

View File

@ -111,7 +111,7 @@ async function getProduct({
setProductLocaleMeta(product) setProductLocaleMeta(product)
} }
return { product: normalizeProduct(product) } return { product: normalizeProduct(product as any) }
} }
return {} return {}

View File

@ -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
View File

@ -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 {

View File

@ -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",

View File

@ -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 ?? []

View File

@ -1,7 +0,0 @@
import { Layout } from '@components/common'
export default function Home() {
return <>Hello</>
}
Home.Layout = Layout

View File

@ -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>

View File

@ -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"

View File

@ -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"