wip: Product detail view

This commit is contained in:
Sol Irvine 2023-08-19 17:18:45 +09:00
parent 523db4e1d4
commit 097cb83568
14 changed files with 92 additions and 51 deletions

View File

@ -39,7 +39,7 @@ export default async function HomePage({
}
return (
<div className="relative h-screen overflow-scroll">
<div>
<Navbar cart={cart} locale={locale} />
<div className="pt-48">
<ThreeItemGrid lang={locale} />

View File

@ -0,0 +1,43 @@
import Footer from 'components/layout/footer';
import { SupportedLocale } from 'components/layout/navbar/language-control';
import Navbar from 'components/layout/navbar';
import { getCart } from 'lib/shopify';
import { cookies } from 'next/headers';
import { ReactNode, Suspense } from 'react';
export const runtime = 'edge';
const { SITE_NAME } = process.env;
export const metadata = {
title: SITE_NAME,
description: SITE_NAME,
openGraph: {
type: 'website'
}
};
export default async function ProductLayout({
params: { locale },
children
}: {
params: { locale?: SupportedLocale };
children: ReactNode[] | ReactNode | string;
}) {
const cartId = cookies().get('cartId')?.value;
let cart;
if (cartId) {
cart = await getCart(cartId);
}
return (
<div>
<Navbar cart={cart} locale={locale} />
{children}
<Suspense>
<Footer cart={cart} />
</Suspense>
</div>
);
}

View File

@ -3,7 +3,6 @@ import { notFound } from 'next/navigation';
import { Suspense } from 'react';
import { GridTileImage } from 'components/grid/tile';
import Footer from 'components/layout/footer';
import { Gallery } from 'components/product/gallery';
import { ProductDescription } from 'components/product/product-description';
import { HIDDEN_PRODUCT_TAG } from 'lib/constants';
@ -81,8 +80,8 @@ export default async function ProductPage({ params }: { params: { handle: string
__html: JSON.stringify(productJsonLd)
}}
/>
<div className="mx-auto max-w-screen-xl px-4">
<div className="flex flex-col rounded-lg border border-neutral-200 bg-white p-8 dark:border-neutral-800 dark:bg-black md:p-12 lg:flex-row">
<div className="mx-auto max-w-screen-xl px-4 py-24">
<div className="flex flex-col p-8 md:p-12 lg:flex-row lg:space-x-6">
<div className="h-full w-full basis-full lg:basis-4/6">
<Gallery
images={product.images.map((image: Image) => ({
@ -100,9 +99,6 @@ export default async function ProductPage({ params }: { params: { handle: string
<RelatedProducts id={product.id} />
</Suspense>
</div>
<Suspense>
<Footer />
</Suspense>
</>
);
}
@ -110,18 +106,20 @@ export default async function ProductPage({ params }: { params: { handle: string
async function RelatedProducts({ id }: { id: string }) {
const relatedProducts = await getProductRecommendations({ productId: id });
console.debug({ relatedProducts });
if (!relatedProducts.length) return null;
return (
<div className="py-8">
<h2 className="mb-4 text-2xl font-bold">Related Products</h2>
<div className="py-24">
<h2 className="font-multilingual mb-4 text-2xl">Related Products</h2>
<ul className="flex w-full gap-4 overflow-x-auto pt-1">
{relatedProducts.map((product) => (
<li
key={product.handle}
className="aspect-square w-full flex-none min-[475px]:w-1/2 sm:w-1/3 md:w-1/4 lg:w-1/5"
>
<Link className="relative h-full w-full" href={`/product/${product.handle}`}>
<Link className="relative block h-full w-full" href={`/product/${product.handle}`}>
<GridTileImage
alt={product.title}
label={{

View File

@ -31,7 +31,7 @@ export default async function ProductPage({
}
return (
<div className="relative h-screen overflow-scroll">
<div>
<Navbar cart={cart} locale={locale} />
<div className="py-24 md:py-48">
<ThreeItemGrid lang={locale} />

View File

@ -52,7 +52,7 @@ export function AddToCart({
});
}}
className={clsx(
'relative flex w-full items-center justify-center rounded-full bg-blue-600 p-4 tracking-wide text-white hover:opacity-90',
'relative flex w-full items-center justify-center rounded-md bg-white/20 p-4 font-sans tracking-wide text-white hover:opacity-90',
{
'cursor-not-allowed opacity-60 hover:opacity-60': !availableForSale || !selectedVariantId,
'cursor-not-allowed': isPending

View File

@ -3,7 +3,7 @@ import clsx from 'clsx';
export default function CloseCart({ className }: { className?: string }) {
return (
<div className="relative flex h-11 w-11 items-center justify-center rounded-md border border-neutral-200 text-black transition-colors dark:border-neutral-700 dark:text-white">
<div className="relative flex h-11 w-11 items-center justify-center rounded-md border border-white/20 text-white transition-colors">
<XMarkIcon className={clsx('h-6 transition-all ease-in-out hover:scale-110 ', className)} />
</div>
);

View File

@ -64,7 +64,7 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
<Dialog.Panel className="fixed bottom-0 right-0 top-0 flex h-full w-full flex-col border-l border-neutral-200 bg-white/80 p-6 text-black backdrop-blur-xl dark:border-neutral-700 dark:bg-black/80 dark:text-white md:w-[390px]">
<Dialog.Panel className="fixed bottom-0 right-0 top-0 flex h-full w-full flex-col border-l border-white/20 bg-dark p-6 text-white backdrop-blur-xl md:w-[390px]">
<div className="flex items-center justify-between">
<p className="text-lg font-semibold">My Cart</p>
@ -96,10 +96,7 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
);
return (
<li
key={i}
className="flex w-full flex-col border-b border-neutral-300 dark:border-neutral-700"
>
<li key={i} className="flex w-full flex-col border-b border-white/20">
<div className="relative flex w-full flex-row justify-between px-1 py-4">
<div className="absolute z-40 -mt-2 ml-[55px]">
<DeleteItemButton item={item} />
@ -109,7 +106,7 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
onClick={closeCart}
className="z-30 flex flex-row space-x-4"
>
<div className="relative h-16 w-16 cursor-pointer overflow-hidden rounded-md border border-neutral-300 bg-neutral-300 dark:border-neutral-700 dark:bg-neutral-900 dark:hover:bg-neutral-800">
<div className="relative h-16 w-16 cursor-pointer overflow-hidden rounded-md border border-white/20 bg-white/40">
<Image
className="h-full w-full object-cover"
width={64}
@ -127,9 +124,7 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
{item.merchandise.product.title}
</span>
{item.merchandise.title !== DEFAULT_OPTION ? (
<p className="text-sm text-neutral-500 dark:text-neutral-400">
{item.merchandise.title}
</p>
<p className="text-sm text-white">{item.merchandise.title}</p>
) : null}
</div>
</Link>
@ -139,7 +134,7 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
amount={item.cost.totalAmount.amount}
currencyCode={item.cost.totalAmount.currencyCode}
/>
<div className="ml-auto flex h-9 flex-row items-center rounded-full border border-neutral-200 dark:border-neutral-700">
<div className="ml-auto flex h-9 flex-row items-center rounded-full border border-white/20">
<EditItemQuantityButton item={item} type="minus" />
<p className="w-6 text-center">
<span className="w-full text-sm">{item.quantity}</span>
@ -153,22 +148,22 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
})}
</ul>
<div className="py-4 text-sm text-neutral-500 dark:text-neutral-400">
<div className="mb-3 flex items-center justify-between border-b border-neutral-200 pb-1 dark:border-neutral-700">
<div className="mb-3 flex items-center justify-between border-b border-white/20 pb-1">
<p>Taxes</p>
<Price
className="text-right text-base text-black dark:text-white"
className="text-right text-base text-white"
amount={cart.cost.totalTaxAmount.amount}
currencyCode={cart.cost.totalTaxAmount.currencyCode}
/>
</div>
<div className="mb-3 flex items-center justify-between border-b border-neutral-200 pb-1 pt-1 dark:border-neutral-700">
<div className="mb-3 flex items-center justify-between border-b border-white/20 pb-1 pt-1">
<p>Shipping</p>
<p className="text-right">Calculated at checkout</p>
<p className="text-right text-white/50">Calculated at checkout</p>
</div>
<div className="mb-3 flex items-center justify-between border-b border-neutral-200 pb-1 pt-1 dark:border-neutral-700">
<div className="mb-3 flex items-center justify-between border-b border-white/20 pb-1 pt-1">
<p>Total</p>
<Price
className="text-right text-base text-black dark:text-white"
className="text-right text-base text-white"
amount={cart.cost.totalAmount.amount}
currencyCode={cart.cost.totalAmount.currencyCode}
/>

View File

@ -19,7 +19,7 @@ export default function OpenCart({
/>
{quantity ? (
<div className="absolute right-[23%] top-[85%] -mr-2 -mt-2 h-5 w-5 -translate-x-1/2 -translate-y-1/2 transform text-[12px] font-medium text-white">
<div className="absolute right-[23%] top-[85%] -mr-2 -mt-2 h-5 w-5 -translate-x-1/2 -translate-y-1/2 transform font-sans text-[12px] font-medium text-white">
{quantity}
</div>
) : null}

View File

@ -29,7 +29,9 @@ export default async function Footer({ cart }: { cart?: Cart }) {
<div className="flex flex-col space-y-24">
<NewsletterFooter />
<div className="hidden flex-row items-end space-x-12 pt-24 md:flex">
<KanjiLogo className="h-64" />
<Link href="/" className="transition-opacity duration-150 hover:opacity-90">
<KanjiLogo className="h-64" />
</Link>
<div className="flex flex-row items-end space-x-6">
<div className="flex flex-col items-start space-y-2">
<p className="font-japan text-3xl font-extralight"></p>
@ -45,7 +47,9 @@ export default async function Footer({ cart }: { cart?: Cart }) {
</div>
<div className="w-full md:w-[40%]">
<div className="flex w-full flex-row items-end space-x-12 pt-24 md:hidden">
<KanjiLogo className="h-64" />
<Link href="/" className="transition-opacity duration-150 hover:opacity-90">
<KanjiLogo className="h-64" />
</Link>
<div className="flex grow flex-col items-end space-y-6">
<div className="flex flex-col items-start space-y-2">
<p className="font-japan text-3xl font-extralight"></p>

View File

@ -32,7 +32,7 @@ export default function Navbar({ cart, locale }: { cart?: Cart; locale?: Support
>
<div className="mx-auto flex max-w-screen-xl flex-row items-start justify-between">
<div className="px-4 py-2">
<Link href="/">
<Link href="/" className="transition-opacity duration-150 hover:opacity-90">
<LogoNamemark className={clsx('w-[260px]', 'fill-current')} />
</Link>
</div>
@ -51,7 +51,7 @@ export default function Navbar({ cart, locale }: { cart?: Cart; locale?: Support
className={clsx('mx-auto flex max-w-screen-xl flex-row items-start justify-between px-6')}
>
<div>
<Link href="/">
<Link href="/" className="transition-opacity duration-150 hover:opacity-90">
<LogoNamemark
className={clsx(
inView ? 'w-[260px] md:w-[600px]' : 'w-[260px] md:w-[260px]',

View File

@ -30,7 +30,7 @@ export default function NewsletterSignup() {
className={clsx(
'w-full min-w-0 appearance-none',
'bg-white outline-none',
'px-4 py-2 focus:outline-none',
'px-4 py-3 focus:outline-none',
'focus:ring-2 focus:ring-inset focus:ring-emerald-300 focus:ring-offset-0',
'text-gray-900 placeholder-gray-400'
)}
@ -40,7 +40,7 @@ export default function NewsletterSignup() {
<button
type="submit"
className={clsx(
'px-4 py-2',
'px-4 py-3',
'transition-colors duration-150',
'flex w-full items-center justify-center',
'border border-white/30 hover:border-white',

View File

@ -28,7 +28,7 @@ export default function NewsletterSignup() {
className={clsx(
'w-full min-w-0 appearance-none',
'bg-white outline-none',
'px-4 py-2 focus:outline-none',
'px-4 py-3 focus:outline-none',
'focus:ring-2 focus:ring-inset focus:ring-emerald-300 focus:ring-offset-0',
'text-gray-900 placeholder-gray-400'
)}
@ -38,7 +38,7 @@ export default function NewsletterSignup() {
<button
type="submit"
className={clsx(
'px-4 py-2',
'px-4 py-3',
'transition-colors duration-150',
'font-multilingual flex w-full items-center justify-center font-extralight',
'border border-white/30 hover:border-white',

View File

@ -24,14 +24,14 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
const previousUrl = createUrl(pathname, previousSearchParams);
const buttonClassName =
'h-full px-6 transition-all ease-in-out hover:scale-110 hover:text-black dark:hover:text-white flex items-center justify-center';
'h-full px-6 transition-all ease-in-out hover:scale-110 hover:text-white flex items-center justify-center';
return (
<>
<div className="relative aspect-square h-full max-h-[550px] w-full overflow-hidden">
{images[imageIndex] && (
<Image
className="h-full w-full object-contain"
className="h-full w-full object-cover"
fill
sizes="(min-width: 1024px) 66vw, 100vw"
alt={images[imageIndex]?.altText as string}
@ -41,8 +41,8 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
)}
{images.length > 1 ? (
<div className="absolute bottom-[15%] flex w-full justify-center">
<div className="mx-auto flex h-11 items-center rounded-full border border-white bg-neutral-50/80 text-neutral-500 backdrop-blur dark:border-black dark:bg-neutral-900/80">
<div className="absolute bottom-[8%] flex w-full justify-center">
<div className="mx-auto flex h-11 items-center rounded-full border border-white/40 bg-dark/40 text-white/70 backdrop-blur">
<Link
aria-label="Previous product image"
href={previousUrl}
@ -51,7 +51,7 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
>
<ArrowLeftIcon className="h-5" />
</Link>
<div className="mx-1 h-6 w-px bg-neutral-500"></div>
<div className="mx-1 h-6 w-px bg-white/40"></div>
<Link
aria-label="Next product image"
href={nextUrl}
@ -74,18 +74,19 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
imageSearchParams.set('image', index.toString());
return (
<li key={image.src} className="h-auto w-20">
<li key={image.src} className="aspect-square h-auto w-20">
<Link
aria-label="Enlarge product image"
href={createUrl(pathname, imageSearchParams)}
scroll={false}
className="h-full w-full"
className="relative block h-full w-full"
>
<GridTileImage
alt={image.altText}
src={image.src}
width={80}
height={80}
fill
// width={80}
// height={80}
active={isActive}
/>
</Link>

View File

@ -7,9 +7,9 @@ import { VariantSelector } from './variant-selector';
export function ProductDescription({ product }: { product: Product }) {
return (
<>
<div className="mb-6 flex flex-col border-b pb-6 dark:border-neutral-700">
<h1 className="mb-2 text-5xl font-medium">{product.title}</h1>
<div className="mr-auto w-auto rounded-full bg-blue-600 p-2 text-sm text-white">
<div className="mb-6 flex flex-col border-b border-white/20 pb-6">
<h1 className="font-multilingual mb-2 text-5xl">{product.title}</h1>
<div className="font-multilingual mr-auto w-auto text-lg text-white">
<Price
amount={product.priceRange.maxVariantPrice.amount}
currencyCode={product.priceRange.maxVariantPrice.currencyCode}
@ -20,7 +20,7 @@ export function ProductDescription({ product }: { product: Product }) {
{product.descriptionHtml ? (
<Prose
className="mb-6 text-sm leading-tight dark:text-white/[60%]"
className="mb-6 text-lg leading-tight dark:text-white/[60%]"
html={product.descriptionHtml}
/>
) : null}