mirror of
https://github.com/vercel/commerce.git
synced 2025-05-08 18:57:51 +00:00
clean out all the stuff we don't use
This commit is contained in:
parent
f6652da4ab
commit
a2c8ca6b61
14
.env.example
14
.env.example
@ -1,7 +1,7 @@
|
|||||||
COMPANY_NAME="Vercel Inc."
|
# API specifics
|
||||||
TWITTER_CREATOR="@vercel"
|
NEXT_PUBLIC_FW_API_URL="https://api.staging.fourthwall.com"
|
||||||
TWITTER_SITE="https://nextjs.org/commerce"
|
|
||||||
SITE_NAME="Next.js Commerce"
|
# Site specifics
|
||||||
SHOPIFY_REVALIDATION_SECRET=""
|
NEXT_PUBLIC_FW_COLLECTION="launch"
|
||||||
SHOPIFY_STOREFRONT_ACCESS_TOKEN=""
|
NEXT_PUBLIC_FW_PUBLIC_TOKEN=""
|
||||||
SHOPIFY_STORE_DOMAIN="[your-shopify-store-subdomain].myshopify.com"
|
NEXT_PUBLIC_FW_CHECKOUT="https://jieren-shop.staging.fourthwall.com"
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
import Footer from 'components/layout/footer';
|
|
||||||
|
|
||||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="w-full">
|
|
||||||
<div className="mx-8 max-w-2xl py-20 sm:mx-auto">{children}</div>
|
|
||||||
</div>
|
|
||||||
<Footer />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
import OpengraphImage from 'components/opengraph-image';
|
|
||||||
import { getPage } from 'lib/shopify';
|
|
||||||
|
|
||||||
export const runtime = 'edge';
|
|
||||||
|
|
||||||
export default async function Image({ params }: { params: { page: string } }) {
|
|
||||||
const page = await getPage(params.page);
|
|
||||||
const title = page.seo?.title || page.title;
|
|
||||||
|
|
||||||
return await OpengraphImage({ title });
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
import type { Metadata } from 'next';
|
|
||||||
|
|
||||||
import Prose from 'components/prose';
|
|
||||||
import { getPage } from 'lib/shopify';
|
|
||||||
import { notFound } from 'next/navigation';
|
|
||||||
|
|
||||||
export async function generateMetadata({
|
|
||||||
params
|
|
||||||
}: {
|
|
||||||
params: { page: string };
|
|
||||||
}): Promise<Metadata> {
|
|
||||||
const page = await getPage(params.page);
|
|
||||||
|
|
||||||
if (!page) return notFound();
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: page.seo?.title || page.title,
|
|
||||||
description: page.seo?.description || page.bodySummary,
|
|
||||||
openGraph: {
|
|
||||||
publishedTime: page.createdAt,
|
|
||||||
modifiedTime: page.updatedAt,
|
|
||||||
type: 'article'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function Page({ params }: { params: { page: string } }) {
|
|
||||||
const page = await getPage(params.page);
|
|
||||||
|
|
||||||
if (!page) return notFound();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h1 className="mb-8 text-5xl font-bold">{page.title}</h1>
|
|
||||||
<Prose className="mb-8" html={page.body as string} />
|
|
||||||
<p className="text-sm italic">
|
|
||||||
{`This document was last updated on ${new Intl.DateTimeFormat(undefined, {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'long',
|
|
||||||
day: 'numeric'
|
|
||||||
}).format(new Date(page.updatedAt))}.`}
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
import OpengraphImage from 'components/opengraph-image';
|
|
||||||
import { getCollection } from 'lib/shopify';
|
|
||||||
|
|
||||||
export const runtime = 'edge';
|
|
||||||
|
|
||||||
export default async function Image({ params }: { params: { collection: string } }) {
|
|
||||||
const collection = await getCollection(params.collection);
|
|
||||||
const title = collection?.seo?.title || collection?.title;
|
|
||||||
|
|
||||||
return await OpengraphImage({ title });
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
import { getCollection } from 'lib/shopify';
|
|
||||||
import { Metadata } from 'next';
|
|
||||||
import { notFound } from 'next/navigation';
|
|
||||||
|
|
||||||
import Grid from 'components/grid';
|
|
||||||
import ProductGridItems from 'components/layout/product-grid-items';
|
|
||||||
import { defaultSort, sorting } from 'lib/constants';
|
|
||||||
import { getCollectionProducts } from 'lib/fourthwall';
|
|
||||||
|
|
||||||
export async function generateMetadata({
|
|
||||||
params
|
|
||||||
}: {
|
|
||||||
params: { collection: string };
|
|
||||||
}): Promise<Metadata> {
|
|
||||||
const collection = await getCollection(params.collection);
|
|
||||||
|
|
||||||
if (!collection) return notFound();
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: collection.seo?.title || collection.title,
|
|
||||||
description:
|
|
||||||
collection.seo?.description || collection.description || `${collection.title} products`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function CategoryPage({
|
|
||||||
params,
|
|
||||||
searchParams
|
|
||||||
}: {
|
|
||||||
params: { collection: string };
|
|
||||||
searchParams?: { [key: string]: string | string[] | undefined };
|
|
||||||
}) {
|
|
||||||
const { sort } = searchParams as { [key: string]: string };
|
|
||||||
const { sortKey, reverse } = sorting.find((item) => item.slug === sort) || defaultSort;
|
|
||||||
const products = await getCollectionProducts({ collection: params.collection, sortKey, reverse });
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section>
|
|
||||||
{products.length === 0 ? (
|
|
||||||
<p className="py-3 text-lg">{`No products found in this collection`}</p>
|
|
||||||
) : (
|
|
||||||
<Grid className="grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
|
|
||||||
<ProductGridItems products={products} />
|
|
||||||
</Grid>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { useSearchParams } from 'next/navigation';
|
|
||||||
import { Fragment } from 'react';
|
|
||||||
|
|
||||||
// Ensure children are re-rendered when the search query changes
|
|
||||||
export default function ChildrenWrapper({ children }: { children: React.ReactNode }) {
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
return <Fragment key={searchParams.get('q')}>{children}</Fragment>;
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
import Footer from 'components/layout/footer';
|
|
||||||
import Collections from 'components/layout/search/collections';
|
|
||||||
import FilterList from 'components/layout/search/filter';
|
|
||||||
import { sorting } from 'lib/constants';
|
|
||||||
import ChildrenWrapper from './children-wrapper';
|
|
||||||
|
|
||||||
export default function SearchLayout({ children }: { children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="mx-auto flex max-w-screen-2xl flex-col gap-8 px-4 pb-4 text-black md:flex-row dark:text-white">
|
|
||||||
<div className="order-first w-full flex-none md:max-w-[125px]">
|
|
||||||
<Collections />
|
|
||||||
</div>
|
|
||||||
<div className="order-last min-h-screen w-full md:order-none">
|
|
||||||
<ChildrenWrapper>{children}</ChildrenWrapper>
|
|
||||||
</div>
|
|
||||||
<div className="order-none flex-none md:order-last md:w-[125px]">
|
|
||||||
<FilterList list={sorting} title="Sort by" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Footer />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
import Grid from 'components/grid';
|
|
||||||
|
|
||||||
export default function Loading() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="mb-4 h-6" />
|
|
||||||
<Grid className="grid-cols-2 lg:grid-cols-3">
|
|
||||||
{Array(12)
|
|
||||||
.fill(0)
|
|
||||||
.map((_, index) => {
|
|
||||||
return (
|
|
||||||
<Grid.Item key={index} className="animate-pulse bg-neutral-100 dark:bg-neutral-800" />
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Grid>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
import Grid from 'components/grid';
|
|
||||||
import ProductGridItems from 'components/layout/product-grid-items';
|
|
||||||
import { defaultSort, sorting } from 'lib/constants';
|
|
||||||
import { getProducts } from 'lib/shopify';
|
|
||||||
|
|
||||||
export const metadata = {
|
|
||||||
title: 'Search',
|
|
||||||
description: 'Search for products in the store.'
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function SearchPage({
|
|
||||||
searchParams
|
|
||||||
}: {
|
|
||||||
searchParams?: { [key: string]: string | string[] | undefined };
|
|
||||||
}) {
|
|
||||||
const { sort, q: searchValue } = searchParams as { [key: string]: string };
|
|
||||||
const { sortKey, reverse } = sorting.find((item) => item.slug === sort) || defaultSort;
|
|
||||||
|
|
||||||
const products = await getProducts({ sortKey, reverse, query: searchValue });
|
|
||||||
const resultsText = products.length > 1 ? 'results' : 'result';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{searchValue ? (
|
|
||||||
<p className="mb-4">
|
|
||||||
{products.length === 0
|
|
||||||
? 'There are no products that match '
|
|
||||||
: `Showing ${products.length} ${resultsText} for `}
|
|
||||||
<span className="font-bold">"{searchValue}"</span>
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
{products.length > 0 ? (
|
|
||||||
<Grid className="grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
|
|
||||||
<ProductGridItems products={products} />
|
|
||||||
</Grid>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -4,7 +4,7 @@ import { PlusIcon } from '@heroicons/react/24/outline';
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { addItem } from 'components/cart/actions';
|
import { addItem } from 'components/cart/actions';
|
||||||
import { useProduct } from 'components/product/product-context';
|
import { useProduct } from 'components/product/product-context';
|
||||||
import { Product, ProductVariant } from 'lib/shopify/types';
|
import { Product, ProductVariant } from 'lib/types';
|
||||||
import { useFormState } from 'react-dom';
|
import { useFormState } from 'react-dom';
|
||||||
import { useCart } from './cart-context';
|
import { useCart } from './cart-context';
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import type { Cart, CartItem, Product, ProductVariant } from 'lib/shopify/types';
|
import type { Cart, CartItem, Product, ProductVariant } from 'lib/types';
|
||||||
import React, { createContext, use, useContext, useMemo, useOptimistic } from 'react';
|
import React, { createContext, use, useContext, useMemo, useOptimistic } from 'react';
|
||||||
|
|
||||||
type UpdateType = 'plus' | 'minus' | 'delete';
|
type UpdateType = 'plus' | 'minus' | 'delete';
|
||||||
@ -94,6 +94,7 @@ function createEmptyCart(): Cart {
|
|||||||
checkoutUrl: '',
|
checkoutUrl: '',
|
||||||
totalQuantity: 0,
|
totalQuantity: 0,
|
||||||
lines: [],
|
lines: [],
|
||||||
|
currency: 'USD',
|
||||||
cost: {
|
cost: {
|
||||||
subtotalAmount: { amount: '0', currencyCode: 'USD' },
|
subtotalAmount: { amount: '0', currencyCode: 'USD' },
|
||||||
totalAmount: { amount: '0', currencyCode: 'USD' },
|
totalAmount: { amount: '0', currencyCode: 'USD' },
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { XMarkIcon } from '@heroicons/react/24/outline';
|
import { XMarkIcon } from '@heroicons/react/24/outline';
|
||||||
import { removeItem } from 'components/cart/actions';
|
import { removeItem } from 'components/cart/actions';
|
||||||
import type { CartItem } from 'lib/shopify/types';
|
import type { CartItem } from 'lib/types';
|
||||||
import { useFormState } from 'react-dom';
|
import { useFormState } from 'react-dom';
|
||||||
|
|
||||||
export function DeleteItemButton({
|
export function DeleteItemButton({
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import { MinusIcon, PlusIcon } from '@heroicons/react/24/outline';
|
import { MinusIcon, PlusIcon } from '@heroicons/react/24/outline';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { updateItemQuantity } from 'components/cart/actions';
|
import { updateItemQuantity } from 'components/cart/actions';
|
||||||
import type { CartItem } from 'lib/shopify/types';
|
import type { CartItem } from 'lib/types';
|
||||||
import { useFormState } from 'react-dom';
|
import { useFormState } from 'react-dom';
|
||||||
|
|
||||||
function SubmitButton({ type }: { type: 'plus' | 'minus' }) {
|
function SubmitButton({ type }: { type: 'plus' | 'minus' }) {
|
||||||
|
@ -9,7 +9,7 @@ export default function OpenCart({
|
|||||||
quantity?: number;
|
quantity?: number;
|
||||||
}) {
|
}) {
|
||||||
return (
|
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-neutral-200 text-black transition-colors hover:bg-gray-50 dark:border-neutral-700 dark:text-white">
|
||||||
<ShoppingCartIcon
|
<ShoppingCartIcon
|
||||||
className={clsx('h-4 transition-all ease-in-out hover:scale-110', className)}
|
className={clsx('h-4 transition-all ease-in-out hover:scale-110', className)}
|
||||||
/>
|
/>
|
||||||
|
@ -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/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';
|
||||||
|
@ -30,7 +30,7 @@ export function CurrencySelector({ currency }: { currency: string; }) {
|
|||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-indigo-500"
|
className="inline-flex h-11 justify-center items-center w-full rounded-md border border-neutral-200 px-4 py-2 bg-white text-sm font-medium text-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-indigo-500"
|
||||||
onClick={() => setIsOpen(!isOpen)}
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
>
|
>
|
||||||
{selectedCurrency}
|
{selectedCurrency}
|
||||||
|
@ -1,24 +1,11 @@
|
|||||||
import CartModal from 'components/cart/modal';
|
import CartModal from 'components/cart/modal';
|
||||||
import LogoSquare from 'components/logo-square';
|
import LogoSquare from 'components/logo-square';
|
||||||
import { Menu } from 'lib/shopify/types';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Suspense } from 'react';
|
|
||||||
import { CurrencySelector } from './currency';
|
import { CurrencySelector } from './currency';
|
||||||
import MobileMenu from './mobile-menu';
|
|
||||||
import Search, { SearchSkeleton } from './search';
|
|
||||||
|
|
||||||
const { SITE_NAME } = process.env;
|
|
||||||
|
|
||||||
export function Navbar({currency}: {currency: string}) {
|
export function Navbar({currency}: {currency: string}) {
|
||||||
const menu: any[] = [];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="relative flex items-center justify-between p-4 lg:px-6">
|
<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 items-center">
|
||||||
<div className="flex w-full md:w-1/3">
|
<div className="flex w-full md:w-1/3">
|
||||||
<Link
|
<Link
|
||||||
@ -28,31 +15,13 @@ export function Navbar({currency}: {currency: string}) {
|
|||||||
>
|
>
|
||||||
<LogoSquare />
|
<LogoSquare />
|
||||||
<div className="ml-2 flex-none text-sm font-medium uppercase md:hidden lg:block">
|
<div className="ml-2 flex-none text-sm font-medium uppercase md:hidden lg:block">
|
||||||
{SITE_NAME}
|
Launch on Fourthwall!
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</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}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden justify-center md:flex md:w-1/3">
|
<div className="hidden justify-center md:flex md:w-1/3">
|
||||||
<Suspense fallback={<SearchSkeleton />}>
|
|
||||||
<Search />
|
|
||||||
</Suspense>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end md:w-1/3">
|
<div className="flex justify-end md:w-1/3 gap-4">
|
||||||
<CurrencySelector currency={currency} />
|
<CurrencySelector currency={currency} />
|
||||||
<CartModal />
|
<CartModal />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,100 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { Dialog, Transition } from '@headlessui/react';
|
|
||||||
import Link from 'next/link';
|
|
||||||
import { usePathname, useSearchParams } from 'next/navigation';
|
|
||||||
import { Fragment, Suspense, useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline';
|
|
||||||
import { Menu } from 'lib/shopify/types';
|
|
||||||
import Search, { SearchSkeleton } from './search';
|
|
||||||
|
|
||||||
export default function MobileMenu({ menu }: { menu: Menu[] }) {
|
|
||||||
const pathname = usePathname();
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const openMobileMenu = () => setIsOpen(true);
|
|
||||||
const closeMobileMenu = () => setIsOpen(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleResize = () => {
|
|
||||||
if (window.innerWidth > 768) {
|
|
||||||
setIsOpen(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
window.addEventListener('resize', handleResize);
|
|
||||||
return () => window.removeEventListener('resize', handleResize);
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsOpen(false);
|
|
||||||
}, [pathname, searchParams]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
onClick={openMobileMenu}
|
|
||||||
aria-label="Open mobile menu"
|
|
||||||
className="flex h-11 w-11 items-center justify-center rounded-md border border-neutral-200 text-black transition-colors md:hidden dark:border-neutral-700 dark:text-white"
|
|
||||||
>
|
|
||||||
<Bars3Icon className="h-4" />
|
|
||||||
</button>
|
|
||||||
<Transition show={isOpen}>
|
|
||||||
<Dialog onClose={closeMobileMenu} className="relative z-50">
|
|
||||||
<Transition.Child
|
|
||||||
as={Fragment}
|
|
||||||
enter="transition-all ease-in-out duration-300"
|
|
||||||
enterFrom="opacity-0 backdrop-blur-none"
|
|
||||||
enterTo="opacity-100 backdrop-blur-[.5px]"
|
|
||||||
leave="transition-all ease-in-out duration-200"
|
|
||||||
leaveFrom="opacity-100 backdrop-blur-[.5px]"
|
|
||||||
leaveTo="opacity-0 backdrop-blur-none"
|
|
||||||
>
|
|
||||||
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
|
|
||||||
</Transition.Child>
|
|
||||||
<Transition.Child
|
|
||||||
as={Fragment}
|
|
||||||
enter="transition-all ease-in-out duration-300"
|
|
||||||
enterFrom="translate-x-[-100%]"
|
|
||||||
enterTo="translate-x-0"
|
|
||||||
leave="transition-all ease-in-out duration-200"
|
|
||||||
leaveFrom="translate-x-0"
|
|
||||||
leaveTo="translate-x-[-100%]"
|
|
||||||
>
|
|
||||||
<Dialog.Panel className="fixed bottom-0 left-0 right-0 top-0 flex h-full w-full flex-col bg-white pb-6 dark:bg-black">
|
|
||||||
<div className="p-4">
|
|
||||||
<button
|
|
||||||
className="mb-4 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"
|
|
||||||
onClick={closeMobileMenu}
|
|
||||||
aria-label="Close mobile menu"
|
|
||||||
>
|
|
||||||
<XMarkIcon className="h-6" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div className="mb-4 w-full">
|
|
||||||
<Suspense fallback={<SearchSkeleton />}>
|
|
||||||
<Search />
|
|
||||||
</Suspense>
|
|
||||||
</div>
|
|
||||||
{menu.length ? (
|
|
||||||
<ul className="flex w-full flex-col">
|
|
||||||
{menu.map((item: Menu) => (
|
|
||||||
<li
|
|
||||||
className="py-2 text-xl text-black transition-colors hover:text-neutral-500 dark:text-white"
|
|
||||||
key={item.title}
|
|
||||||
>
|
|
||||||
<Link href={item.path} prefetch={true} onClick={closeMobileMenu}>
|
|
||||||
{item.title}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</Dialog.Panel>
|
|
||||||
</Transition.Child>
|
|
||||||
</Dialog>
|
|
||||||
</Transition>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
|
|
||||||
import Form from 'next/form';
|
|
||||||
import { useSearchParams } from 'next/navigation';
|
|
||||||
|
|
||||||
export default function Search() {
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form action="/search" className="w-max-[550px] relative w-full lg:w-80 xl:w-full">
|
|
||||||
<input
|
|
||||||
key={searchParams?.get('q')}
|
|
||||||
type="text"
|
|
||||||
name="q"
|
|
||||||
placeholder="Search for products..."
|
|
||||||
autoComplete="off"
|
|
||||||
defaultValue={searchParams?.get('q') || ''}
|
|
||||||
className="text-md w-full rounded-lg border bg-white px-4 py-2 text-black placeholder:text-neutral-500 md:text-sm dark:border-neutral-800 dark:bg-transparent dark:text-white dark:placeholder:text-neutral-400"
|
|
||||||
/>
|
|
||||||
<div className="absolute right-0 top-0 mr-3 flex h-full items-center">
|
|
||||||
<MagnifyingGlassIcon className="h-4" />
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SearchSkeleton() {
|
|
||||||
return (
|
|
||||||
<form className="w-max-[550px] relative w-full lg:w-80 xl:w-full">
|
|
||||||
<input
|
|
||||||
placeholder="Search for products..."
|
|
||||||
className="w-full rounded-lg border bg-white px-4 py-2 text-sm text-black placeholder:text-neutral-500 dark:border-neutral-800 dark:bg-transparent dark:text-white dark:placeholder:text-neutral-400"
|
|
||||||
/>
|
|
||||||
<div className="absolute right-0 top-0 mr-3 flex h-full items-center">
|
|
||||||
<MagnifyingGlassIcon className="h-4" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
import Grid from 'components/grid';
|
import Grid from 'components/grid';
|
||||||
import { GridTileImage } from 'components/grid/tile';
|
import { GridTileImage } from 'components/grid/tile';
|
||||||
import { Product } from 'lib/shopify/types';
|
import { Product } from 'lib/types';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
export default function ProductGridItems({ products }: { products: Product[] }) {
|
export default function ProductGridItems({ products }: { products: Product[] }) {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/24/outline';
|
import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/24/outline';
|
||||||
import { GridTileImage } from 'components/grid/tile';
|
import { GridTileImage } from 'components/grid/tile';
|
||||||
import { useProduct, useUpdateURL } from 'components/product/product-context';
|
import { useProduct, useUpdateURL } from 'components/product/product-context';
|
||||||
import { Product } from 'lib/shopify/types';
|
import { Product } from 'lib/types';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
|
||||||
export function Gallery({ product }: { product: Product }) {
|
export function Gallery({ product }: { product: Product }) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { AddToCart } from 'components/cart/add-to-cart';
|
import { AddToCart } from 'components/cart/add-to-cart';
|
||||||
import Price from 'components/price';
|
import Price from 'components/price';
|
||||||
import Prose from 'components/prose';
|
import Prose from 'components/prose';
|
||||||
import { Product } from 'lib/shopify/types';
|
import { Product } from 'lib/types';
|
||||||
import { VariantSelector } from './variant-selector';
|
import { VariantSelector } from './variant-selector';
|
||||||
|
|
||||||
export function ProductDescription({ product }: { product: Product }) {
|
export function ProductDescription({ product }: { product: Product }) {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useProduct, useUpdateURL } from 'components/product/product-context';
|
import { useProduct, useUpdateURL } from 'components/product/product-context';
|
||||||
import { ProductOption, ProductVariant } from 'lib/shopify/types';
|
import { ProductOption, ProductVariant } from 'lib/types';
|
||||||
|
|
||||||
type Combination = {
|
type Combination = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Cart } from "lib/shopify/types";
|
import { Cart } from "lib/types";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { Toaster } from "sonner";
|
import { Toaster } from "sonner";
|
||||||
import { CartProvider } from "./cart/cart-context";
|
import { CartProvider } from "./cart/cart-context";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Cart, Menu, Product } from "lib/shopify/types";
|
import { Cart, Menu, Product } from "lib/types";
|
||||||
import { reshapeCart, reshapeProduct, reshapeProducts } from "./reshape";
|
import { reshapeCart, reshapeProduct, reshapeProducts } from "./reshape";
|
||||||
import { FourthwallCart, FourthwallCheckout, FourthwallProduct } from "./types";
|
import { FourthwallCart, FourthwallCheckout, FourthwallProduct } from "./types";
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user