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:
Rami Bitar
2026-08-01 12:05:57 -04:00
co-authored by Claude Opus 5
parent 69f7435d6e
commit e04f1e0405
20 changed files with 546 additions and 466 deletions
+27 -4
View File
@@ -81,11 +81,19 @@ export interface Product {
interface UseProductsOptions {
first?: number;
/** Cursor from a previous page's `endCursor`; omit for the first page. */
after?: string | null;
query?: string;
sortKey?: 'BEST_SELLING' | 'CREATED_AT' | 'PRICE' | 'TITLE';
reverse?: boolean;
}
export interface ProductsPage {
products: Product[];
hasNextPage: boolean;
endCursor: string | null;
}
interface UseProductsReturn {
products: Product[];
loading: boolean;
@@ -94,18 +102,33 @@ interface UseProductsReturn {
}
// Fetch multiple products
export async function getProducts({
export async function getProducts(
options: UseProductsOptions = {}
): Promise<Product[]> {
const { products } = await getProductsPage(options);
return products;
}
// Same fetch, but keeps the cursor so callers can page through the catalogue.
export async function getProductsPage({
first = 20,
after = null,
query = '',
sortKey = 'BEST_SELLING',
reverse = false,
}: UseProductsOptions = {}): Promise<Product[]> {
}: UseProductsOptions = {}): Promise<ProductsPage> {
const response = await shopifyFetch({
query: GET_PRODUCTS_QUERY,
variables: { first, query, sortKey, reverse },
variables: { first, after, query, sortKey, reverse },
});
return response.data.products.edges.map((edge: { node: Product }) => edge.node);
const { edges, pageInfo } = response.data.products;
return {
products: edges.map((edge: { node: Product }) => edge.node),
hasNextPage: Boolean(pageInfo?.hasNextPage),
endCursor: pageInfo?.endCursor ?? null,
};
}
// Fetch a single product by handle