Template
Redesign cart and collections, fix product pagination
- Cart drawer: single-column layout matching the site — no bag icon, no dividers, square images, tighter type, discount code form, estimated total, and a Go to Checkout button - Cart: per-line loading state so one row's update no longer disables every other row or the checkout button - Discounts: cartDiscountCodesUpdate mutation plus discountCodes on the cart fragment, wired to an applyDiscountCode store action - Products: fix "load more" returning the same first page — the query now takes an `after` cursor and getProductsPage exposes pageInfo - Collections: cards match the product cards (no border, radius, blurb, or CTA row) at 4-up on desktop and 2-up on mobile; section headers match the products section - Shop Pay: cart permalink with payment=shop_pay instead of the hosted shop-js element - Buttons converted to the shadcn Button component throughout - PDP: swipeable image carousel with dots on mobile, sticky info column, colour swatches never fall back to variant photos - Rename the store from Stride to Shop; larger header wordmark Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jWbNNJLksC1QG8z8845FX
This commit is contained in:
co-authored by
Claude Opus 5
parent
69f7435d6e
commit
e04f1e0405
@@ -2,7 +2,7 @@
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import ProductCard from './product-card';
|
||||
import { getProducts } from '@/hooks/use-shopify-products';
|
||||
import { getProductsPage } from '@/hooks/use-shopify-products';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Loader } from '@/app/components/ui/loader';
|
||||
|
||||
@@ -64,11 +64,11 @@ const Products: React.FC<ProductsProps> = ({
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loadingMore, setLoadingMore] = useState(false);
|
||||
const [hasMoreProducts, setHasMoreProducts] = useState(true);
|
||||
const [cursor, setCursor] = useState<string | null>(null);
|
||||
|
||||
const fetchProducts = async (
|
||||
currentProducts: Product[] = [],
|
||||
loadMore = false
|
||||
) => {
|
||||
// Paging is cursor-based: without `after`, Shopify returns the same first
|
||||
// page every time and "load more" appends nothing.
|
||||
const fetchProducts = async (loadMore = false) => {
|
||||
try {
|
||||
if (loadMore) {
|
||||
setLoadingMore(true);
|
||||
@@ -77,27 +77,25 @@ const Products: React.FC<ProductsProps> = ({
|
||||
setError(null);
|
||||
}
|
||||
|
||||
const newProducts = await getProducts({
|
||||
const page = await getProductsPage({
|
||||
first: limit,
|
||||
after: loadMore ? cursor : null,
|
||||
sortKey: 'CREATED_AT',
|
||||
reverse: true,
|
||||
});
|
||||
|
||||
if (loadMore) {
|
||||
const existingIds = new Set(currentProducts.map((p) => p.id));
|
||||
const uniqueNewProducts = newProducts.filter(
|
||||
(p) => !existingIds.has(p.id)
|
||||
);
|
||||
setProducts((prev) => {
|
||||
if (!loadMore) return page.products;
|
||||
|
||||
if (uniqueNewProducts.length === 0) {
|
||||
setHasMoreProducts(false);
|
||||
} else {
|
||||
setProducts((prev) => [...prev, ...uniqueNewProducts]);
|
||||
}
|
||||
} else {
|
||||
setProducts(newProducts);
|
||||
setHasMoreProducts(newProducts.length === limit);
|
||||
}
|
||||
const existingIds = new Set(prev.map((p) => p.id));
|
||||
return [
|
||||
...prev,
|
||||
...page.products.filter((p) => !existingIds.has(p.id)),
|
||||
];
|
||||
});
|
||||
|
||||
setCursor(page.endCursor);
|
||||
setHasMoreProducts(page.hasNextPage);
|
||||
} catch (err) {
|
||||
console.error('Error fetching products:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to load products');
|
||||
@@ -113,7 +111,7 @@ const Products: React.FC<ProductsProps> = ({
|
||||
|
||||
const handleLoadMore = () => {
|
||||
if (!loadingMore && hasMoreProducts) {
|
||||
fetchProducts(products, true);
|
||||
fetchProducts(true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -146,7 +144,7 @@ const Products: React.FC<ProductsProps> = ({
|
||||
|
||||
if (error || products.length === 0) {
|
||||
return (
|
||||
<div className="py-20 bg-zinc-50">
|
||||
<div className="py-20 bg-background">
|
||||
<div className="max-w-screen-2xl mx-auto px-8 text-center">
|
||||
<h2 className="text-2xl md:text-3xl font-normal max-w-3xl mx-auto text-foreground">
|
||||
{title}
|
||||
@@ -154,21 +152,20 @@ const Products: React.FC<ProductsProps> = ({
|
||||
<p className="text-sm font-normal max-w-xl mx-auto text-muted-foreground mt-2.5 mb-12">
|
||||
{subtitle}
|
||||
</p>
|
||||
<div className="mx-auto max-w-sm bg-white border border-border rounded-3xl p-12">
|
||||
<i className="ri-inbox-line text-6xl text-muted-foreground mb-6 block"></i>
|
||||
<h3 className="font-semibold text-2xl mb-3 tracking-tight">
|
||||
{error ? 'Connection Error' : 'Coming Soon'}
|
||||
</h3>
|
||||
<p className="text-muted-foreground mb-8 text-base">
|
||||
{error ||
|
||||
'Our curated collection is being prepared. Please check back shortly.'}
|
||||
</p>
|
||||
{error && (
|
||||
<Button onClick={() => fetchProducts()} className="btn-modern">
|
||||
Try Again
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mb-6">
|
||||
{error ||
|
||||
'Our curated collection is being prepared. Please check back shortly.'}
|
||||
</p>
|
||||
{error && (
|
||||
<Button
|
||||
onClick={() => fetchProducts()}
|
||||
variant="outline"
|
||||
size="lg"
|
||||
className="font-normal text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
Try again
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -195,17 +192,12 @@ const Products: React.FC<ProductsProps> = ({
|
||||
<Button
|
||||
onClick={handleLoadMore}
|
||||
disabled={loadingMore}
|
||||
variant="outline"
|
||||
size="lg"
|
||||
className="btn-modern rounded-2xl px-14 py-7 text-sm tracking-widest font-medium border border-border"
|
||||
className="font-normal text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{loadingMore ? (
|
||||
<>
|
||||
<Loader size={18} className="mr-3" />
|
||||
LOADING MORE
|
||||
</>
|
||||
) : (
|
||||
'LOAD MORE PRODUCTS'
|
||||
)}
|
||||
{loadingMore && <Loader size={16} />}
|
||||
{loadingMore ? 'Loading' : 'Load more'}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user