feat(poc): changes for new design

This commit is contained in:
Björn Meyer 2023-07-27 17:14:18 +02:00
parent 82c0fb8813
commit 5ad4654c55
13 changed files with 106 additions and 124 deletions

View File

@ -8,7 +8,7 @@ import { Gallery } from 'components/product/gallery';
import { ProductDescription } from 'components/product/product-description'; import { ProductDescription } from 'components/product/product-description';
import { HIDDEN_PRODUCT_TAG } from 'lib/constants'; import { HIDDEN_PRODUCT_TAG } from 'lib/constants';
import { getProduct, getProductRecommendations } from 'lib/shopware'; import { getProduct, getProductRecommendations } from 'lib/shopware';
import { Image } from 'lib/shopify/types'; import { Image } from 'lib/shopware/types';
import Link from 'next/link'; import Link from 'next/link';
export const runtime = 'edge'; export const runtime = 'edge';
@ -132,8 +132,8 @@ async function RelatedProducts({ id }: { id: string }) {
currencyCode: product.priceRange.maxVariantPrice.currencyCode currencyCode: product.priceRange.maxVariantPrice.currencyCode
}} }}
src={product.featuredImage?.url} src={product.featuredImage?.url}
width={600} width={500}
height={600} height={500}
/> />
</Link> </Link>
); );

View File

