add: basic setting & logo components, error component

This commit is contained in:
Minjee Son 2024-09-04 19:41:50 +01:00
parent 694c5c17ba
commit 26bf7d8dfb
11 changed files with 295 additions and 77 deletions

View File

@ -1,7 +0,0 @@
COMPANY_NAME="Vercel Inc."
TWITTER_CREATOR="@vercel"
TWITTER_SITE="https://nextjs.org/commerce"
SITE_NAME="Next.js Commerce"
SHOPIFY_REVALIDATION_SECRET=""
SHOPIFY_STOREFRONT_ACCESS_TOKEN=""
SHOPIFY_STORE_DOMAIN="[your-shopify-store-subdomain].myshopify.com"

View File

@ -1,6 +1,15 @@
'use client';
export default function Error({ reset }: { reset: () => void }) {
import { useRouter } from 'next/navigation';
export default function Error({ resetAction }: { resetAction?: () => void }) {
const router = useRouter();
// '/'로 이동하는 defaultReset 함수
const defaultReset = () => {
router.push('/'); // 메인 페이지로 이동
};
return (
<div className="mx-auto my-4 flex max-w-xl flex-col rounded-lg border border-neutral-200 bg-white p-8 md:p-12 dark:border-neutral-800 dark:bg-black">
<h2 className="text-xl font-bold">Oh no!</h2>
@ -10,7 +19,7 @@ export default function Error({ reset }: { reset: () => void }) {
</p>
<button
className="mx-auto mt-4 flex w-full items-center justify-center rounded-full bg-blue-600 p-4 tracking-wide text-white hover:opacity-90"
onClick={() => reset()}
onClick={resetAction || defaultReset}
>
Try Again
</button>

View File

@ -1,3 +1,4 @@
@import url('https://fonts.googleapis.com/css2?family=Libre+Baskerville:wght@400;700&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
@ -14,6 +15,10 @@
}
}
body {
@apply font-baskerville;
}
a,
input,
button {

View File

@ -1,12 +1,8 @@
import { CartProvider } from 'components/cart/cart-context';
import { Navbar } from 'components/layout/navbar';
import { WelcomeToast } from 'components/welcome-toast';
import { GeistSans } from 'geist/font/sans';
import { getCart } from 'lib/shopify';
import { ensureStartsWith } from 'lib/utils';
import { cookies } from 'next/headers';
import { ReactNode } from 'react';
import { Toaster } from 'sonner';
import './globals.css';
const { TWITTER_CREATOR, TWITTER_SITE, SITE_NAME } = process.env;
@ -44,14 +40,7 @@ export default async function RootLayout({ children }: { children: ReactNode })
return (
<html lang="en" className={GeistSans.variable}>
<body className="bg-neutral-50 text-black selection:bg-teal-300 dark:bg-neutral-900 dark:text-white dark:selection:bg-pink-500 dark:selection:text-white">
<CartProvider cartPromise={cart}>
<Navbar />
<main>
{children}
<Toaster closeButton />
<WelcomeToast />
</main>
</CartProvider>
<main>{children}</main>
</body>
</html>
);

View File

@ -1,7 +1,12 @@
import { Carousel } from 'components/carousel';
import { ThreeItemGrid } from 'components/grid/three-items';
import Error from 'app/error';
import Footer from 'components/layout/footer';
import { Navbar } from 'components/layout/navbar';
import Search from 'components/layout/navbar/search';
import PriceBoxes from 'components/price-boxes';
import { getCollectionProducts } from 'lib/shopify';
import Image from 'next/image';
//Todo: change to proper metadata
export const metadata = {
description: 'High-performance ecommerce store built with Next.js, Vercel, and Shopify.',
openGraph: {
@ -9,11 +14,48 @@ export const metadata = {
}
};
export default function HomePage() {
export default async function HomePage() {
const products = await getCollectionProducts({ collection: 'landing' });
//Todo: change to proper error handling
if (!products[0]) return <Error />;
return (
<>
<ThreeItemGrid />
<Carousel />
<section className="relative">
<Navbar />
<div className="relative w-screen">
<Image
src={
products[0].featuredImage.url || '' //Todo: default image
}
alt={products[0].featuredImage.altText || 'Main product'}
width={products[0].featuredImage.width}
height={products[0].featuredImage.height}
quality={100}
priority
/>
</div>
<div className="absolute bottom-0 flex w-full flex-col items-center text-lightText">
<h1 className="text-xl">{products[0].title}</h1>
<span className="mb-6 mt-1 text-sm text-lightText/80">Read more</span>
<PriceBoxes
products={[
{ title: 'Box of 20', price: '£2,460' },
{ title: 'Single Cigar', price: '£120' }
]}
totalWidth={384}
/>
</div>
</section>
<section className="flex">
<Search />
<div className="flex">
Filter by
<button>Size</button>
<button>Strength</button>
</div>
</section>
<Footer />
</>
);

File diff suppressed because one or more lines are too long

View File

@ -1,59 +1,36 @@
import CartModal from 'components/cart/modal';
import LogoSquare from 'components/logo-square';
import { getMenu } from 'lib/shopify';
import { Menu } from 'lib/shopify/types';
import Link from 'next/link';
import { Suspense } from 'react';
import MobileMenu from './mobile-menu';
import Search, { SearchSkeleton } from './search';
const { SITE_NAME } = process.env;
import {
Bars3Icon,
MagnifyingGlassIcon,
ShoppingBagIcon,
UserIcon
} from '@heroicons/react/24/outline';
import Logo from 'components/icons/logo';
import { SearchSkeleton } from './search';
export async function Navbar() {
const menu = await getMenu('next-js-frontend-header-menu');
return (
<nav className="relative flex items-center justify-between p-4 lg:px-6">
<div className="block flex-none md:hidden">
<Suspense fallback={null}>
<MobileMenu menu={menu} />
</Suspense>
</div>
<div className="flex w-full items-center">
<div className="flex w-full md:w-1/3">
<Link
href="/"
prefetch={true}
className="mr-2 flex w-full items-center justify-center md:w-auto lg:mr-6"
>
<LogoSquare />
<div className="ml-2 flex-none text-sm font-medium uppercase md:hidden lg:block">
{SITE_NAME}
</div>
</Link>
{menu.length ? (
<ul className="hidden gap-6 text-sm md:flex md:items-center">
{menu.map((item: Menu) => (
<li key={item.title}>
<Link
href={item.path}
prefetch={true}
className="text-neutral-500 underline-offset-4 hover:text-black hover:underline dark:text-neutral-400 dark:hover:text-neutral-300"
>
{item.title}
</Link>
</li>
))}
</ul>
) : null}
<nav className="absolute left-0 top-0 z-10 flex w-full items-center px-[4.38rem] py-[3.12rem]">
<div className="flex w-full items-center justify-between">
{/* burger menu */}
<div className="block flex-none">
<Bars3Icon width={24} height={24} color="white" />
</div>
<div className="hidden justify-center md:flex md:w-1/3">
{/* logo */}
<div className="justify-center md:flex md:w-1/3">
<Suspense fallback={<SearchSkeleton />}>
<Search />
<Link href="/" prefetch={true}>
<Logo />
</Link>
</Suspense>
</div>
<div className="flex justify-end md:w-1/3">
<CartModal />
{/* 3 icons */}
<div className="flex justify-between gap-[1.88rem]">
<MagnifyingGlassIcon width={16} height={16} color="white" />
<UserIcon width={16} height={16} fill="white" color="white" />
<ShoppingBagIcon width={16} height={16} fill="white" color="white" />
</div>
</div>
</nav>

View File

@ -0,0 +1,29 @@
interface PriceBoxesProps {
products: { title: string; price: string }[];
totalWidth: number;
}
const PriceBoxes = ({ products, totalWidth }: PriceBoxesProps) => {
return (
<div className={`text-mainBG flex w-[${totalWidth}px] justify-center gap-[10px]`}>
{products.map(({ title, price }) => (
<PriceBox title={title} price={price} />
))}
</div>
);
};
export default PriceBoxes;
interface PriceBoxProps {
title: string;
price: string;
}
const PriceBox = ({ title, price }: PriceBoxProps) => {
return (
<div className="flex w-full flex-col items-center gap-[5px] rounded bg-black/50">
<p>{title}</p>
<p className="text-xs text-lightText/80">{price}</p>
</div>
);
};

View File

@ -28,10 +28,13 @@
"@types/node": "20.14.12",
"@types/react": "18.3.3",
"@types/react-dom": "18.3.0",
"@types/react-slick": "^0.23.13",
"autoprefixer": "^10.4.19",
"postcss": "^8.4.39",
"prettier": "3.3.3",
"prettier-plugin-tailwindcss": "^0.6.5",
"react-slick": "^0.30.2",
"swiper": "^11.1.12",
"tailwindcss": "^3.4.6",
"typescript": "5.5.4"
}

70
pnpm-lock.yaml generated
View File

@ -48,6 +48,9 @@ importers:
'@types/react-dom':
specifier: 18.3.0
version: 18.3.0
'@types/react-slick':
specifier: ^0.23.13
version: 0.23.13
autoprefixer:
specifier: ^10.4.19
version: 10.4.19(postcss@8.4.39)
@ -60,6 +63,12 @@ importers:
prettier-plugin-tailwindcss:
specifier: ^0.6.5
version: 0.6.5(prettier@3.3.3)
react-slick:
specifier: ^0.30.2
version: 0.30.2(react-dom@19.0.0-rc-3208e73e-20240730(react@19.0.0-rc-3208e73e-20240730))(react@19.0.0-rc-3208e73e-20240730)
swiper:
specifier: ^11.1.12
version: 11.1.12
tailwindcss:
specifier: ^3.4.6
version: 3.4.6
@ -382,6 +391,9 @@ packages:
'@types/react-dom@18.3.0':
resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==}
'@types/react-slick@0.23.13':
resolution: {integrity: sha512-bNZfDhe/L8t5OQzIyhrRhBr/61pfBcWaYJoq6UDqFtv5LMwfg4NsVDD2J8N01JqdAdxLjOt66OZEp6PX+dGs/A==}
'@types/react@18.3.3':
resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==}
@ -452,6 +464,9 @@ packages:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
classnames@2.5.1:
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
@ -511,6 +526,9 @@ packages:
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
enquire.js@2.1.6:
resolution: {integrity: sha512-/KujNpO+PT63F7Hlpu4h3pE3TokKRHN26JYmQpPyjkRD/N57R7bPDNojMXdi7uveAKjYB7yQnartCxZnFWr0Xw==}
escalade@3.1.2:
resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
engines: {node: '>=6'}
@ -602,6 +620,9 @@ packages:
resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==}
hasBin: true
json2mq@0.2.0:
resolution: {integrity: sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==}
lilconfig@2.1.0:
resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
engines: {node: '>=10'}
@ -616,6 +637,9 @@ packages:
lodash.castarray@4.4.0:
resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
lodash.debounce@4.0.8:
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
lodash.isplainobject@4.0.6:
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
@ -832,6 +856,12 @@ packages:
peerDependencies:
react: 19.0.0-rc-3208e73e-20240730
react-slick@0.30.2:
resolution: {integrity: sha512-XvQJi7mRHuiU3b9irsqS9SGIgftIfdV5/tNcURTb5LdIokRA5kIIx3l4rlq2XYHfxcSntXapoRg/GxaVOM1yfg==}
peerDependencies:
react: ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0
react-dom: ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0
react@19.0.0-rc-3208e73e-20240730:
resolution: {integrity: sha512-4TmFOcgSfwM8w18vXLnEt8tb3ilO9a0GRJA9zQSYjZ5ie6g/zkxagRvZvZbEmhaNgDSF/PKmEdWmfBtlUBcjkA==}
engines: {node: '>=0.10.0'}
@ -843,6 +873,9 @@ packages:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
resize-observer-polyfill@1.5.1:
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
resolve@1.22.8:
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
hasBin: true
@ -895,6 +928,9 @@ packages:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
engines: {node: '>=10.0.0'}
string-convert@0.2.1:
resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@ -933,6 +969,10 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
swiper@11.1.12:
resolution: {integrity: sha512-PUkCToYAZMB4kP7z+YfPnkMHOMwMO71g8vUhz2o5INGIgIMb6Sb0XiP6cEJFsiFTd7FRDn5XCbg+KVKPDZqXLw==}
engines: {node: '>= 4.7.0'}
tabbable@6.2.0:
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
@ -1261,6 +1301,10 @@ snapshots:
dependencies:
'@types/react': 18.3.3
'@types/react-slick@0.23.13':
dependencies:
'@types/react': 18.3.3
'@types/react@18.3.3':
dependencies:
'@types/prop-types': 15.7.12
@ -1334,6 +1378,8 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
classnames@2.5.1: {}
client-only@0.0.1: {}
clsx@2.1.1: {}
@ -1383,6 +1429,8 @@ snapshots:
emoji-regex@9.2.2: {}
enquire.js@2.1.6: {}
escalade@3.1.2: {}
fast-glob@3.3.2:
@ -1471,6 +1519,10 @@ snapshots:
jiti@1.21.6: {}
json2mq@0.2.0:
dependencies:
string-convert: 0.2.1
lilconfig@2.1.0: {}
lilconfig@3.1.2: {}
@ -1479,6 +1531,8 @@ snapshots:
lodash.castarray@4.4.0: {}
lodash.debounce@4.0.8: {}
lodash.isplainobject@4.0.6: {}
lodash.merge@4.6.2: {}
@ -1623,6 +1677,16 @@ snapshots:
react: 19.0.0-rc-3208e73e-20240730
scheduler: 0.25.0-rc-3208e73e-20240730
react-slick@0.30.2(react-dom@19.0.0-rc-3208e73e-20240730(react@19.0.0-rc-3208e73e-20240730))(react@19.0.0-rc-3208e73e-20240730):
dependencies:
classnames: 2.5.1
enquire.js: 2.1.6
json2mq: 0.2.0
lodash.debounce: 4.0.8
react: 19.0.0-rc-3208e73e-20240730
react-dom: 19.0.0-rc-3208e73e-20240730(react@19.0.0-rc-3208e73e-20240730)
resize-observer-polyfill: 1.5.1
react@19.0.0-rc-3208e73e-20240730: {}
read-cache@1.0.0:
@ -1633,6 +1697,8 @@ snapshots:
dependencies:
picomatch: 2.3.1
resize-observer-polyfill@1.5.1: {}
resolve@1.22.8:
dependencies:
is-core-module: 2.15.0
@ -1699,6 +1765,8 @@ snapshots:
streamsearch@1.1.0: {}
string-convert@0.2.1: {}
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
@ -1736,6 +1804,8 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
swiper@11.1.12: {}
tabbable@6.2.0: {}
tailwindcss@3.4.6:

View File

@ -6,7 +6,11 @@ module.exports = {
theme: {
extend: {
fontFamily: {
sans: ['var(--font-geist-sans)']
baskerville: ['Libre Baskerville', 'serif']
},
colors: {
lightText: '#FFF2DB',
mainBG: '#F8E8D1'
},
keyframes: {
fadeIn: {