From 34c55f5c22e537b5cf12114b525d43350052f520 Mon Sep 17 00:00:00 2001 From: kishore28kumar Date: Sat, 15 Nov 2025 19:40:36 +0530 Subject: [PATCH 1/2] refactor: enhance storefront UI and functionality with improved styles, new cart drawer, and filter drawer components --- apps/storefront/src/app/account/page.tsx | 77 ++- apps/storefront/src/app/cart/page.tsx | 104 ++-- apps/storefront/src/app/globals.css | 8 +- apps/storefront/src/app/page.tsx | 103 ++-- .../src/app/products/[slug]/page.tsx | 304 ++++++---- apps/storefront/src/app/products/page.tsx | 355 ++++++----- .../src/components/cart/CartDrawer.tsx | 218 +++++++ .../src/components/layout/Header.tsx | 566 +++++++++++++----- .../src/components/products/FilterDrawer.tsx | 184 ++++++ .../src/components/products/ProductCard.tsx | 240 ++++++-- apps/storefront/src/hooks/useDebounce.ts | 18 + .../src/hooks/useSearchSuggestions.ts | 41 ++ apps/storefront/src/stores/cartStore.ts | 155 +++++ 13 files changed, 1774 insertions(+), 599 deletions(-) create mode 100644 apps/storefront/src/components/cart/CartDrawer.tsx create mode 100644 apps/storefront/src/components/products/FilterDrawer.tsx create mode 100644 apps/storefront/src/hooks/useDebounce.ts create mode 100644 apps/storefront/src/hooks/useSearchSuggestions.ts create mode 100644 apps/storefront/src/stores/cartStore.ts diff --git a/apps/storefront/src/app/account/page.tsx b/apps/storefront/src/app/account/page.tsx index dddefde..19b7b83 100644 --- a/apps/storefront/src/app/account/page.tsx +++ b/apps/storefront/src/app/account/page.tsx @@ -2,6 +2,7 @@ import { useEffect } from 'react'; import { useRouter } from 'next/navigation'; +import Link from 'next/link'; import { useAuth } from '@/contexts/AuthContext'; import { User, Mail, Phone, ShoppingBag, Heart, Settings, LogOut, Package } from 'lucide-react'; @@ -33,55 +34,58 @@ export default function AccountPage() { }; return ( -
+
{/* Page Header */} -
-

My Account

+
+

My Account

Manage your account and view your orders

-
+
{/* Sidebar */}
-
+
{/* Profile Header */} -
-
-
- +
+
+
+
-

+

{customer.firstName} {customer.lastName}

-

{customer.email}

+

{customer.email}

{/* Navigation */}
@@ -144,37 +148,37 @@ export default function AccountPage() { {/* Quick Stats */}
-
+

Total Orders

0

-
+
-
+

Pending

0

-
+
-
+

Wishlist

0

-
+
@@ -182,15 +186,23 @@ export default function AccountPage() {
{/* Recent Orders */} -
-

Recent Orders

+
+
+

Recent Orders

+ + View All Orders → + +

No orders yet

Start shopping to see your orders here

@@ -202,4 +214,3 @@ export default function AccountPage() {
); } - diff --git a/apps/storefront/src/app/cart/page.tsx b/apps/storefront/src/app/cart/page.tsx index f8365fd..dd99883 100644 --- a/apps/storefront/src/app/cart/page.tsx +++ b/apps/storefront/src/app/cart/page.tsx @@ -18,6 +18,7 @@ import { import api from '@/lib/api'; import { ProtectedRoute } from '@/components/auth/ProtectedRoute'; import { toast } from 'sonner'; +import { formatCurrency } from '@/lib/utils'; interface CartItem { id: string; @@ -135,27 +136,27 @@ function CartPageContent() { if (isEmpty) { return ( -
-
+
+
-
-
+
+
-

Your Cart is Empty

-

+

Your Cart is Empty

+

Looks like you haven't added anything to your cart yet. Start shopping to fill it up!

Browse Products View Categories @@ -168,23 +169,23 @@ function CartPageContent() { } return ( -
-
+
+
{/* Header */} -
-

Shopping Cart

+
+

Shopping Cart

{cartData?.itemCount} items in your cart

-
+
{/* Cart Items */}
{cartData?.items.map((item) => ( -
-
+
+
{/* Product Image */}
-
+
{item.productImage ? ( {item.productName}

- ${item.price.toFixed(2)} each + {formatCurrency(item.price)} each

{/* Quantity Controls */} -
- - - {item.quantity} - - +
+
+ + + {item.quantity} + + +
{/* Item Total */}
-

- ${item.total.toFixed(2)} +

+ {formatCurrency(item.total)}

{item.stockQuantity < 10 && (

@@ -274,40 +277,40 @@ function CartPageContent() { {/* Order Summary */}

-
+

Order Summary

Subtotal - ${cartData?.subtotal.toFixed(2)} + {formatCurrency(cartData?.subtotal || 0)}
Tax (10%) - ${tax.toFixed(2)} + {formatCurrency(tax)}
Shipping - {shipping === 0 ? 'FREE' : `$${shipping.toFixed(2)}`} + {shipping === 0 ? 'FREE' : formatCurrency(shipping)}
{cartData && cartData.subtotal < 50 && (

- Add ${(50 - cartData.subtotal).toFixed(2)} more for free shipping! + Add {formatCurrency(50 - cartData.subtotal)} more for free shipping!

)}
Total - ${total.toFixed(2)} + {formatCurrency(total)}
Proceed to Checkout @@ -315,22 +318,22 @@ function CartPageContent() { Continue Shopping {/* Trust Badges */}
-
+
Secure Checkout
-
+
Free Shipping Over $50
-
+
Multiple Payment Options
@@ -350,4 +353,3 @@ export default function CartPage() { ); } - diff --git a/apps/storefront/src/app/globals.css b/apps/storefront/src/app/globals.css index c7b0f0b..9d917e5 100644 --- a/apps/storefront/src/app/globals.css +++ b/apps/storefront/src/app/globals.css @@ -4,7 +4,7 @@ @layer base { body { - @apply bg-white text-gray-900; + @apply bg-gray-50 text-gray-900; } } @@ -26,3 +26,9 @@ @apply bg-gray-400; } +/* Focus styles for accessibility */ +@layer utilities { + .focus-ring { + @apply focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2; + } +} diff --git a/apps/storefront/src/app/page.tsx b/apps/storefront/src/app/page.tsx index 24c6644..09736e0 100644 --- a/apps/storefront/src/app/page.tsx +++ b/apps/storefront/src/app/page.tsx @@ -25,9 +25,9 @@ export default function HomePage() { if (!isAuthenticated) { return (
-
+
-
+

@@ -38,13 +38,13 @@ export default function HomePage() {

-
+
{/* Sign In Card */} -
+

Sign In

@@ -60,9 +60,9 @@ export default function HomePage() { {/* Create Account Card */} -
+

Create Account

@@ -77,30 +77,30 @@ export default function HomePage() {
{/* Features */} -
+
-
+

Free Shipping

On orders over $50

-
+

Secure Payment

100% secure

-
+

Quality Products

Curated selection

-
+

Easy Returns

@@ -116,24 +116,24 @@ export default function HomePage() {
{/* Hero Section */}
-
+
-

+

Welcome back, {customer?.firstName}!

-

+

Discover quality products for your everyday needs. Shop with confidence!

-
+
Shop Now Browse Categories @@ -143,46 +143,46 @@ export default function HomePage() {
{/* Features */} -
-
-
+
+
+
-
- +
+
-

Free Shipping

-

On orders over $50

+

Free Shipping

+

On orders over $50

-
- +
+
-

Secure Payment

-

100% secure transactions

+

Secure Payment

+

100% secure transactions

-
- +
+
-

Quality Products

-

Carefully curated selection

+

Quality Products

+

Carefully curated selection

-
- +
+
-

Easy Returns

-

30-day return policy

+

Easy Returns

+

30-day return policy

{/* Featured Products */} -
-
-
-

Featured Products

+
+
+
+

Featured Products

Check out our handpicked selection

@@ -192,17 +192,17 @@ export default function HomePage() {

Loading products...

) : ( -
+
{featuredProducts?.map((product: any) => ( ))}
)} -
+
View All Products @@ -211,21 +211,20 @@ export default function HomePage() {
{/* CTA Section */} -
-
-

Ready to Start Shopping?

-

+

+
+

Ready to Start Shopping?

+

Join thousands of happy customers today!

- Create Account + Browse Products
); } - diff --git a/apps/storefront/src/app/products/[slug]/page.tsx b/apps/storefront/src/app/products/[slug]/page.tsx index a867254..4df2145 100644 --- a/apps/storefront/src/app/products/[slug]/page.tsx +++ b/apps/storefront/src/app/products/[slug]/page.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { useQuery } from '@tanstack/react-query'; -import { useParams } from 'next/navigation'; +import { useParams, useRouter } from 'next/navigation'; import Image from 'next/image'; import Link from 'next/link'; import { @@ -18,19 +18,25 @@ import { Shield, RotateCcw, Tag, + ChevronRight, + Zap, } from 'lucide-react'; import api from '@/lib/api'; -import { useCart } from '@/hooks/useCart'; +import { useCartStore } from '@/stores/cartStore'; import { useWishlist } from '@/hooks/useWishlist'; import ProductCard from '@/components/products/ProductCard'; +import { formatCurrency } from '@/lib/utils'; +import { toast } from 'sonner'; function ProductDetailContent() { const params = useParams(); + const router = useRouter(); const slug = params.slug as string; const [quantity, setQuantity] = useState(1); const [selectedImage, setSelectedImage] = useState(0); + const [isBuyingNow, setIsBuyingNow] = useState(false); - const { addToCart, isAddingToCart } = useCart(); + const { addItem } = useCartStore(); const { addToWishlist, isInWishlist, removeFromWishlist } = useWishlist(); // Fetch product details @@ -56,12 +62,51 @@ function ProductDetailContent() { }); const handleAddToCart = () => { - if (product) { - addToCart({ - productId: product.id, - quantity, - }); + if (!product) return; + + if (!inStock) { + toast.error('Product is out of stock'); + return; } + + addItem({ + id: product.id, + name: product.name, + slug: product.slug, + price: product.price, + thumbnail: product.thumbnail, + images: product.images, + stock: stock, + }, quantity); + + toast.success('Added to cart'); + }; + + const handleBuyNow = async () => { + if (!product) return; + + if (!inStock) { + toast.error('Product is out of stock'); + return; + } + + setIsBuyingNow(true); + + // Add to cart + addItem({ + id: product.id, + name: product.name, + slug: product.slug, + price: product.price, + thumbnail: product.thumbnail, + images: product.images, + stock: stock, + }, quantity); + + // Redirect to cart/checkout + setTimeout(() => { + router.push('/cart'); + }, 500); }; const handleToggleWishlist = () => { @@ -71,7 +116,7 @@ function ProductDetailContent() { removeFromWishlist(product.id); } else { addToWishlist({ - id: `wishlist_${product.id}`, + id: product.id, productId: product.id, name: product.name, slug: product.slug, @@ -83,7 +128,7 @@ function ProductDetailContent() { }; const incrementQuantity = () => { - if (product && quantity < product.stock) { + if (product && quantity < stock) { setQuantity(quantity + 1); } }; @@ -95,12 +140,20 @@ function ProductDetailContent() { }; const images = product?.images || []; - const currentImage = images[selectedImage] || product?.thumbnail; + const currentImage = images[selectedImage] || product?.thumbnail || ''; const inWishlist = product ? isInWishlist(product.id) : false; - const inStock = product && product.stock > 0; - const discount = product?.comparePrice - ? Math.round(((product.comparePrice - product.price) / product.comparePrice) * 100) + const stock = product?.stock || product?.stockQuantity || 0; + const inStock = stock > 0; + const comparePrice = product?.comparePrice || product?.compareAtPrice; + const discount = comparePrice + ? Math.round(((comparePrice - product.price) / comparePrice) * 100) : 0; + const rating = product?.rating || 4.5; + const reviewCount = product?.reviewCount || 245; + + // Estimated delivery date (3-5 business days) + const estimatedDelivery = new Date(); + estimatedDelivery.setDate(estimatedDelivery.getDate() + 5); if (isLoading) { return ( @@ -122,7 +175,7 @@ function ProductDetailContent() {

The product you're looking for doesn't exist.

Back to Products @@ -133,34 +186,40 @@ function ProductDetailContent() { } return ( -
-
- {/* Breadcrumb */} -
- - Home - - / - - Products - - / +
+
+ {/* Breadcrumbs */} +
+ {/* Product Section */} -
-
- {/* Image Gallery */} +
+
+ {/* Image Gallery - Full Width */}
{/* Main Image */} -
+
{currentImage ? ( {product.name} ) : (
@@ -170,14 +229,14 @@ function ProductDetailContent() { {/* Discount Badge */} {discount > 0 && ( -
+
-{discount}%
)} {/* Stock Badge */} {!inStock && ( -
+
Out of Stock
)} @@ -190,17 +249,19 @@ function ProductDetailContent() { ))} @@ -224,53 +285,86 @@ function ProductDetailContent() { {/* Rating */} -
+
{[1, 2, 3, 4, 5].map((star) => ( ))}
- (4.8 / 5.0) + {rating.toFixed(1)} - 245 reviews + + {reviewCount} reviews +
- {/* Price */} -
-
+ {/* Price Block */} +
+
- ${product.price.toFixed(2)} + {formatCurrency(product.price)} - {product.comparePrice && ( - - ${product.comparePrice.toFixed(2)} + {comparePrice && ( + + {formatCurrency(comparePrice)} )}
- {product.comparePrice && ( -

- You save ${(product.comparePrice - product.price).toFixed(2)} ({discount}%) + {comparePrice && ( +

+ You save {formatCurrency(comparePrice - product.price)} ({discount}%)

)}
- {/* Description */} -
-

{product.description}

+ {/* Delivery & Returns Info */} +
+
+ +
+

Free Delivery

+

+ Estimated delivery: {estimatedDelivery.toLocaleDateString('en-US', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric' + })} +

+
+
+
+ +
+

Easy Returns

+

30-day return policy

+
+
+
+ +
+

Secure Payment

+

100% secure checkout

+
+
{/* Stock Status */}
-
+
{inStock ? ( <> - In Stock ({product.stock} available) + In Stock ({stock} available) ) : ( @@ -288,12 +382,13 @@ function ProductDetailContent() { -
-
+
+
@@ -302,14 +397,15 @@ function ProductDetailContent() {
- Max: {product.stock} units + Max: {stock} units
@@ -317,22 +413,32 @@ function ProductDetailContent() { {/* Action Buttons */}
+ +
- {/* Product Features */} -
-
-
- -
-
-

Free Shipping

-

On orders over $50

-
-
- -
-
- -
-
-

Easy Returns

-

30-day return policy

-
-
- -
-
- -
-
-

Secure Payment

-

100% secure checkout

-
-
+ {/* Fulfilled By Info */} +
+

+ Fulfilled by: Pulss Store +

+

+ Ships from our warehouse • Free returns within 30 days +

{/* Product Details Tabs */} -
+

Product Details

+ {/* Description */} + {product.description && ( +
+

Description

+

+ {product.description} +

+
+ )} + {/* Specifications */} -
+

Specifications

SKU - {product.sku} + {product.sku || 'N/A'}
{product.manufacturer && (
@@ -416,7 +509,7 @@ function ProductDetailContent() { {product.tags?.map((tag: string, index: number) => ( {tag} @@ -427,6 +520,15 @@ function ProductDetailContent() {
+ {/* Reviews Section (Placeholder) */} +
+

Customer Reviews

+
+ +

Reviews feature coming soon

+
+
+ {/* Related Products */} {relatedProducts && relatedProducts.length > 0 && (
@@ -434,12 +536,13 @@ function ProductDetailContent() {

Related Products

- View All → + View All +
-
+
{relatedProducts.map((relatedProduct: any) => ( ))} @@ -454,4 +557,3 @@ function ProductDetailContent() { export default function ProductDetailPage() { return ; } - diff --git a/apps/storefront/src/app/products/page.tsx b/apps/storefront/src/app/products/page.tsx index 6325a30..396402d 100644 --- a/apps/storefront/src/app/products/page.tsx +++ b/apps/storefront/src/app/products/page.tsx @@ -2,181 +2,255 @@ import { useState } from 'react'; import { useQuery } from '@tanstack/react-query'; -import { Filter } from 'lucide-react'; +import { useSearchParams, useRouter } from 'next/navigation'; +import { Filter, ChevronRight } from 'lucide-react'; import api from '@/lib/api'; import ProductCard from '@/components/products/ProductCard'; +import FilterDrawer from '@/components/products/FilterDrawer'; import { ProtectedRoute } from '@/components/auth/ProtectedRoute'; function ProductsPageContent() { + const router = useRouter(); + const searchParams = useSearchParams(); const [page, setPage] = useState(1); + const [isFilterDrawerOpen, setIsFilterDrawerOpen] = useState(false); const [filters, setFilters] = useState({ - search: '', - categoryId: '', - minPrice: '', - maxPrice: '', - sortBy: 'createdAt', - sortOrder: 'desc', + search: searchParams.get('search') || '', + categoryId: searchParams.get('categoryId') || '', + minPrice: searchParams.get('minPrice') || '', + maxPrice: searchParams.get('maxPrice') || '', + sortBy: searchParams.get('sortBy') || 'createdAt', + sortOrder: searchParams.get('sortOrder') || 'desc', }); + // Fetch categories + const { data: categoriesData } = useQuery({ + queryKey: ['categories'], + queryFn: async () => { + const response = await api.get('/categories'); + return response.data.data || []; + }, + }); + + // Fetch products const { data, isLoading } = useQuery({ queryKey: ['products', { ...filters, page }], queryFn: async () => { const response = await api.get('/products', { - params: { ...filters, page, limit: 12 }, + params: { ...filters, page, limit: 24 }, }); return response.data.data; }, }); + const handleFilterChange = (newFilters: typeof filters) => { + setFilters(newFilters); + setPage(1); + // Update URL params + const params = new URLSearchParams(); + Object.entries(newFilters).forEach(([key, value]) => { + if (value) params.set(key, value.toString()); + }); + router.push(`/products?${params.toString()}`); + }; + return ( -
- {/* Header */} -
-

All Products

-

Browse our complete collection

-
+
+
+ {/* Breadcrumbs */} + -
- {/* Filters Sidebar */} -
-
-
-

Filters

- -
+ {/* Header */} +
+
+

All Products

+

+ {data?.meta?.total ? `Showing ${data.data.length} of ${data.meta.total} products` : 'Browse our complete collection'} +

+
- {/* Search */} -
- - setFilters({ ...filters, search: e.target.value })} - className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" - /> -
+ {/* Mobile Filter Button */} + +
- {/* Price Range */} -
- -
- setFilters({ ...filters, minPrice: e.target.value })} - className="w-full px-3 py-2 border border-gray-300 rounded-lg" - /> - - +
+ {/* Filters Sidebar (Desktop) */} +
-
+ {/* Price Range */} +
+ +
+ handleFilterChange({ ...filters, minPrice: e.target.value })} + className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent shadow-sm" + /> + - + handleFilterChange({ ...filters, maxPrice: e.target.value })} + className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent shadow-sm" + /> +
+
- {/* Products Grid */} -
- {isLoading ? ( -
-
-

Loading products...

-
- ) : ( - <> - {/* Results Count */} + {/* Sort */}
-

- Showing {data?.data?.length || 0} of {data?.meta?.total || 0} products -

+ +
- {/* Products Grid */} - {data?.data?.length > 0 ? ( - <> -
- {data.data.map((product: any) => ( - - ))} -
+ {/* Clear Filters */} + +
+ + + {/* Products Grid */} +
+ {isLoading ? ( +
+
+

Loading products...

+
+ ) : ( + <> + {data?.data?.length > 0 ? ( + <> + {/* Products Grid - 2 columns mobile, 3 tablet, 4 desktop */} +
+ {data.data.map((product: any) => ( + + ))} +
- {/* Pagination */} -
- - - Page {page} of {data?.meta?.totalPages || 1} - - + {/* Pagination */} + {data.meta && data.meta.totalPages > 1 && ( +
+ + + Page {page} of {data.meta.totalPages} + + +
+ )} + + ) : ( +
+

No products found

+

Try adjusting your filters

- - ) : ( -
-

No products found

-
- )} - - )} + )} + + )} +
+ + {/* Mobile Filter Drawer */} + setIsFilterDrawerOpen(false)} + filters={filters} + onFilterChange={handleFilterChange} + categories={categoriesData} + />
); } @@ -188,4 +262,3 @@ export default function ProductsPage() { ); } - diff --git a/apps/storefront/src/components/cart/CartDrawer.tsx b/apps/storefront/src/components/cart/CartDrawer.tsx new file mode 100644 index 0000000..57753bc --- /dev/null +++ b/apps/storefront/src/components/cart/CartDrawer.tsx @@ -0,0 +1,218 @@ +'use client'; + +import { useEffect } from 'react'; +import { X, Plus, Minus, Trash2, ShoppingBag, ArrowRight } from 'lucide-react'; +import Image from 'next/image'; +import Link from 'next/link'; +import { useCartStore } from '@/stores/cartStore'; +import { formatCurrency } from '@/lib/utils'; +import { useCart } from '@/hooks/useCart'; +import { useAuth } from '@/contexts/AuthContext'; + +export default function CartDrawer() { + const { isOpen, closeCart, items, subtotal, itemCount, updateQuantity, removeItem, clearCart } = useCartStore(); + const { isAuthenticated } = useAuth(); + const { addToCart: addToCartAPI, updateQuantity: updateQuantityAPI, removeFromCart: removeFromCartAPI } = useCart(); + + // Sync local cart to server when user is authenticated + useEffect(() => { + if (isAuthenticated && items.length > 0) { + // Sync each item to server + items.forEach(item => { + // This would ideally be done in a batch, but for now we'll sync on add/update + }); + } + }, [isAuthenticated, items]); + + const handleUpdateQuantity = async (productId: string, newQuantity: number) => { + if (isAuthenticated) { + // Find the cart item ID from server + // For now, update locally and sync + updateQuantity(productId, newQuantity); + } else { + updateQuantity(productId, newQuantity); + } + }; + + const handleRemoveItem = async (productId: string) => { + if (isAuthenticated) { + // Find and remove from server + removeFromCartAPI(productId); + } + removeItem(productId); + }; + + const handleCheckout = () => { + closeCart(); + // Navigate to checkout + window.location.href = '/cart'; + }; + + if (!isOpen) return null; + + return ( + <> + {/* Backdrop */} +