@ -58,13 +58,18 @@ export default async function CategoryPage({
<Grid className="grid-cols-2 lg:grid-cols-3"> <Grid className="grid-cols-2 lg:grid-cols-3">
<ProductGridItems products={products} /> <ProductGridItems products={products} />
</Grid> </Grid>
<nav aria-label="Collection pagination" className="block items-center sm:flex"> {total > limit ? (
<Pagination <nav
itemsPerPage={limit} aria-label="Collection pagination"
itemsTotal={total} className="mb-2 mt-4 block items-center sm:flex"
currentPage={page ? parseInt(page) - 1 : 0} >
/> <Pagination
</nav> itemsPerPage={limit}
itemsTotal={total}
currentPage={page ? parseInt(page) - 1 : 0}
/>
</nav>
) : null}
</div> </div>
<div className="order-none flex-none md:order-last md:w-[125px]"> <div className="order-none flex-none md:order-last md:w-[125px]">
<FilterList list={sorting} title="Sort by" /> <FilterList list={sorting} title="Sort by" />

View File

@ -25,7 +25,7 @@ export default async function SearchPage({
return ( return (
<> <>
{searchValue && products.length === 0 ? ( {searchValue && products.length === 0 ? (
<div className="mx-auto flex max-w-7xl flex-col bg-white py-6 text-black dark:bg-black dark:text-white md:flex-row"> <div className="mx-auto flex max-w-screen-2xl flex-col gap-8 px-4 pb-4 text-black dark:text-white md:flex-row">
<p> <p>
{'There are no products that match '} {'There are no products that match '}
<span className="font-bold">&quot;{searchValue}&quot;</span> <span className="font-bold">&quot;{searchValue}&quot;</span>
@ -33,20 +33,22 @@ export default async function SearchPage({
</div> </div>
) : null} ) : null}
{products.length > 0 ? ( {products.length > 0 ? (
<div className="mx-auto flex max-w-7xl flex-col bg-white py-6 text-black dark:bg-black dark:text-white md:flex-row"> <div className="mx-auto flex max-w-screen-2xl flex-col gap-8 px-4 pb-4 text-black dark:text-white md:flex-row">
<div className="order-first flex-none md:w-1/6"> <div className="order-first w-full flex-none md:max-w-[125px]">
{searchValue ? ( {searchValue ? (
<p> <p className="text-sm text-neutral-500">
{`Showing ${products.length} ${resultsText} for `} {`Showing ${products.length} ${resultsText} for `}
<span className="font-bold">&quot;{searchValue}&quot;</span> <span className="font-bold">&quot;{searchValue}&quot;</span>
</p> </p>
) : null} ) : null}
<p className="pt-4">Good place to add other suggest search terms ;)</p> <p className="pt-4 text-xs text-neutral-500">
Good place to add other suggested search terms ;)
</p>
</div> </div>
<Grid className="grid-cols-1 sm:grid-cols-2 lg:grid-cols-3"> <Grid className="grid-cols-2 lg:grid-cols-3">
<ProductGridItems products={products} /> <ProductGridItems products={products} />
</Grid> </Grid>
<div className="order-none md:order-last md:w-1/6 md:flex-none"> <div className="order-none flex-none md:order-last md:w-[125px]">
<FilterList list={sorting} title="Sort by" /> <FilterList list={sorting} title="Sort by" />
</div> </div>
</div> </div>

View File

@ -1,6 +1,8 @@
'use client'; 'use client';
import ReactPaginate from 'react-paginate'; import ReactPaginate from 'react-paginate';
import { ArrowLeftIcon } from '@heroicons/react/24/outline';
import { ArrowRightIcon } from '@heroicons/react/24/outline';
import { createUrl } from 'lib/utils'; import { createUrl } from 'lib/utils';
import { usePathname, useSearchParams, useRouter } from 'next/navigation'; import { usePathname, useSearchParams, useRouter } from 'next/navigation';
@ -52,18 +54,18 @@ export default function Pagination({
initialPage={currentPage} initialPage={currentPage}
pageCount={pageCount} pageCount={pageCount}
breakLabel="..." breakLabel="..."
nextLabel=">>" nextLabel=<ArrowRightIcon className="h-5" />
previousLabel="<<" previousLabel=<ArrowLeftIcon className="h-5" />
renderOnZeroPageCount={null} renderOnZeroPageCount={null}
containerClassName="inline sm:flex text-base h-10 mx-auto" containerClassName="inline sm:flex text-base h-10 mx-auto"
activeClassName="active" activeClassName="active"
pageClassName="m-2 sm:m-0 sm:mx-2 text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white [&.active]:bg-gray-100" pageClassName="m-2 sm:m-0 sm:mx-2 text-gray-500 bg-white border rounded-lg border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white [&.active]:bg-gray-100"
pageLinkClassName="flex items-center justify-center px-4 h-10 ml-0 leading-tight" pageLinkClassName="flex items-center justify-center px-4 h-10 ml-0 leading-tight"
previousClassName="m-2 sm:m-0 sm:mx-2 text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white [&.disabled]:hidden" previousClassName="m-2 sm:m-0 sm:mx-2 text-gray-500 bg-white border rounded-lg border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white [&.disabled]:hidden"
previousLinkClassName="flex items-center justify-center px-4 h-10 ml-0 leading-tight" previousLinkClassName="flex items-center justify-center px-4 h-10 ml-0 leading-tight"
nextClassName="m-2 sm:m-0 sm:mx-2 text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white [&.disabled]:hidden" nextClassName="m-2 sm:m-0 sm:mx-2 text-gray-500 bg-white border rounded-lg border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white [&.disabled]:hidden"
nextLinkClassName="flex items-center justify-center px-4 h-10 ml-0 leading-tight" nextLinkClassName="flex items-center justify-center px-4 h-10 ml-0 leading-tight"
breakClassName="m-2 sm:m-0 sm:mx-2 text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white" breakClassName="m-2 sm:m-0 sm:mx-2 text-gray-500 bg-white border rounded-lg border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
breakLinkClassName="flex items-center justify-center px-4 h-10 ml-0 leading-tight" breakLinkClassName="flex items-center justify-center px-4 h-10 ml-0 leading-tight"
/> />
</> </>

View File

@ -33,7 +33,8 @@ export function GridTileImage({
<Image <Image
className={clsx('relative h-full w-full object-contain', { className={clsx('relative h-full w-full object-contain', {
'transition duration-300 ease-in-out hover:scale-105': isInteractive, 'transition duration-300 ease-in-out hover:scale-105': isInteractive,
'm-4 max-h-[8rem] min-h-[8rem]': props.width === 200 && props.height === 200 // this styling is for the thumbnails below gallery on product detail page 'max-h-[4rem] min-h-[4rem]': props.width === 200 && props.height === 200, // this styling is for the thumbnails below gallery on product detail page
'max-h-[20rem] min-h-[20rem]': props.width === 500 && props.height === 500 // this styling is for the gallery in recommendations on product detail page
})} })}
{...props} {...props}
/> />

View File

@ -1,16 +1,18 @@
import clsx from 'clsx'; import Image from 'next/image';
export default function LogoIcon(props: React.ComponentProps<'svg'>) { export type ImageProps = {
width?: number | string;
height?: number | string;
className?: string;
};
export default function LogoIcon(props: ImageProps) {
return ( return (
<svg <Image
xmlns="http://www.w3.org/2000/svg" src="https://www.shopware.com/media/pages/solutions/shopware-frontends/shopware-frontends-intro-graphic-base.svg"
aria-label={`${process.env.SITE_NAME} logo`} alt="Shopware Composable Frontends Logo"
viewBox="0 0 32 28" width={Number(props.width)}
{...props} height={Number(props.height)}
className={clsx('h-4 w-4 fill-black dark:fill-white', props.className)} ></Image>
>
<path d="M21.5758 9.75769L16 0L0 28H11.6255L21.5758 9.75769Z" />
<path d="M26.2381 17.9167L20.7382 28H32L26.2381 17.9167Z" />
</svg>
); );
} }

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { Menu } from 'lib/shopify/types'; import { Menu } from 'lib/shopware/types';
import Link from 'next/link'; import Link from 'next/link';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@ -15,7 +15,7 @@ const FooterMenuItem = ({ item }: { item: Menu }) => {
}, [pathname, item.path]); }, [pathname, item.path]);
return ( return (
<li className="mt-2 first:mt-1"> <li key={item.title} className="mt-2 first:mt-1">
<Link <Link
href={item.path} href={item.path}
className={clsx( className={clsx(
@ -34,13 +34,25 @@ const FooterMenuItem = ({ item }: { item: Menu }) => {
export default function FooterMenu({ menu }: { menu: Menu[] }) { export default function FooterMenu({ menu }: { menu: Menu[] }) {
if (!menu.length) return null; if (!menu.length) return null;
return ( return menu.map((item: Menu) => (
<nav> <nav className="col-span-1 lg:col-span-3" key={item.title + item.type}>
<ul> {item.type === 'headline' ? <span className="font-bold">{item.title}</span> : null}
{menu.map((item: Menu) => { {item.children.length > 0 ? (
return <FooterMenuItem key={item.title} item={item} />; <ul className="py-3 md:py-0 md:pt-4" key={item.title}>
})} {item.children.map((item: Menu) => (
</ul> <FooterMenuItem key={item.title} item={item} />
))}
</ul>
) : (
// if there are no children, at least display a link
<Link
key={item.title}
href={item.path}
className="text-gray-800 transition duration-150 ease-in-out hover:text-gray-300 dark:text-gray-100"
>
{item.title}
</Link>
)}
</nav> </nav>
); ));
} }

View File

@ -5,7 +5,6 @@ import GitHubIcon from 'components/icons/github';
import FooterMenu from 'components/layout/footer-menu'; import FooterMenu from 'components/layout/footer-menu';
import LogoSquare from 'components/logo-square'; import LogoSquare from 'components/logo-square';
import { getMenu } from 'lib/shopware'; import { getMenu } from 'lib/shopware';
import { Menu } from 'lib/shopware/types';
import { Suspense } from 'react'; import { Suspense } from 'react';
const { COMPANY_NAME, SITE_NAME } = process.env; const { COMPANY_NAME, SITE_NAME } = process.env;
@ -18,44 +17,30 @@ export default async function Footer() {
const copyrightName = COMPANY_NAME || SITE_NAME || ''; const copyrightName = COMPANY_NAME || SITE_NAME || '';
return ( return (
<footer className="border-t border-gray-700 bg-white text-black dark:bg-black dark:text-white"> <footer className="text-sm text-neutral-400 dark:text-neutral-600">
<div className="mx-auto w-full max-w-7xl px-6"> <div className="mx-auto flex w-full max-w-7xl flex-col gap-6 border-t border-neutral-200 px-6 py-12 text-sm dark:border-neutral-700 md:flex-row md:gap-12 md:px-4 xl:px-0">
<div className="grid grid-cols-1 gap-8 border-b border-gray-700 py-12 transition-colors duration-150 lg:grid-cols-12"> <div className="grid grid-cols-1 gap-8 transition-colors duration-150 lg:grid-cols-12">
<div className="col-span-1 lg:col-span-4"> <div className="col-span-1 lg:col-span-3">
<Link className="flex items-center gap-2 text-black dark:text-white" href="/"> <Link className="flex items-center gap-2 text-black dark:text-white" href="/">
<LogoSquare size="sm" /> <LogoSquare size="sm" />
<span className="uppercase">{SITE_NAME}</span> <span className="uppercase">{SITE_NAME}</span>
</Link> </Link>
</div> </div>
{menu.map((item: Menu) => ( <Suspense
<nav className="col-span-1 lg:col-span-3" key={item.title + item.type}> fallback={
{item.type === 'headline' ? <span className="font-bold">{item.title}</span> : null} <div className="flex h-[188px] w-[200px] flex-col gap-2">
{item.children.length > 0 ? ( <div className={skeleton} />
<ul className="py-3 md:py-0 md:pt-4" key={item.title}> <div className={skeleton} />
{item.children.map((item: Menu) => ( <div className={skeleton} />
<li key={item.title} className="py-3 md:py-0 md:pb-4"> <div className={skeleton} />
<Link <div className={skeleton} />
href={item.path} <div className={skeleton} />
className="text-gray-800 transition duration-150 ease-in-out hover:text-gray-300 dark:text-gray-100" </div>
> }
{item.title} >
</Link> <FooterMenu menu={menu} />
</li> </Suspense>
))} <div className="md:ml-auto">
</ul>
) : (
// if there are no children, at least display a link
<Link
key={item.title}
href={item.path}
className="text-gray-800 transition duration-150 ease-in-out hover:text-gray-300 dark:text-gray-100"
>
{item.title}
</Link>
)}
</nav>
))}
<div className="col-span-1 inline-grid justify-items-end text-black dark:text-white lg:col-span-2">
<a <a
aria-label="Github Repository" aria-label="Github Repository"
href="https://github.com/shopware/frontends" href="https://github.com/shopware/frontends"
@ -66,30 +51,6 @@ export default async function Footer() {
</a> </a>
</div> </div>
</div> </div>
<Suspense
fallback={
<div className="flex h-[188px] w-[200px] flex-col gap-2">
<div className={skeleton} />
<div className={skeleton} />
<div className={skeleton} />
<div className={skeleton} />
<div className={skeleton} />
<div className={skeleton} />
</div>
}
>
<FooterMenu menu={menu} />
</Suspense>
<div className="md:ml-auto">
<a
className="flex items-center gap-2 hover:text-black dark:hover:text-neutral-300"
aria-label="Github Repository"
href="https://github.com/vercel/commerce"
>
<GitHubIcon className="h-6" />
<p>Source</p>
</a>
</div>
</div> </div>
<div className="border-t border-neutral-200 py-6 text-sm dark:border-neutral-700"> <div className="border-t border-neutral-200 py-6 text-sm dark:border-neutral-700">
<div className="mx-auto flex w-full max-w-7xl flex-col items-center gap-1 md:flex-row md:gap-0"> <div className="mx-auto flex w-full max-w-7xl flex-col items-center gap-1 md:flex-row md:gap-0">
@ -97,10 +58,9 @@ export default async function Footer() {
&copy; {copyrightDate} {copyrightName} &copy; {copyrightDate} {copyrightName}
{copyrightName.length && !copyrightName.endsWith('.') ? '.' : ''} All rights reserved. {copyrightName.length && !copyrightName.endsWith('.') ? '.' : ''} All rights reserved.
</p> </p>
<div className="flex items-center text-sm text-white dark:text-black"> <hr className="mx-4 hidden h-4 w-[1px] border-l border-neutral-400 md:inline-block" />
<span className="text-black dark:text-white"> <p>Created by Shopware Composable Frontends</p>
Created by Shopware Composable Frontends <div className="md:ml-auto">
</span>
<a <a
rel="noopener noreferrer" rel="noopener noreferrer"
href="https://frontends.shopware.com/" href="https://frontends.shopware.com/"

View File

@ -7,7 +7,6 @@ import Link from 'next/link';
import { Suspense } from 'react'; import { Suspense } from 'react';
import MobileMenu from './mobile-menu'; import MobileMenu from './mobile-menu';
import Search from './search'; import Search from './search';
const { SITE_NAME } = process.env;
export default async function Navbar() { export default async function Navbar() {
const menu = await getMenu({ type: 'main-navigation' }); const menu = await getMenu({ type: 'main-navigation' });
@ -18,16 +17,13 @@ export default async function Navbar() {
<MobileMenu menu={menu} /> <MobileMenu menu={menu} />
</div> </div>
<div className="flex w-full items-center"> <div className="flex w-full items-center">
<div className="flex w-full md:w-1/3"> <div className="flex w-full md:w-4/6">
<Link <Link
href="/" href="/"
aria-label="Go back home" aria-label="Go back home"
className="mr-2 flex w-full items-center justify-center md:w-auto lg:mr-6" className="mr-2 flex w-full items-center justify-center md:w-auto lg:mr-6"
> >
<LogoSquare /> <LogoSquare />
<div className="ml-2 flex-none text-sm font-medium uppercase md:hidden lg:block">
{SITE_NAME}
</div>
</Link> </Link>
{menu.length ? ( {menu.length ? (
<ul className="hidden text-sm md:flex md:items-center"> <ul className="hidden text-sm md:flex md:items-center">
@ -44,10 +40,10 @@ export default async function Navbar() {
</ul> </ul>
) : null} ) : null}
</div> </div>
<div className="hidden justify-center md:flex md:w-1/3"> <div className="hidden justify-center md:flex md:w-1/6">
<Search /> <Search />
</div> </div>
<div className="flex justify-end md:w-1/3"> <div className="flex justify-end md:w-1/6">
<Suspense fallback={<OpenCart />}> <Suspense fallback={<OpenCart />}>
<Cart /> <Cart />
</Suspense> </Suspense>

View File

@ -13,6 +13,8 @@ export default function LogoSquare({ size }: { size?: 'sm' | undefined }) {
)} )}
> >
<LogoIcon <LogoIcon
width={20}
height={20}
className={clsx({ className={clsx({
'h-[16px] w-[16px]': !size, 'h-[16px] w-[16px]': !size,
'h-[10px] w-[10px]': size === 'sm' 'h-[10px] w-[10px]': size === 'sm'

View File

@ -17,7 +17,7 @@ export default async function OpengraphImage(props?: Props): Promise<ImageRespon
( (
<div tw="flex h-full w-full flex-col items-center justify-center bg-black"> <div tw="flex h-full w-full flex-col items-center justify-center bg-black">
<div tw="flex flex-none items-center justify-center border border-neutral-700 h-[160px] w-[160px] rounded-3xl"> <div tw="flex flex-none items-center justify-center border border-neutral-700 h-[160px] w-[160px] rounded-3xl">
<LogoIcon width="64" height="58" fill="white" /> <LogoIcon width="64" height="58" />
</div> </div>
<p tw="mt-12 text-6xl font-bold text-white">{title}</p> <p tw="mt-12 text-6xl font-bold text-white">{title}</p>
</div> </div>

View File

@ -21,7 +21,7 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
return ( return (
<div className="mr-8 h-full"> <div className="mr-8 h-full">
<div className="relative mb-12 h-full max-h-[550px] overflow-hidden"> <div className="relative mb-12 h-full max-h-[550px] min-h-[550px] overflow-hidden">
{images[currentImageIndex] && ( {images[currentImageIndex] && (
<Image <Image
className="relative h-full w-full object-contain" className="relative h-full w-full object-contain"

View File

@ -18,18 +18,18 @@ export function ProductDescription({ product }: { product: Product }) {
</div> </div>
<VariantSelector options={product.options} variants={product.variants} /> <VariantSelector options={product.options} variants={product.variants} />
{product.descriptionHtml ? (
<Prose
className="mb-6 text-sm leading-tight dark:text-white/[60%]"
html={product.descriptionHtml}
/>
) : null}
<AddToCart <AddToCart
product={product} product={product}
variants={product.variants} variants={product.variants}
availableForSale={product.availableForSale} availableForSale={product.availableForSale}
/> />
{product.descriptionHtml ? (
<Prose
className="m-6 max-h-96 overflow-y-auto text-sm leading-tight dark:text-white/[60%]"
html={product.descriptionHtml}
/>
) : null}
</> </>
); );
} }