From 8493a00aa435fcd03bc6524f6c7ab16d34467d4c Mon Sep 17 00:00:00 2001 From: notshop Date: Sun, 26 Apr 2026 16:36:12 +0000 Subject: [PATCH] chore: add client/src/pages/shop.tsx --- client/src/pages/shop.tsx | 150 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 client/src/pages/shop.tsx diff --git a/client/src/pages/shop.tsx b/client/src/pages/shop.tsx new file mode 100644 index 0000000..6bd873a --- /dev/null +++ b/client/src/pages/shop.tsx @@ -0,0 +1,150 @@ +import { useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { Link } from "wouter"; +import { Search, SlidersHorizontal } from "lucide-react"; +import { useCart } from "../hooks/use-cart"; +import { toast } from "../hooks/use-toast"; + +interface Product { + id: number; + handle: string; + title: string; + description?: string; + price: string; + compareAtPrice?: string; + images?: string[]; + status: string; + tags?: string[]; + hsaFsa?: boolean; +} + +function ProductCard({ product }: { product: Product }) { + const { addItem } = useCart(); + const image = product.images?.[0]; + + const handleAddToCart = async (e: React.MouseEvent) => { + e.preventDefault(); + try { + await addItem(product.id); + toast({ title: "Added to cart", description: product.title }); + } catch { + toast({ title: "Error", description: "Could not add to cart", variant: "destructive" }); + } + }; + + return ( + +
+
+ {image ? ( + {product.title} + ) : ( +
📦
+ )} + {product.hsaFsa && ( + HSA/FSA + )} +
+
+

{product.title}

+
+ ${parseFloat(product.price).toFixed(2)} + {product.compareAtPrice && parseFloat(product.compareAtPrice) > parseFloat(product.price) && ( + ${parseFloat(product.compareAtPrice).toFixed(2)} + )} +
+ +
+
+ + ); +} + +export default function ShopPage() { + const [q, setQ] = useState(""); + const [search, setSearch] = useState(""); + const [page, setPage] = useState(1); + + const { data, isLoading } = useQuery<{ products: Product[]; total: number; pageSize: number }>({ + queryKey: ["/api/products", { q: search, page }], + queryFn: async () => { + const params = new URLSearchParams({ page: String(page), limit: "24" }); + if (search) params.set("q", search); + const res = await fetch(`/api/products?${params}`); + return res.json(); + }, + }); + + const handleSearch = (e: React.FormEvent) => { + e.preventDefault(); + setSearch(q); + setPage(1); + }; + + return ( +
+
+

Shop

+
+
+ + setQ(e.target.value)} + placeholder="Search products..." + className="w-full pl-9 pr-4 py-2 border border-border rounded-md text-sm bg-background focus:outline-none focus:ring-2 focus:ring-primary/30" + data-testid="input-search" + /> +
+ +
+
+ + {isLoading ? ( +
+ {Array.from({ length: 8 }).map((_, i) => ( +
+
+
+
+
+
+
+ ))} +
+ ) : data?.products.length === 0 ? ( +
+

No products found

+ {search && } +
+ ) : ( + <> +
+ {data?.products.map((p) => )} +
+ {data && data.total > data.pageSize && ( +
+ + Page {page} + +
+ )} + + )} +
+ ); +}