mirror of
https://github.com/vercel/commerce.git
synced 2025-06-29 18:01:21 +00:00
Merge branch 'master' into patch-2
This commit is contained in:
commit
586a5b6b6a
1
.gitignore
vendored
1
.gitignore
vendored
@ -25,6 +25,7 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
|
@ -89,8 +89,8 @@ Our commitment to Open Source can be found [here](https://vercel.com/oss).
|
||||
5. Duplicate `.env.template` and rename it to `.env.local`.
|
||||
6. Add proper store values to `.env.local`.
|
||||
7. Run `yarn dev` to build and watch for code changes
|
||||
8. The development branch is `development` (this is the branch pull requests should be made against).
|
||||
On a release, `develop` branch is rebased into `master`.
|
||||
8. The development branch is `canary` (this is the branch pull requests should be made against).
|
||||
On a release, `canary` branch is rebased into `master`.
|
||||
|
||||
|
||||
|
||||
|
@ -102,8 +102,6 @@ a {
|
||||
}
|
||||
|
||||
.animated {
|
||||
-webkit-animation-duration: 1s;
|
||||
animation-duration: 1s;
|
||||
-webkit-animation-duration: 1s;
|
||||
animation-duration: 1s;
|
||||
-webkit-animation-fill-mode: both;
|
||||
|
@ -8,6 +8,13 @@ import useUpdateItem from '@framework/cart/use-update-item'
|
||||
import useRemoveItem from '@framework/cart/use-remove-item'
|
||||
import s from './CartItem.module.css'
|
||||
|
||||
type ItemOption = {
|
||||
name: string,
|
||||
nameId: number,
|
||||
value: string,
|
||||
valueId: number
|
||||
}
|
||||
|
||||
const CartItem = ({
|
||||
item,
|
||||
currencyCode,
|
||||
@ -88,12 +95,20 @@ const CartItem = ({
|
||||
<div className="flex-1 flex flex-col text-base">
|
||||
{/** TODO: Replace this. No `path` found at Cart */}
|
||||
<Link href={`/product/${item.url.split('/')[3]}`}>
|
||||
<span className="font-bold mb-5 text-lg cursor-pointer">
|
||||
<span className="font-bold text-lg cursor-pointer leading-6">
|
||||
{item.name}
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
<div className="flex items-center">
|
||||
{item.options && item.options.length > 0 ? (
|
||||
<div className="">
|
||||
{item.options.map((option:ItemOption, i: number) =>
|
||||
<span key={`${item.id}-${option.name}`} className="text-sm font-semibold text-accents-7">
|
||||
{option.value}{ i === item.options.length -1 ? "" : ", " }
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex items-center mt-3">
|
||||
<button type="button" onClick={() => increaseQuantity(-1)}>
|
||||
<Minus width={18} height={18} />
|
||||
</button>
|
||||
|
@ -29,7 +29,7 @@
|
||||
}
|
||||
|
||||
.item {
|
||||
@apply flex cursor-pointer px-6 py-3 flex transition ease-in-out duration-150 text-primary leading-6 font-medium items-center;
|
||||
@apply flex cursor-pointer px-6 py-3 transition ease-in-out duration-150 text-primary leading-6 font-medium items-center;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
|
@ -61,16 +61,16 @@ const Layout: FC<Props> = ({ children, pageProps }) => {
|
||||
<main className="fit">{children}</main>
|
||||
<Footer pages={pageProps.pages} />
|
||||
|
||||
<Sidebar open={displaySidebar} onClose={closeSidebar}>
|
||||
<CartSidebarView />
|
||||
</Sidebar>
|
||||
|
||||
<Modal open={displayModal} onClose={closeModal}>
|
||||
{modalView === 'LOGIN_VIEW' && <LoginView />}
|
||||
{modalView === 'SIGNUP_VIEW' && <SignUpView />}
|
||||
{modalView === 'FORGOT_VIEW' && <ForgotPassword />}
|
||||
</Modal>
|
||||
|
||||
<Sidebar open={displaySidebar} onClose={closeSidebar}>
|
||||
<CartSidebarView />
|
||||
</Sidebar>
|
||||
|
||||
<FeatureBar
|
||||
title="This site uses cookies to improve your experience. By clicking, you agree to our Privacy Policy."
|
||||
hide={acceptedCookies}
|
||||
|
@ -32,6 +32,7 @@ const ProductCard: FC<Props> = ({
|
||||
imgLayout = 'responsive',
|
||||
}) => {
|
||||
const src = p.images.edges?.[0]?.node?.urlOriginal!
|
||||
const placeholderImg = '/product-img-placeholder.svg';
|
||||
const { price } = usePrice({
|
||||
amount: p.prices?.price?.value,
|
||||
baseAmount: p.prices?.retailPrice?.value,
|
||||
@ -58,7 +59,7 @@ const ProductCard: FC<Props> = ({
|
||||
layout={imgLayout}
|
||||
loading={imgLoading}
|
||||
priority={imgPriority}
|
||||
src={p.images.edges?.[0]?.node.urlOriginal!}
|
||||
src={p.images.edges?.[0]?.node.urlOriginal! || placeholderImg}
|
||||
alt={p.images.edges?.[0]?.node.altText || 'Product Image'}
|
||||
/>
|
||||
</div>
|
||||
@ -81,7 +82,7 @@ const ProductCard: FC<Props> = ({
|
||||
<div className={s.imageContainer}>
|
||||
<Image
|
||||
quality="85"
|
||||
src={src}
|
||||
src={src || placeholderImg}
|
||||
alt={p.name}
|
||||
className={s.productImage}
|
||||
width={imgWidth}
|
||||
|
@ -38,15 +38,14 @@ const ProductView: FC<Props> = ({ product }) => {
|
||||
size: null,
|
||||
color: null,
|
||||
})
|
||||
const variant =
|
||||
getCurrentVariant(product, choices) || product.variants.edges?.[0]
|
||||
const variant = getCurrentVariant(product, choices)
|
||||
|
||||
const addToCart = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
await addItem({
|
||||
productId: product.entityId,
|
||||
variantId: product.variants.edges?.[0]?.node.entityId!,
|
||||
variantId: variant?.node.entityId!,
|
||||
})
|
||||
openSidebar()
|
||||
setLoading(false)
|
||||
@ -156,7 +155,7 @@ const ProductView: FC<Props> = ({ product }) => {
|
||||
<WishlistButton
|
||||
className={s.wishlistButton}
|
||||
productId={product.entityId}
|
||||
variant={product.variants.edges?.[0]!}
|
||||
variant={variant!}
|
||||
/>
|
||||
</div>
|
||||
</Container>
|
||||
|
@ -32,8 +32,10 @@ export function getProductOptions(product: ProductNode) {
|
||||
export function getCurrentVariant(product: ProductNode, opts: SelectedOptions) {
|
||||
const variant = product.variants.edges?.find((edge) => {
|
||||
const { node } = edge ?? {}
|
||||
const numberOfDefinedOpts = Object.values(opts).filter(value => value !== null).length;
|
||||
const numberOfEdges = node?.productOptions?.edges?.length;
|
||||
|
||||
return Object.entries(opts).every(([key, value]) =>
|
||||
const isEdgeEqualToOption = ([key, value]:[string, string | null]) =>
|
||||
node?.productOptions.edges?.find((edge) => {
|
||||
if (
|
||||
edge?.node.__typename === 'MultipleChoiceOption' &&
|
||||
@ -43,9 +45,12 @@ export function getCurrentVariant(product: ProductNode, opts: SelectedOptions) {
|
||||
(valueEdge) => valueEdge?.node.label === value
|
||||
)
|
||||
}
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
return numberOfDefinedOpts === numberOfEdges ?
|
||||
Object.entries(opts).every(isEdgeEqualToOption)
|
||||
: Object.entries(opts).some(isEdgeEqualToOption)
|
||||
})
|
||||
|
||||
return variant
|
||||
return variant ?? product.variants.edges?.[0]
|
||||
}
|
||||
|
@ -90,6 +90,7 @@ function uiReducer(state: State, action: Action) {
|
||||
return {
|
||||
...state,
|
||||
displayModal: true,
|
||||
displaySidebar: false,
|
||||
}
|
||||
}
|
||||
case 'CLOSE_MODAL': {
|
||||
|
@ -26,8 +26,8 @@ const addItem: CartHandlers['addItem'] = async ({
|
||||
}),
|
||||
}
|
||||
const { data } = cartId
|
||||
? await config.storeApiFetch(`/v3/carts/${cartId}/items`, options)
|
||||
: await config.storeApiFetch('/v3/carts', options)
|
||||
? await config.storeApiFetch(`/v3/carts/${cartId}/items?include=line_items.physical_items.options`, options)
|
||||
: await config.storeApiFetch('/v3/carts?include=line_items.physical_items.options', options)
|
||||
|
||||
// Create or update the cart cookie
|
||||
res.setHeader(
|
||||
|
@ -12,7 +12,7 @@ const getCart: CartHandlers['getCart'] = async ({
|
||||
|
||||
if (cartId) {
|
||||
try {
|
||||
result = await config.storeApiFetch(`/v3/carts/${cartId}`)
|
||||
result = await config.storeApiFetch(`/v3/carts/${cartId}?include=line_items.physical_items.options`)
|
||||
} catch (error) {
|
||||
if (error instanceof BigcommerceApiError && error.status === 404) {
|
||||
// Remove the cookie if it exists but the cart wasn't found
|
||||
|
@ -15,7 +15,7 @@ const removeItem: CartHandlers['removeItem'] = async ({
|
||||
}
|
||||
|
||||
const result = await config.storeApiFetch<{ data: any } | null>(
|
||||
`/v3/carts/${cartId}/items/${itemId}`,
|
||||
`/v3/carts/${cartId}/items/${itemId}?include=line_items.physical_items.options`,
|
||||
{ method: 'DELETE' }
|
||||
)
|
||||
const data = result?.data ?? null
|
||||
|
@ -16,7 +16,7 @@ const updateItem: CartHandlers['updateItem'] = async ({
|
||||
}
|
||||
|
||||
const { data } = await config.storeApiFetch(
|
||||
`/v3/carts/${cartId}/items/${itemId}`,
|
||||
`/v3/carts/${cartId}/items/${itemId}?include=line_items.physical_items.options`,
|
||||
{
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
|
@ -26,8 +26,7 @@ export const fetcher: HookFetcher<SearchProductsData, SearchProductsInput> = (
|
||||
if (search) url.searchParams.set('search', search)
|
||||
if (Number.isInteger(categoryId))
|
||||
url.searchParams.set('category', String(categoryId))
|
||||
if (Number.isInteger(categoryId))
|
||||
url.searchParams.set('brand', String(brandId))
|
||||
if (Number.isInteger(brandId)) url.searchParams.set('brand', String(brandId))
|
||||
if (sort) url.searchParams.set('sort', sort)
|
||||
|
||||
return fetch({
|
||||
|
@ -65,7 +65,7 @@ export default function Pages({
|
||||
page,
|
||||
}: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto py-20">
|
||||
<div className="max-w-2xl mx-8 sm:mx-auto py-20">
|
||||
{page?.body && <Text html={page.body} />}
|
||||
</div>
|
||||
)
|
||||
|
@ -44,6 +44,7 @@ export async function getStaticProps({
|
||||
// We prefer to do the computation at buildtime/servertime
|
||||
const { featured, bestSelling } = (() => {
|
||||
// Create a copy of products that we can mutate
|
||||
// Filter products that do not have images
|
||||
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
|
||||
|
@ -106,9 +106,9 @@ export default function Search({
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
@ -205,9 +205,9 @@ export default function Search({
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
@ -378,9 +378,9 @@ export default function Search({
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
7
public/product-img-placeholder.svg
Normal file
7
public/product-img-placeholder.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800">
|
||||
<defs/>
|
||||
<g fill="none" fill-rule="nonzero">
|
||||
<path fill="#EAEAEA" d="M0 0h800v800H0z"/>
|
||||
<path fill="#FFF" d="M366.333 365.833c0 5.695-1.993 10.535-5.979 14.521-3.986 3.986-8.826 5.98-14.52 5.98-5.695 0-10.535-1.994-14.522-5.98-3.986-3.986-5.979-8.826-5.979-14.52 0-5.695 1.993-10.535 5.98-14.522 3.986-3.986 8.826-5.979 14.52-5.979 5.695 0 10.535 1.993 14.521 5.98 3.986 3.986 5.98 8.826 5.98 14.52zm109.334 41v47.834H325.333v-20.5L359.5 400l17.083 17.083 54.667-54.666 44.417 44.416zm10.25-75.166H315.083c-.925 0-1.726.338-2.402 1.014-.676.676-1.014 1.477-1.014 2.402v129.834c0 .925.338 1.726 1.014 2.402.676.676 1.477 1.014 2.402 1.014h170.834c.925 0 1.726-.338 2.402-1.014.676-.676 1.014-1.477 1.014-2.402V335.083c0-.925-.338-1.726-1.014-2.402-.676-.676-1.477-1.014-2.402-1.014zM503 335.083v129.834c0 4.698-1.673 8.72-5.018 12.065-3.346 3.345-7.367 5.018-12.065 5.018H315.083c-4.698 0-8.72-1.673-12.065-5.018-3.345-3.346-5.018-7.367-5.018-12.065V335.083c0-4.698 1.673-8.72 5.018-12.065 3.346-3.345 7.367-5.018 12.065-5.018h170.834c4.698 0 8.72 1.673 12.065 5.018 3.345 3.346 5.018 7.367 5.018 12.065z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
Loading…
x
Reference in New Issue
Block a user