mirror of
https://github.com/vercel/commerce.git
synced 2025-05-19 07:56:59 +00:00
fix: Add to cart buttons on homepage
This commit is contained in:
parent
f43226198d
commit
d9838f8807
@ -25,5 +25,5 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[lang='ja'] .font-multilingual {
|
[lang='ja'] .font-multilingual {
|
||||||
@apply font-japan;
|
@apply font-japan tracking-widest;
|
||||||
}
|
}
|
||||||
|
@ -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 />
|
||||||
|
75
components/cart/inline-add-to-cart.tsx
Normal file
75
components/cart/inline-add-to-cart.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -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",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user