Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/layout/public/NavbarLayout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ const NavbarLayout = () => {
</button>

{searchOpen && (
<div className="absolute top-[calc(100%+6px)] left-0 right-0 z-[500] rounded-sm border border-white/10 bg-navy2 p-2 shadow-2xl">
<div className="absolute top-[calc(100%+6px)] left-0 right-0 rounded-sm border border-white/10 bg-navy2 p-2 shadow-2xl" style={{ zIndex: 500 }}>
{searchText.trim() ? (
<>
<div className="px-2 py-1 text-[0.65rem] font-bold tracking-widest text-gray uppercase">Suggestions</div>
Expand Down Expand Up @@ -374,7 +374,7 @@ const NavbarLayout = () => {
)}

<Link
to="/dashboard/wishlist"
to="/wishlist"
className="icon-btn text-gray2 hover:text-teal relative rounded-sm px-1.5 py-2 no-underline transition hover:bg-[rgba(0,201,167,0.15)] min-[640px]:px-[0.7rem]"
title="Wishlist"
>
Expand Down
132 changes: 132 additions & 0 deletions src/pages/public/public_Wishlist/WishlistView.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { Heart, ShoppingBag, Trash2 } from 'lucide-react';
import { addToCart, getWishlistItems, removeFromWishlist } from '../../../services/shopStorageService';

const WishlistView = () => {
const [wishlistItems, setWishlistItems] = useState([]);

useEffect(() => {
setWishlistItems(getWishlistItems());

const refresh = () => setWishlistItems(getWishlistItems());
window.addEventListener('storage', refresh);
window.addEventListener('esuuq:shop-updated', refresh);

return () => {
window.removeEventListener('storage', refresh);
window.removeEventListener('esuuq:shop-updated', refresh);
};
}, []);

const removeItem = (id) => {
setWishlistItems(removeFromWishlist(id));
};

const addItemToCart = (item) => {
addToCart(item, 1);
};

const clearAll = () => {
wishlistItems.slice().forEach((item) => removeFromWishlist(item.id));
setWishlistItems([]);
};

return (
<section className="px-3 py-8 min-[640px]:px-4 min-[900px]:px-8">
<div className="container mx-auto">
<div className="mb-6 flex flex-wrap items-end justify-between gap-4">
<div>
<div className="mb-2 inline-flex items-center gap-2 rounded-full border border-teal/20 bg-teal/10 px-3 py-1 text-[0.68rem] font-bold tracking-[0.25em] text-teal uppercase">
<Heart size={12} /> Public Wishlist
</div>
<h1 className="font-syne text-2xl font-bold text-white min-[640px]:text-3xl">
Saved <span className="text-teal">Items</span>
</h1>
<p className="text-gray2 mt-2 text-sm">
{wishlistItems.length} item(s) saved on this device. Browse, remove, or move them to cart anytime.
</p>
</div>

{wishlistItems.length > 0 ? (
<button
type="button"
onClick={clearAll}
className="text-gray2 hover:border-red hover:text-red rounded border border-white/[0.07] px-4 py-2 text-[0.78rem] font-bold transition-colors"
>
Clear All
</button>
) : null}
</div>

{!wishlistItems.length ? (
<div className="bg-card rounded-md border border-white/[0.07] p-8 text-center">
<div className="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-full bg-teal/10 text-teal">
<Heart size={24} />
</div>
<div className="text-white text-lg font-semibold">Your public wishlist is empty</div>
<div className="text-gray2 mt-2 text-sm">Tap the heart icon on any product to save it here.</div>
<Link
to="/"
className="bg-teal text-navy hover:bg-teal2 mt-5 inline-flex items-center gap-2 rounded px-4 py-2 text-sm font-medium no-underline"
>
<ShoppingBag size={14} /> Continue Shopping
</Link>
</div>
) : (
<div className="grid grid-cols-1 gap-4 min-[580px]:grid-cols-2 min-[900px]:grid-cols-3 min-[1200px]:grid-cols-4">
{wishlistItems.map((item) => (
<div
key={item.id}
className="group bg-card overflow-hidden rounded-md border border-white/[0.07] transition-all hover:-translate-y-0.5 hover:border-teal/30"
>
<Link to={item.slug || item.id ? `/product/${item.slug || item.id}` : '/'} className="block no-underline">
<div className="relative flex h-44 items-center justify-center overflow-hidden border-b border-white/[0.07] bg-[#0F172A]">
{item.image ? (
<img
src={item.image}
alt={item.name}
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-110"
/>
) : (
<span className="text-5xl">{item.icon || '🛍'}</span>
)}
</div>
</Link>

<div className="p-3">
<div className="mb-1 text-[0.875rem] font-medium text-white xl:text-[1rem]">
{item.name}
</div>
<div className="font-syne text-[0.875rem] font-bold text-white">
${Number(item.price || 0).toFixed(2)}
</div>

<div className="mt-3 flex gap-2">
<button
type="button"
onClick={() => addItemToCart(item)}
className="bg-teal text-navy hover:bg-teal2 flex-1 rounded px-3 py-1.5 text-[0.74rem] font-medium"
>
Add to Cart
</button>
<button
type="button"
onClick={() => removeItem(item.id)}
className="text-gray2 hover:border-red hover:text-red rounded border border-white/[0.07] px-3 py-1.5 text-[0.74rem] font-bold transition-colors"
aria-label={`Remove ${item.name} from wishlist`}
>
<Trash2 size={14} />
</button>
</div>
</div>
</div>
))}
</div>
)}
</div>
</section>
);
};

export default WishlistView;
43 changes: 43 additions & 0 deletions src/router/components/RouteErrorBoundary.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { isRouteErrorResponse, useRouteError } from 'react-router-dom';

const RouteErrorBoundary = () => {
const error = useRouteError();

const title = isRouteErrorResponse(error)
? `${error.status} ${error.statusText || 'Route Error'}`
: 'Application Error';

const message = error instanceof Error
? error.message
: isRouteErrorResponse(error)
? error.data || 'Unable to load this page.'
: 'Unable to load this page. Please try again.';

return (
<div className="flex min-h-screen items-center justify-center bg-[#0b1020] px-4 text-white">
<div className="bg-card w-full max-w-xl rounded-2xl border border-white/10 p-6 shadow-2xl shadow-black/30">
<div className="text-xs font-bold uppercase tracking-[0.3em] text-teal">ESUUQ</div>
<h1 className="font-['Syne'] mt-3 text-2xl font-bold">{title}</h1>
<p className="text-gray2 mt-3 text-sm leading-6">{message}</p>
<div className="mt-6 flex flex-wrap gap-3">
<button
type="button"
onClick={() => window.location.reload()}
className="bg-teal text-navy hover:bg-teal2 rounded px-4 py-2 text-sm font-semibold transition-colors"
>
Reload page
</button>
<button
type="button"
onClick={() => window.history.back()}
className="rounded border border-white/10 px-4 py-2 text-sm font-semibold text-white transition-colors hover:border-teal hover:text-teal"
>
Go back
</button>
</div>
</div>
</div>
);
};

export default RouteErrorBoundary;
13 changes: 8 additions & 5 deletions src/router/router.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createBrowserRouter, createRoutesFromElements, Route, useNavigate, useP
import RootLayout from '../layout/public/RootLayout';
import LoadingFallback from './components/LoadingFallback';
import ProtectedRoute from './components/ProtectedRoute';
import RouteErrorBoundary from './components/RouteErrorBoundary';

const Home = lazy(() => import('../pages/public/public_Home/Home'));
const ContactView = lazy(() => import('../pages/public/public_contact/ContactView'));
Expand All @@ -23,6 +24,7 @@ const SearchView = lazy(() => import('../pages/public/public_Search/SearchView')
const ProductDetailsView = lazy(
() => import('../pages/public/public_ProductDetails/ProductDetailsView')
);
const WishlistView = lazy(() => import('../pages/public/public_Wishlist/WishlistView'));

// Account Pages
const LoginView = lazy(() => import('../pages/auth/LoginView'));
Expand Down Expand Up @@ -161,7 +163,7 @@ const AdminPlaceholder = ({ title, icon }) => (
const router = createBrowserRouter(
createRoutesFromElements(
<>
<Route path="/" element={<RootLayout />}>
<Route path="/" element={<RootLayout />} errorElement={<RouteErrorBoundary />}>
<Route index element={<Home />} />
<Route path="contact" element={wrap(ContactView)} />
<Route path="electronics" element={wrap(ElectronicsView)} />
Expand All @@ -176,6 +178,7 @@ const router = createBrowserRouter(
<Route path="pet-supplies" element={wrap(PetSuppliesView)} />
<Route path="health" element={wrap(HealthView)} />
<Route path="search" element={wrap(SearchView)} />
<Route path="wishlist" element={wrap(WishlistView)} />
<Route path="cart" element={<ProtectedRoute>{wrap(CartView)}</ProtectedRoute>} />
<Route
path="product/:id"
Expand All @@ -199,7 +202,7 @@ const router = createBrowserRouter(
<ProtectedRoute>
{wrap(UserView)}
</ProtectedRoute>
}>
} errorElement={<RouteErrorBoundary />}>
<Route index element={wrapElement(<UserDashboardRoute />)} />
<Route path="orders" element={wrap(UserOrders)} />
<Route path="track" element={wrap(UserTrackOrder)} />
Expand All @@ -217,7 +220,7 @@ const router = createBrowserRouter(
<ProtectedRoute allowedRoles={['admin']}>
{wrap(AdminView)}
</ProtectedRoute>
}>
} errorElement={<RouteErrorBoundary />}>
<Route index element={wrapElement(<AdminDashboardRoute />)} />
<Route path="orders" element={wrap(AdminOrders)} />
<Route path="products" element={wrap(AdminProducts)} />
Expand All @@ -237,7 +240,7 @@ const router = createBrowserRouter(
<ProtectedRoute allowedRoles={['merchant']}>
{wrap(MerchantView)}
</ProtectedRoute>
}>
} errorElement={<RouteErrorBoundary />}>
<Route index element={wrapElement(<MerchantDashboardRoute />)} />
<Route path="orders" element={wrap(MerchantOrders)} />
<Route path="products" element={wrapElement(<MerchantProductsRoute />)} />
Expand All @@ -256,7 +259,7 @@ const router = createBrowserRouter(
<ProtectedRoute allowedRoles={['sub_admin', 'admin']}>
{wrap(SubAdminView)}
</ProtectedRoute>
}>
} errorElement={<RouteErrorBoundary />}>
<Route index element={wrap(SubAdminDashboard)} />
<Route path="support-tickets" element={wrap(SubAdminSupportTickets)} />
<Route path="review-moderation" element={wrap(SubAdminReviewModeration)} />
Expand Down
Loading