fix: Add to cart buttons on homepage

This commit is contained in:
Sol Irvine 2023-08-23 23:24:03 -07:00
parent f43226198d
commit d9838f8807
7 changed files with 100 additions and 19 deletions

View File

@ -25,5 +25,5 @@ button {
} }
[lang='ja'] .font-multilingual { [lang='ja'] .font-multilingual {
@apply font-japan; @apply font-japan tracking-widest;
} }

View File

@ -1,4 +1,3 @@
import { ThreeItemGrid } from 'components/grid/three-items';
import Footer from 'components/layout/footer'; import Footer from 'components/layout/footer';
import { SupportedLocale } from 'components/layout/navbar/language-control'; import { SupportedLocale } from 'components/layout/navbar/language-control';
@ -11,6 +10,7 @@ import HomeImage006 from '@images/home-images/home-image-006.webp';
import HomeImage007 from '@images/home-images/home-image-007.webp'; import HomeImage007 from '@images/home-images/home-image-007.webp';
import HomeImage008 from '@images/home-images/home-image-008.webp'; import HomeImage008 from '@images/home-images/home-image-008.webp';
import clsx from 'clsx'; import clsx from 'clsx';
import { HomepageProducts } from 'components/grid/homepage-products';
import AboutNaraiPreview from 'components/layout/about-narai-preview'; import AboutNaraiPreview from 'components/layout/about-narai-preview';
import ConceptPreview from 'components/layout/concept-preview'; import ConceptPreview from 'components/layout/concept-preview';
import LocationPreview from 'components/layout/location-preview'; import LocationPreview from 'components/layout/location-preview';
@ -52,7 +52,7 @@ export default async function HomePage({
<div> <div>
<Navbar cart={cart} locale={locale} /> <Navbar cart={cart} locale={locale} />
<div className="pt-12 md:pt-48"> <div className="pt-12 md:pt-48">
<ThreeItemGrid lang={locale} /> <HomepageProducts lang={locale} />
</div> </div>
<div className="py-48"> <div className="py-48">
<NewsletterSignup /> <NewsletterSignup />

View File

@ -0,0 +1,75 @@
'use client';
import clsx from 'clsx';
import { addItem } from 'components/cart/actions';
import LoadingDots from 'components/loading-dots';
import { ProductVariant } from 'lib/shopify/types';
import { useTranslations } from 'next-intl';
import { useRouter, useSearchParams } from 'next/navigation';
import { useTransition } from 'react';
export function InlineAddToCart({
variants,
availableForSale
}: {
variants: ProductVariant[];
availableForSale: boolean;
}) {
const router = useRouter();
const searchParams = useSearchParams();
const t = useTranslations('Index');
const [isPending, startTransition] = useTransition();
const defaultVariantId = variants.length === 1 ? variants[0]?.id : undefined;
const variant = variants.find((variant: ProductVariant) =>
variant.selectedOptions.every(
(option) => option.value === searchParams.get(option.name.toLowerCase())
)
);
const selectedVariantId = variant?.id || defaultVariantId;
const title = !availableForSale
? 'Out of stock'
: !selectedVariantId
? 'Please select options'
: undefined;
return (
<button
aria-label="Add item to cart"
disabled={isPending || !availableForSale || !selectedVariantId}
title={title}
onClick={() => {
// Safeguard in case someone messes with `disabled` in devtools.
if (!availableForSale || !selectedVariantId) return;
startTransition(async () => {
const error = await addItem(selectedVariantId);
if (error) {
// Trigger the error boundary in the root error.js
throw new Error(error.toString());
}
router.refresh();
});
}}
className={clsx(
'p-4',
'relative flex w-full items-center justify-center',
'border border-white/20 hover:border-white',
'font-serif text-base tracking-wider text-white',
'transition-colors duration-150',
{
'cursor-not-allowed opacity-60 hover:opacity-60': !availableForSale || !selectedVariantId,
'cursor-not-allowed': isPending
}
)}
>
{!isPending ? (
<span>{availableForSale ? t('cart.add') : t('cart.out-of-stock')}</span>
) : (
<LoadingDots className="my-3 bg-white" />
)}
</button>
);
}

View File

@ -1,4 +1,5 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { InlineAddToCart } from 'components/cart/inline-add-to-cart';
import { SupportedLocale } from 'components/layout/navbar/language-control'; import { SupportedLocale } from 'components/layout/navbar/language-control';
import { getCollectionProducts } from 'lib/shopify'; import { getCollectionProducts } from 'lib/shopify';
import type { Product } from 'lib/shopify/types'; import type { Product } from 'lib/shopify/types';
@ -6,13 +7,17 @@ import Link from 'next/link';
import Label from '../label'; import Label from '../label';
import { GridTileImage } from './tile'; import { GridTileImage } from './tile';
function ThreeItemGridItem({ item, priority }: { item: Product; priority?: boolean }) { function HomepageProductsItem({ item, priority }: { item: Product; priority?: boolean }) {
const size = item?.variants?.[0]?.selectedOptions?.find((option) => option.name === 'Size'); const size = item?.variants?.[0]?.selectedOptions?.find((option) => option.name === 'Size');
const image = item?.variants?.[0]?.image; const image = item?.variants?.[0]?.image;
return !!image ? ( return !!image ? (
<div className={clsx('col-span-1 row-span-1 md:col-span-2 md:row-span-1')}> <div
<Link className="w-full bg-black/30" href={`/product/${item.handle}`}> className={clsx(
'col-span-1 row-span-1 flex flex-col justify-between space-y-6 md:col-span-2 md:row-span-1'
)}
>
<Link className="block w-full" href={`/product/${item.handle}`}>
<div className="relative block aspect-tall overflow-hidden "> <div className="relative block aspect-tall overflow-hidden ">
<GridTileImage <GridTileImage
src={image?.url} src={image?.url}
@ -32,11 +37,12 @@ function ThreeItemGridItem({ item, priority }: { item: Product; priority?: boole
<div className="line-clamp-4 pt-2 font-extralight">{item?.summary?.value}</div> <div className="line-clamp-4 pt-2 font-extralight">{item?.summary?.value}</div>
</div> </div>
</Link> </Link>
<InlineAddToCart variants={item.variants} availableForSale={item.availableForSale} />
</div> </div>
) : null; ) : null;
} }
export async function ThreeItemGrid({ lang }: { lang?: SupportedLocale }) { export async function HomepageProducts({ lang }: { lang?: SupportedLocale }) {
// Collections that start with `hidden-*` are hidden from the search page. // Collections that start with `hidden-*` are hidden from the search page.
const homepageItems = await getCollectionProducts({ const homepageItems = await getCollectionProducts({
collection: 'hidden-homepage-featured-items', collection: 'hidden-homepage-featured-items',
@ -50,14 +56,14 @@ export async function ThreeItemGrid({ lang }: { lang?: SupportedLocale }) {
return ( return (
<section <section
className={clsx( className={clsx(
'mx-auto grid max-w-screen-xl gap-6 px-4 pb-4 ', 'mx-auto grid max-w-screen-xl gap-12 px-4 pb-4 ',
'grid-cols-1 md:grid-cols-6', 'grid-cols-1 md:grid-cols-6',
'grid-rows-3 md:grid-rows-1' 'grid-rows-3 md:grid-rows-1'
)} )}
> >
<ThreeItemGridItem item={firstProduct} priority={true} /> <HomepageProductsItem item={firstProduct} priority={true} />
<ThreeItemGridItem item={secondProduct} priority={true} /> <HomepageProductsItem item={secondProduct} priority={true} />
<ThreeItemGridItem item={thirdProduct} /> <HomepageProductsItem item={thirdProduct} />
</section> </section>
); );
} }

View File

@ -15,7 +15,9 @@ const Label = ({
return ( return (
<div className={clsx('@container/label')}> <div className={clsx('@container/label')}>
<div className="flex flex-col space-y-2"> <div className="flex flex-col space-y-2">
<h3 className="mr-4 line-clamp-2 flex-grow font-serif text-3xl md:text-4xl">{title}</h3> <h3 className="mr-4 line-clamp-2 flex-grow font-serif text-3xl tracking-wider md:text-4xl">
{title}
</h3>
<div className="font-multilingual flex flex-row items-center space-x-2"> <div className="font-multilingual flex flex-row items-center space-x-2">
<Price <Price
className="flex-none" className="flex-none"

View File

@ -6,12 +6,10 @@ import Link from 'next/link';
export default function Shoplist() { export default function Shoplist() {
const t = useTranslations('Index'); const t = useTranslations('Index');
return ( return (
<div className="mx-auto max-w-screen-xl space-y-4 px-6" id="shops"> <div className="font-multilingual mx-auto max-w-screen-xl space-y-4 px-6" id="shops">
<div className="flex w-full flex-row items-baseline space-x-12 pb-6"> <div className="flex w-full flex-row items-baseline space-x-12 pb-6">
<h2 className="font-serif text-6xl tracking-wider">shop list</h2> <h2 className="font-serif text-6xl tracking-wider">shop list</h2>
<h3 className="font-multilingual text-2xl font-extralight tracking-wider"> <h3 className="text-2xl font-extralight tracking-wider">{t('shops.subtitle')}</h3>
{t('shops.subtitle')}
</h3>
</div> </div>
<div className="grid w-full grid-cols-2 gap-px"> <div className="grid w-full grid-cols-2 gap-px">
<Link <Link

View File

@ -71,8 +71,8 @@
} }
}, },
"cart": { "cart": {
"add": "Add to cart", "add": "add to cart",
"out-of-stock": "Out of stock", "out-of-stock": "out of stock",
"title": "Shopping Bag", "title": "Shopping Bag",
"subtitle": "Review your Order", "subtitle": "Review your Order",
"empty": "Your shopping bag is empty", "empty": "Your shopping bag is empty",
@ -92,7 +92,7 @@
"editNote": "Edit note", "editNote": "Edit note",
"hideNote": "Hide", "hideNote": "Hide",
"saving": "Saving...", "saving": "Saving...",
"addFeaturedProduct": "+ Add" "addFeaturedProduct": "+ add"
}, },
"age-gate": { "age-gate": {
"title": "Confirm Your Age", "title": "Confirm Your Age",