mirror of
https://github.com/vercel/commerce.git
synced 2025-05-16 14:36:59 +00:00
Merge branch 'feat/add-product-ld' of https://github.com/abhu-A-J/commerce into feat/add-product-ld
This commit is contained in:
commit
577b7bdbc6
@ -4,6 +4,7 @@ import { notFound } from 'next/navigation';
|
||||
|
||||
import Grid from 'components/grid';
|
||||
import ProductGridItems from 'components/layout/product-grid-items';
|
||||
import { defaultSort, sorting } from 'lib/constants';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
@ -32,8 +33,16 @@ export async function generateMetadata({
|
||||
};
|
||||
}
|
||||
|
||||
export default async function CategoryPage({ params }: { params: { collection: string } }) {
|
||||
const products = await getCollectionProducts(params.collection);
|
||||
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>
|
||||
|
@ -4,7 +4,7 @@ import Link from 'next/link';
|
||||
|
||||
export async function Carousel() {
|
||||
// Collections that start with `hidden-*` are hidden from the search page.
|
||||
const products = await getCollectionProducts('hidden-homepage-carousel');
|
||||
const products = await getCollectionProducts({ collection: 'hidden-homepage-carousel' });
|
||||
|
||||
if (!products?.length) return null;
|
||||
|
||||
|
@ -37,7 +37,9 @@ function ThreeItemGridItem({
|
||||
|
||||
export async function ThreeItemGrid() {
|
||||
// Collections that start with `hidden-*` are hidden from the search page.
|
||||
const homepageItems = await getCollectionProducts('hidden-homepage-featured-items');
|
||||
const homepageItems = await getCollectionProducts({
|
||||
collection: 'hidden-homepage-featured-items'
|
||||
});
|
||||
|
||||
if (!homepageItems[0] || !homepageItems[1] || !homepageItems[2]) return null;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { Dialog } from '@headlessui/react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import Link from 'next/link';
|
||||
import { usePathname, useSearchParams } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
@ -42,57 +42,77 @@ export default function MobileMenu({ menu }: { menu: Menu[] }) {
|
||||
>
|
||||
<MenuIcon className="h-6" />
|
||||
</button>
|
||||
<Dialog
|
||||
open={mobileMenuIsOpen}
|
||||
onClose={() => {
|
||||
setMobileMenuIsOpen(false);
|
||||
}}
|
||||
className="relative z-50"
|
||||
>
|
||||
<div className="fixed inset-0 flex justify-end" data-testid="mobile-menu">
|
||||
<Dialog.Panel
|
||||
<AnimatePresence initial={false}>
|
||||
{mobileMenuIsOpen && (
|
||||
<Dialog
|
||||
as={motion.div}
|
||||
variants={{
|
||||
open: { opacity: 1 }
|
||||
initial="closed"
|
||||
animate="open"
|
||||
exit="closed"
|
||||
key="dialog"
|
||||
static
|
||||
open={mobileMenuIsOpen}
|
||||
onClose={() => {
|
||||
setMobileMenuIsOpen(false);
|
||||
}}
|
||||
className="flex w-full flex-col bg-white pb-6 dark:bg-black"
|
||||
className="relative z-50"
|
||||
>
|
||||
<div className="p-4">
|
||||
<button
|
||||
className="mb-4"
|
||||
onClick={() => {
|
||||
setMobileMenuIsOpen(false);
|
||||
<motion.div
|
||||
variants={{
|
||||
open: { opacity: 1, backdropFilter: 'blur(0.5px)' },
|
||||
closed: { opacity: 0, backdropFilter: 'blur(0px)' }
|
||||
}}
|
||||
className="fixed inset-0 bg-black/30"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div className="fixed inset-0 flex justify-end" data-testid="mobile-menu">
|
||||
<Dialog.Panel
|
||||
as={motion.div}
|
||||
variants={{
|
||||
open: { translateX: 0 },
|
||||
closed: { translateX: '-100%' }
|
||||
}}
|
||||
aria-label="Close mobile menu"
|
||||
data-testid="close-mobile-menu"
|
||||
transition={{ type: 'spring', bounce: 0, duration: 0.3 }}
|
||||
className="flex w-full flex-col bg-white pb-6 dark:bg-black"
|
||||
>
|
||||
<CloseIcon className="h-6" />
|
||||
</button>
|
||||
<div className="p-4">
|
||||
<button
|
||||
className="mb-4"
|
||||
onClick={() => {
|
||||
setMobileMenuIsOpen(false);
|
||||
}}
|
||||
aria-label="Close mobile menu"
|
||||
data-testid="close-mobile-menu"
|
||||
>
|
||||
<CloseIcon className="h-6" />
|
||||
</button>
|
||||
|
||||
<div className="mb-4 w-full">
|
||||
<Search />
|
||||
</div>
|
||||
{menu.length ? (
|
||||
<ul className="flex flex-col">
|
||||
{menu.map((item: Menu) => (
|
||||
<li key={item.title}>
|
||||
<Link
|
||||
href={item.path}
|
||||
className="rounded-lg py-1 text-xl text-black transition-colors hover:text-gray-500 dark:text-white"
|
||||
onClick={() => {
|
||||
setMobileMenuIsOpen(false);
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
<div className="mb-4 w-full">
|
||||
<Search />
|
||||
</div>
|
||||
{menu.length ? (
|
||||
<ul className="flex flex-col">
|
||||
{menu.map((item: Menu) => (
|
||||
<li key={item.title}>
|
||||
<Link
|
||||
href={item.path}
|
||||
className="rounded-lg py-1 text-xl text-black transition-colors hover:text-gray-500 dark:text-white"
|
||||
onClick={() => {
|
||||
setMobileMenuIsOpen(false);
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Dialog>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
|
||||
import SearchIcon from 'components/icons/search';
|
||||
import { createUrl } from 'lib/utils';
|
||||
|
||||
export default function Search() {
|
||||
const router = useRouter();
|
||||
@ -13,12 +14,15 @@ export default function Search() {
|
||||
|
||||
const val = e.target as HTMLFormElement;
|
||||
const search = val.search as HTMLInputElement;
|
||||
const newParams = new URLSearchParams(searchParams.toString());
|
||||
|
||||
if (search.value) {
|
||||
router.push(`/search?q=${search.value}`);
|
||||
newParams.set('q', search.value);
|
||||
} else {
|
||||
router.push(`/search`);
|
||||
newParams.delete('q');
|
||||
}
|
||||
|
||||
router.push(createUrl('/search', newParams));
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -12,6 +12,9 @@ function PathFilterItem({ item }: { item: PathFilterItem }) {
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const [active, setActive] = useState(pathname === item.path);
|
||||
const newParams = new URLSearchParams(searchParams.toString());
|
||||
|
||||
newParams.delete('q');
|
||||
|
||||
useEffect(() => {
|
||||
setActive(pathname === item.path);
|
||||
@ -20,7 +23,7 @@ function PathFilterItem({ item }: { item: PathFilterItem }) {
|
||||
return (
|
||||
<li className="mt-2 flex text-sm text-gray-400" key={item.title}>
|
||||
<Link
|
||||
href={createUrl(item.path, searchParams)}
|
||||
href={createUrl(item.path, newParams)}
|
||||
className={clsx('w-full hover:text-gray-800 dark:hover:text-gray-100', {
|
||||
'text-gray-600 dark:text-gray-400': !active,
|
||||
'font-semibold text-black dark:text-white': active
|
||||
@ -36,6 +39,7 @@ function SortFilterItem({ item }: { item: SortFilterItem }) {
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const [active, setActive] = useState(searchParams.get('sort') === item.slug);
|
||||
const q = searchParams.get('q');
|
||||
|
||||
useEffect(() => {
|
||||
setActive(searchParams.get('sort') === item.slug);
|
||||
@ -43,7 +47,13 @@ function SortFilterItem({ item }: { item: SortFilterItem }) {
|
||||
|
||||
const href =
|
||||
item.slug && item.slug.length
|
||||
? createUrl(pathname, new URLSearchParams({ sort: item.slug }))
|
||||
? createUrl(
|
||||
pathname,
|
||||
new URLSearchParams({
|
||||
...(q && { q }),
|
||||
sort: item.slug
|
||||
})
|
||||
)
|
||||
: pathname;
|
||||
|
||||
return (
|
||||
|
@ -257,16 +257,26 @@ export async function getCollection(handle: string): Promise<Collection | undefi
|
||||
return reshapeCollection(res.body.data.collection);
|
||||
}
|
||||
|
||||
export async function getCollectionProducts(handle: string): Promise<Product[]> {
|
||||
export async function getCollectionProducts({
|
||||
collection,
|
||||
reverse,
|
||||
sortKey
|
||||
}: {
|
||||
collection: string;
|
||||
reverse?: boolean;
|
||||
sortKey?: string;
|
||||
}): Promise<Product[]> {
|
||||
const res = await shopifyFetch<ShopifyCollectionProductsOperation>({
|
||||
query: getCollectionProductsQuery,
|
||||
variables: {
|
||||
handle
|
||||
handle: collection,
|
||||
reverse,
|
||||
sortKey
|
||||
}
|
||||
});
|
||||
|
||||
if (!res.body.data.collection) {
|
||||
console.log('No collection found for handle', handle);
|
||||
console.log(`No collection found for \`${collection}\``);
|
||||
return [];
|
||||
}
|
||||
|
||||
|
@ -37,9 +37,13 @@ export const getCollectionsQuery = /* GraphQL */ `
|
||||
`;
|
||||
|
||||
export const getCollectionProductsQuery = /* GraphQL */ `
|
||||
query getCollectionProducts($handle: String!) {
|
||||
query getCollectionProducts(
|
||||
$handle: String!
|
||||
$sortKey: ProductCollectionSortKeys
|
||||
$reverse: Boolean
|
||||
) {
|
||||
collection(handle: $handle) {
|
||||
products(first: 100) {
|
||||
products(sortKey: $sortKey, reverse: $reverse, first: 100) {
|
||||
edges {
|
||||
node {
|
||||
...product
|
||||
|
@ -201,6 +201,8 @@ export type ShopifyCollectionProductsOperation = {
|
||||
};
|
||||
variables: {
|
||||
handle: string;
|
||||
reverse?: boolean;
|
||||
sortKey?: string;
|
||||
};
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user