-
Notifications
You must be signed in to change notification settings - Fork 398
Fix/UI improvements #2653
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Fix/UI improvements #2653
Conversation
|
Someone is attempting to deploy a commit to the Vivek Prajapati's projects Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughAdds multiple new UI components (buttons, hero, search, product card, loaders, navbar) and a UI showcase page with animations. Introduces AuthContext login state and integrates login-gating into product interactions. Updates styles via a new enhanced CSS file. Minor layout/content tweaks in Help, Contributors, Footer, and README typo. Changes
Sequence Diagram(s)sequenceDiagram
participant U as User
participant UI as ProductCard/ProductGrid
participant A as AuthContext
participant R as Router
U->>UI: Click Add to Cart / Buy Now / Wishlist
UI->>A: Check isLoggedIn
alt Not logged in
UI-->>U: Notify "Please login..."
UI->>R: navigate("/auth")
else Logged in
UI-->>U: Proceed with action (add/view/wishlist)
end
sequenceDiagram
participant U as User
participant S as SmartSearchBar
participant LS as localStorage
participant P as Parent(onSearch)
U->>S: Type query / focus
S-->>S: Filter mockSuggestions (name/category)
S-->>U: Show dropdown (suggestions/recent/trending)
U->>S: Press Enter / click item
S->>LS: Update recentSearches (dedupe, cap 5)
S->>P: onSearch(query)
S-->>U: Close dropdown, update input
sequenceDiagram
participant App as App
participant Ctx as AuthContext
participant U as User
U->>Ctx: loginAsUser(userData) / loginAsAdmin()
Ctx-->>App: isLoggedIn=true, user set, isAdmin state
U->>Ctx: logout()
Ctx-->>App: isLoggedIn=false, user cleared, isAdmin=false
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/context/AuthContext.jsx (1)
1-42: Synchronize AuthContext with existing localStorage auth usage
– UpdateloginAsAdmin,loginAsUser, andlogoutinAuthContext.jsxto read/write the same keys used elsewhere (isLoggedIn,isAdmin, and a unifieduserkey), rather than only in state.
– Replace or remove all directlocalStorage.getItem("isLoggedIn"),getItem("isAdmin"),getItem("username")/"email"calls (e.g. insrc/components/Navbar/UserNavbar.jsx,src/User/pages/Dashboard/*,src/PrivateRoute.jsx,src/Admin/components/AdminNavbar/AdminNavbar.jsx) to useuseAuthor the standardized keys.src/User/components/ProductCard/ProductCard.jsx (1)
31-37: Harden star rendering for fractional/out-of-range ratingsUsing Array(rating) will break on non-integers and allow >5 stars.
Apply:
- <div className="flex gap-1 text-yellow-500"> - {[...Array(rating)].map((_, i) => ( + <div className="flex gap-1 text-yellow-500"> + {[...Array(Math.max(0, Math.min(5, Math.round(Number(rating) || 0))))].map((_, i) => ( <FaStar key={i} /> ))} </div>src/User/components/Popular_Categories/ProductGrid.jsx (2)
129-136: Rating text shows count, not the rounded ratingPR objective says “rating 3.6 shows 4 stars and displays ‘(4)’.” This prints rating.count (likely number of reviews).
- <span className="text-gray-500 ml-1">({product.rating.count})</span> + <span className="text-gray-500 ml-1">({Math.round(product.rating.rate)})</span>
150-156: “Buy Now” is disabled if item is already in cartThis blocks checkout for items already in cart. Remove the disabled state; gating is already handled in handleBuyNow.
- <button + <button onClick={() => { onAddToCart(product); // First, add the product to the cart handleBuyNow(); // Then, navigate to the payment page }} className="mt-1 bg-orange-600 text-white px-4 py-2 rounded text-sm w-full hover:bg-orange-700 transition-colors disabled:opacity-45 disabled:pointer-events-none" - disabled={cartItems.find((item) => item.id === product.id)}> - {cartItems.find((item) => item.id === product.id) - ? "⚡Buy Now" - : "⚡Buy Now"} + > + ⚡Buy Now </button>
🧹 Nitpick comments (50)
README.md (1)
94-94: Typo fix looks good; unify link style and phrasing (nit).Use consistent links to repo files and slightly clearer wording.
-We accept contributions to our project pertaining to the [Code of Conduct](https://github.com/codervivek5/VigyBag?tab=coc-ov-file) and [Contributing Guidelines](https://github.com/codervivek5/VigyBag/blob/main/CONTRIBUTING.md) stated below. +We accept contributions to our project in accordance with the [Code of Conduct](https://github.com/codervivek5/VigyBag/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/codervivek5/VigyBag/blob/main/CONTRIBUTING.md) stated below.src/User/components/Footer/Footer.jsx (1)
156-156: Good addition; consider rel="nofollow" (nit).External social links often use nofollow.
-<a href="https://github.com/codervivek5/VigyBag" target="_blank" rel="noopener noreferrer" aria-label="GitHub Repository" className="social-link"><FaGithub className="social-icon" /></a> +<a href="https://github.com/codervivek5/VigyBag" target="_blank" rel="noopener noreferrer nofollow" aria-label="GitHub Repository" className="social-link"><FaGithub className="social-icon" /></a>src/User/pages/UserAuth/UserAuth.jsx (1)
461-486: Required checkbox works; add rel on new-tab Links and consider tracking acceptance server-side.HTML5 required blocks submission—nice. Add rel to Links opened in new tabs. Also, for compliance/audit, consider persisting terms acceptance (boolean/timestamp/version) via API.
- <Link + <Link to="/terms-and-condition" - className="text-[#4caf50] hover:underline" + className="text-[#4caf50] hover:underline" target="_blank" + rel="noopener noreferrer" > ... - <Link + <Link to="/privacy-policy" - className="text-[#4caf50] hover:underline" + className="text-[#4caf50] hover:underline" target="_blank" + rel="noopener noreferrer" >If you want, I can propose a minimal state + payload change to record acceptance.
src/User/pages/Help/Help.jsx (1)
43-58: Layout fix looks good; move inline styles to CSS and drop stray {" "} (nit).Keeps styles consistent with your enhanced UI.
- />{" "} + />Optional: extract the color/border styles on the heading and divider into a CSS class or Tailwind utility for consistency.
src/User/pages/Contributors/Contributors.jsx (3)
21-29: Avoid magic number duplication for initial count (nit).Define a constant to keep 6 in one place.
- const [visibleContributors, setVisibleContributors] = useState(6); + const INITIAL_VISIBLE = 6; + const [visibleContributors, setVisibleContributors] = useState(INITIAL_VISIBLE); ... - setVisibleContributors(6); + setVisibleContributors(INITIAL_VISIBLE);
367-373: Prefer stable keys over index for mapped list.Using a stable unique key prevents unnecessary re-renders on reorder.
- {contributorsData.slice(0, visibleContributors).map((contributor, index) => ( + {contributorsData.slice(0, visibleContributors).map((contributor) => ( <ContributorCard - key={index} + key={contributor.name} {...contributor} roleColor="text-green-600" /> ))}
375-386: Minor color token cleanup (nit).8-digit hex with ff is equivalent to opaque; use Tailwind token for consistency.
- className="px-6 py-3 bg-[#15a349ff] text-white rounded-lg hover:bg-green-600 focus:outline-none transition duration-300 font-semibold"> + className="px-6 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 focus:outline-none transition duration-300 font-semibold">src/styles/enhanced-ui.css (3)
5-16: Backdrops need graceful fallback and GPU hintNot all browsers/devices support backdrop-filter; add a @supports gate with a safe fallback and consider will-change to reduce repaints on hover-heavy UIs.
.glass-effect { background: rgba(255, 255, 255, 0.25); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.18); + will-change: backdrop-filter; } + +@supports not ((backdrop-filter: blur(10px)) or (-webkit-backdrop-filter: blur(10px))) { + .glass-effect { + background: rgba(255, 255, 255, 0.6); + } + .glass-dark { + background: rgba(0, 0, 0, 0.6); + } +}
86-104: Add visible focus styles for keyboard users on .btn-ecoImprove accessibility and compliance by styling focus-visible.
.btn-eco { background: linear-gradient(135deg, #10b981 0%, #059669 100%); @@ cursor: pointer; } .btn-eco:hover { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(16, 185, 129, 0.3); } + +.btn-eco:focus-visible { + outline: 3px solid rgba(34, 197, 94, 0.7); + outline-offset: 2px; +}
124-133: Tokenize shadows/spacing via CSS variables for consistencyOptional: promote shadow and radius tokens to :root to keep design consistent with --eco-* palette.
src/index.css (2)
2-6: Import order may cause unintended overrides with TailwindCustom CSS before Tailwind can be overridden by Tailwind’s layers. Either (1) move the import after @tailwind utilities or (2) wrap classes in enhanced-ui.css with @layer components/utilities. Option (1) shown below.
@import url('https://fonts.googleapis.com/css2?family=Baloo+2:wght@400..800&display=swap'); -@import './styles/enhanced-ui.css'; @tailwind base; @tailwind components; @tailwind utilities; +@import './styles/enhanced-ui.css';To confirm no regressions due to cascade, inspect a few elements using .btn-eco or .card-modern after this change.
9-29: Scrollbar styling: add Firefox support and eco tokensBroaden compatibility and align to theme variables.
+/* Firefox */ +* { + scrollbar-width: thin; + scrollbar-color: var(--eco-primary, #10b981) transparent; +} /* WebKit */ ::-webkit-scrollbar { width: 8px; } @@ ::-webkit-scrollbar-thumb { - background: linear-gradient(rgb(20, 83, 45), #d6a075); + background: linear-gradient(var(--eco-text, rgb(20, 83, 45)), #d6a075);src/User/components/LoadingComponents/ProductCardSkeleton.jsx (2)
1-14: Prefer CSS shimmer and reduce motion-aware behaviorUsing framer-motion for N skeleton blocks is heavier than a pure-CSS shimmer, and it ignores prefers-reduced-motion. Either swap to the global .skeleton class or gate animations via useReducedMotion.
-import { motion } from 'framer-motion'; +import { motion, useReducedMotion } from 'framer-motion'; @@ const ProductCardSkeleton = () => { - const shimmerVariants = { + const prefersReducedMotion = useReducedMotion(); + const shimmerVariants = { animate: { - x: [-100, 100], + x: prefersReducedMotion ? 0 : [-100, 100], transition: { - repeat: Infinity, - duration: 1.5, + repeat: prefersReducedMotion ? 0 : Infinity, + duration: prefersReducedMotion ? 0 : 1.5, ease: "easeInOut" } } };
30-36: Mark all decorative shimmer layers and dots as aria-hiddenPrevents noisy announcements in screen readers.
- <motion.div + <motion.div variants={shimmerVariants} animate="animate" - className="absolute inset-0 bg-gradient-to-r from-transparent via-white/60 to-transparent" + aria-hidden="true" + className="absolute inset-0 bg-gradient-to-r from-transparent via-white/60 to-transparent" /> @@ - <motion.div + <motion.div variants={shimmerVariants} animate="animate" - className="absolute inset-0 bg-gradient-to-r from-transparent via-white/60 to-transparent" + aria-hidden="true" + className="absolute inset-0 bg-gradient-to-r from-transparent via-white/60 to-transparent" /> @@ - <div key={i} className="w-3 h-3 bg-gray-200 rounded" /> + <div key={i} className="w-3 h-3 bg-gray-200 rounded" aria-hidden="true" /> @@ - <motion.div + <motion.div variants={shimmerVariants} animate="animate" - className="absolute inset-0 bg-gradient-to-r from-transparent via-white/60 to-transparent" + aria-hidden="true" + className="absolute inset-0 bg-gradient-to-r from-transparent via-white/60 to-transparent" /> @@ - <motion.div + <motion.div variants={shimmerVariants} animate="animate" - className="absolute inset-0 bg-gradient-to-r from-transparent via-white/60 to-transparent" + aria-hidden="true" + className="absolute inset-0 bg-gradient-to-r from-transparent via-white/60 to-transparent" />Also applies to: 40-53, 65-78
src/context/AuthContext.jsx (3)
1-1: Import useEffect for persistence sync-import React, { createContext, useState, useContext } from 'react'; +import React, { createContext, useState, useContext, useEffect } from 'react';
27-35: Optional: add a single effect to keep storage in syncIf you prefer centralization over handler writes, add this effect and drop per-handler storage writes.
+ useEffect(() => { + localStorage.setItem('isAdmin', String(isAdmin)); + localStorage.setItem('isLoggedIn', String(isLoggedIn)); + if (user) localStorage.setItem('user', JSON.stringify(user)); + else localStorage.removeItem('user'); + }, [isAdmin, isLoggedIn, user]);
27-35: Expose a stable context value to reduce re-rendersOptional: memoize the context value to avoid unnecessary subtree renders on stable refs.
- return ( - <AuthContext.Provider value={{ - isAdmin, - isLoggedIn, - user, - loginAsAdmin, - loginAsUser, - logout - }}> + const value = React.useMemo(() => ({ + isAdmin, isLoggedIn, user, loginAsAdmin, loginAsUser, logout + }), [isAdmin, isLoggedIn, user]); + return ( + <AuthContext.Provider value={value}> {children} </AuthContext.Provider> );src/User/components/LoadingComponents/PageLoader.jsx (1)
65-75: Brand copy nit: consider using app name via configIf the project name changes, “Loading VigyBag” hard-codes a string. Pull from a config/env for reuse.
src/User/components/EnhancedHero/EnhancedHero.jsx (3)
6-6: “Learn More” is inert. Expose a handler prop to wire navigation.-const EnhancedHero = ({ onShopNowClick }) => { +const EnhancedHero = ({ onShopNowClick, onLearnMoreClick }) => {- <motion.button + <motion.button + onClick={onLearnMoreClick} className="border-2 border-green-600 text-green-700 px-8 py-4 rounded-full text-lg font-semibold hover:bg-green-50" whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} transition={{ type: "spring", stiffness: 300 }} > Learn More </motion.button>Also applies to: 151-158
22-51: Minor: Hoist static variants/constants outside the component to avoid re-creation on every render.
161-198: Hard-coded stats could mislead; consider props or CMS-driven values.Would you like a quick refactor to accept stats via props with sensible defaults?
src/User/components/EnhancedButtons/AnimatedButton.jsx (4)
43-51: Remove unused rippleVariants.- const rippleVariants = { - initial: { scale: 0, opacity: 0.5 }, - animate: { - scale: 4, - opacity: 0, - transition: { duration: 0.6, ease: 'easeOut' } - } - };
109-116: Avoid Next.js-specific <style jsx>; use a plain style tag or move keyframes to CSS.- <style jsx>{` + <style>{` @keyframes ripple { to { transform: scale(4); opacity: 0; } } `}</style>If the project uses styled-jsx, ignore; otherwise this prevents an odd jsx attribute on <style>.
84-87: Guard against unknown variant keys.- className={`${baseClasses} ${variants[variant]} ${sizes[size]} ${className} ${ + className={`${baseClasses} ${(variants[variant] || variants.primary)} ${sizes[size]} ${className} ${ disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer' }`}
24-31: Optional: expose semantic variants via data-attributes for theming; current strings are fine but hard to override.src/User/components/ModernNavbar/ModernNavbar.jsx (4)
81-105: Active tab check is strict equality; nested routes won’t highlight.- {navItems.map((item) => ( + {navItems.map((item) => { + const isActive = location.pathname === item.path || location.pathname.startsWith(item.path + '/'); + return ( <Link key={item.name} to={item.path} className="relative group" > <motion.span className={`text-gray-700 hover:text-green-600 font-medium transition-colors ${ - location.pathname === item.path ? 'text-green-600' : '' + isActive ? 'text-green-600' : '' }`} whileHover={{ y: -2 }} transition={{ type: 'spring', stiffness: 300 }} > {item.name} </motion.span> - {location.pathname === item.path && ( + {isActive && ( <motion.div className="absolute -bottom-1 left-0 right-0 h-0.5 bg-green-600" layoutId="activeTab" transition={{ type: 'spring', stiffness: 300, damping: 30 }} /> )} </Link> - ))} + )})}
128-173: Wishlist/Cart/Login actions don’t navigate; wire to routes.- <motion.button + <motion.button className="relative p-2 text-gray-600 hover:text-green-600 transition-colors" whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }} + onClick={() => (window.location.href = '/wishlist')} > ... - <motion.button + <motion.button className="relative p-2 text-gray-600 hover:text-green-600 transition-colors" whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }} + onClick={() => (window.location.href = '/cart')} > ... - <motion.button + <motion.button className="hidden md:flex items-center space-x-2 bg-green-600 text-white px-4 py-2 rounded-full hover:bg-green-700 transition-colors" whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} + onClick={() => (window.location.href = '/auth')} >Prefer using useNavigate, or convert to motion(Link) for better SPA navigation.
116-125: Add basic a11y: label the search and annotate the menu toggle.- <input + <input type="text" placeholder="Search eco-friendly products..." value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} onFocus={() => setIsSearchFocused(true)} onBlur={() => setIsSearchFocused(false)} + aria-label="Search products" className="w-full pl-10 pr-4 py-2 border border-gray-200 rounded-full focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent bg-white/80 backdrop-blur-sm" />- <motion.button + <motion.button className="lg:hidden p-2 text-gray-600" onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)} whileTap={{ scale: 0.9 }} + aria-label="Toggle menu" + aria-expanded={isMobileMenuOpen} + aria-controls="mobile-menu" >Also add id="mobile-menu" to the drawer container if desired.
Also applies to: 164-171
14-20: Minor: throttle scroll handler or use passive listener to reduce main-thread work on scroll-heavy pages.src/User/components/ProductCard/ProductCard.jsx (2)
10-15: Use consistent notifications (toast) instead of blocking alertsThe rest of the app uses react-hot-toast; align for consistency and better UX.
Proposed change:
-import { useNavigate } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; +import toast from "react-hot-toast"; ... - if (!isLoggedIn) { - alert("Please login to add items to your cart!"); + if (!isLoggedIn) { + toast.error("Please login to add items to your cart!"); navigate("/auth"); return; } - // Add to cart logic here - alert(`${title} added to cart!`); + // Add to cart logic here + toast.success(`${title} added to cart!`);
41-58: Accessibility: add an aria-label to the buttonImproves screen-reader clarity.
- <button + <button + aria-label="Add to bag" onClick={handleAddToCart}src/User/components/Popular_Categories/ProductGrid.jsx (3)
139-148: Minor: avoid repeated lookups for cart membershipCompute once for readability and tiny perf win.
- <button + {(() => { const inCart = !!cartItems.find((item) => item.id === product.id); return ( + <button className="mt-1 bg-[#166635ff] text-white px-4 py-2 rounded text-sm w-full hover:bg-[#3d9970ff] transition-colors disabled:opacity-45 disabled:pointer-events-none" onClick={() => { onAddToCart(product); }} - disabled={cartItems.find((item) => item.id === product.id)}> - {cartItems.find((item) => item.id === product.id) + disabled={inCart}> + {inCart ? "Added" : "Add to Cart"} </button> + )})()}
77-105: DRY up auth gating for wishlist actionsThe login checks are duplicated in both branches. Extract a guard to reduce repetition.
+ const requireLogin = (fn) => { + if (!isLoggedIn) { + toast.error("Please login to manage wishlist!"); + navigate("/auth"); + return; + } + fn(); + }; ... - onClick={() => { - if (!isLoggedIn) { - toast.error("Please login to manage wishlist!"); - navigate("/auth"); - return; - } - onAddToWishlist(product); - toast.success("Item removed from wishlist!"); - }}> + onClick={() => + requireLogin(() => { + onAddToWishlist(product); + toast.success("Item removed from wishlist!"); + }) + }> ... - onClick={() => { - if (!isLoggedIn) { - toast.error("Please login to add items to wishlist!"); - navigate("/auth"); - return; - } - onAddToWishlist(product); - toast.success("Item added to wishlist!"); - }}> + onClick={() => + requireLogin(() => { + onAddToWishlist(product); + toast.success("Item added to wishlist!"); + }) + }>
8-9: Unused import PaymentPageRemove to keep things tidy.
-import PaymentPage from "../../pages/Payment/Payment";src/User/components/EnhancedProductCard/EnhancedProductCard.jsx (4)
147-153: No add-to-cart handler wiredButton does nothing. Either accept an onAddToCart(product) prop or integrate with Redux to dispatch manageCartItem.
Example (prop-driven, non-breaking for current Home usage):
-const EnhancedProductCard = ({ product }) => { +const EnhancedProductCard = ({ product, onAddToCart }) => { ... - <motion.button + <motion.button className="bg-green-600 text-white px-4 py-2 rounded-full text-sm font-semibold hover:bg-green-700" whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} + onClick={() => onAddToCart?.(product)} > Add to Cart </motion.button>I can also provide a Redux-wired variant if you prefer.
128-138: Static rating; derive from data or provide sensible fallbackHardcoded “(4.2)” and 4 filled stars may mislead.
- {[...Array(5)].map((_, i) => ( + {(() => { + const rating = Number(product?.rating ?? 4.2); + const stars = Math.max(0, Math.min(5, Math.round(rating))); + return [...Array(5)].map((_, i) => ( <FaStar key={i} size={12} - className={i < 4 ? 'text-yellow-400' : 'text-gray-300'} + className={i < stars ? 'text-yellow-400' : 'text-gray-300'} /> - ))} - <span className="text-xs text-gray-500 ml-1">(4.2)</span> + )})()} + <span className="text-xs text-gray-500 ml-1">({Math.round(Number(product?.rating ?? 4.2))})</span>
141-145: Static price; read from product with graceful defaultAvoid showing ₹299/₹399 for all items.
- <span className="text-lg font-bold text-green-600">₹299</span> - <span className="text-sm text-gray-400 line-through">₹399</span> + <span className="text-lg font-bold text-green-600">₹{product?.price ?? 299}</span> + <span className="text-sm text-gray-400 line-through">₹{product?.originalPrice ?? product?.mrp ?? 399}</span>
54-63: Add aria-pressed and aria-label to wishlist toggleImproves accessibility.
- <motion.button + <motion.button className={`absolute top-3 right-3 z-20 p-2 rounded-full ${ isWishlisted ? 'bg-red-500 text-white' : 'bg-white/80 text-gray-600' } shadow-md`} whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }} onClick={() => setIsWishlisted(!isWishlisted)} + aria-pressed={isWishlisted} + aria-label={isWishlisted ? "Remove from wishlist" : "Add to wishlist"} >src/User/pages/Home/Home.jsx (4)
6-8: Remove unused import LatestInMarketCardIt’s no longer used after switching to EnhancedProductCard.
-import LatestInMarketCard from "../../components/HomPageCard/LatestInMarketCard";
264-281: Consider passing handlers into EnhancedProductCardRight now cards render purely visual UI. If you adopt the onAddToCart/onView props as suggested, wire them here.
Example:
- <EnhancedProductCard product={product} /> + <EnhancedProductCard + product={product} + onAddToCart={() => {/* dispatch/manageCartItem here */}} + />I can wire this to Redux with a small helper.
15-23: Optional: move static data to a config modulepopularCategories/latestProducts can live in a constants file to keep Home lean.
30-31: DownArrow import appears unusedRemove if not used later.
-import DownArrow from "../../components/DownArrow/downArrow";src/User/components/EnhancedSearch/SmartSearchBar.jsx (6)
25-35: Initialize recent searches from localStorage on mount.You persist recent searches but never hydrate them, so the dropdown’s “Recent Searches” starts empty on reload.
Apply this diff above the filtering useEffect:
+ // Load recent searches from localStorage + useEffect(() => { + try { + const saved = JSON.parse(localStorage.getItem('recentSearches') || '[]'); + if (Array.isArray(saved)) setRecentSearches(saved.slice(0, 5)); + } catch { + // ignore storage read errors (private mode/quota) + } + }, []);
61-65: Use onKeyDown instead of deprecated onKeyPress.onKeyPress is deprecated; switch to onKeyDown for reliability across React versions.
- const handleKeyPress = (e) => { + const handleKeyDown = (e) => { if (e.key === 'Enter') { handleSearch(query); } }; @@ - onKeyPress={handleKeyPress} + onKeyDown={handleKeyDown}Also applies to: 111-121
111-121: Lightweight a11y: add combobox semantics.Expose dropdown state to assistive tech and label options.
<input ref={inputRef} type="text" value={query} onChange={(e) => setQuery(e.target.value)} onFocus={() => setIsActive(true)} - onKeyPress={handleKeyPress} + onKeyDown={handleKeyDown} placeholder={placeholder} - className="w-full pl-12 pr-12 py-4 text-gray-800 placeholder-gray-500 focus:outline-none bg-transparent" + className="w-full pl-12 pr-12 py-4 text-gray-800 placeholder-gray-500 focus:outline-none bg-transparent" + role="combobox" + aria-expanded={isActive} + aria-controls="smart-search-dropdown" + aria-autocomplete="list" /> @@ - <motion.div + <motion.div + id="smart-search-dropdown" variants={dropdownVariants} initial="hidden" animate="visible" exit="hidden" className="absolute top-full left-0 right-0 mt-2 bg-white rounded-2xl shadow-2xl border border-gray-100 overflow-hidden z-50" > @@ - {suggestions.map((item) => ( + {suggestions.map((item) => ( <motion.div key={item.id} - className="flex items-center justify-between p-3 rounded-lg hover:bg-gray-50 cursor-pointer group" + className="flex items-center justify-between p-3 rounded-lg hover:bg-gray-50 cursor-pointer group" + role="option" + tabIndex={0} whileHover={{ x: 4 }} onClick={() => handleSearch(item.name)} >Also applies to: 149-158, 167-173
48-56: Guard localStorage writes.localStorage.setItem can throw (private mode/quota). Wrap in try/catch.
- localStorage.setItem('recentSearches', JSON.stringify(newRecentSearches)); + try { + localStorage.setItem('recentSearches', JSON.stringify(newRecentSearches)); + } catch {}
138-146: Remove or wire the spinner; currently dead code.The spinner is always display:none. Either control it with a loading flag or remove it.
- {/* Loading Indicator */} - <div className="absolute right-4 top-1/2 transform -translate-y-1/2"> - <motion.div - className="w-5 h-5 border-2 border-green-500 border-t-transparent rounded-full" - animate={{ rotate: 360 }} - transition={{ duration: 1, repeat: Infinity, ease: 'linear' }} - style={{ display: 'none' }} // Show when loading - /> - </div>
1-24: Consolidate search bars to reduce duplication.There are multiple search components in the repo (Dashboard/SearchBar.jsx, components/SearchBar.jsx, pages/Home/SearchBar.jsx). Consider a headless useSearch hook (debounce, persistence, suggestions) and a single shared SmartSearchBar to standardize UX and logic.
src/User/pages/UIShowcase/UIShowcase.jsx (2)
1-1: Avoid dangling timeouts for the loader.setTimeout in onMouseLeave can stack and survive unmount. Keep a ref and clear on reuse/unmount.
-import React, { useState } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; @@ - const [showLoader, setShowLoader] = useState(false); + const [showLoader, setShowLoader] = useState(false); + const loaderTimerRef = useRef(null); + useEffect(() => { + return () => { + if (loaderTimerRef.current) clearTimeout(loaderTimerRef.current); + }; + }, []); @@ - <AnimatedButton - variant="danger" - onClick={() => setShowLoader(true)} - onMouseLeave={() => setTimeout(() => setShowLoader(false), 2000)} - > + <AnimatedButton + variant="danger" + onClick={() => setShowLoader(true)} + onMouseLeave={() => { + if (loaderTimerRef.current) clearTimeout(loaderTimerRef.current); + loaderTimerRef.current = setTimeout(() => setShowLoader(false), 2000); + }} + >Also applies to: 12-14, 209-215
286-288: Wire “Back to Home” to navigation (optional).Hook this to routing so the CTA does something in the showcase.
Example with react-router:
- <AnimatedButton variant="secondary" size="lg"> - Back to Home - </AnimatedButton> + <AnimatedButton variant="secondary" size="lg" onClick={() => window.location.assign('/')}> + Back to Home + </AnimatedButton>(Or use useNavigate if react-router is available.)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (19)
README.md(1 hunks)src/User/components/EnhancedButtons/AnimatedButton.jsx(1 hunks)src/User/components/EnhancedHero/EnhancedHero.jsx(1 hunks)src/User/components/EnhancedProductCard/EnhancedProductCard.jsx(1 hunks)src/User/components/EnhancedSearch/SmartSearchBar.jsx(1 hunks)src/User/components/Footer/Footer.jsx(2 hunks)src/User/components/LoadingComponents/PageLoader.jsx(1 hunks)src/User/components/LoadingComponents/ProductCardSkeleton.jsx(1 hunks)src/User/components/ModernNavbar/ModernNavbar.jsx(1 hunks)src/User/components/Popular_Categories/ProductGrid.jsx(5 hunks)src/User/components/ProductCard/ProductCard.jsx(2 hunks)src/User/pages/Contributors/Contributors.jsx(3 hunks)src/User/pages/Help/Help.jsx(2 hunks)src/User/pages/Home/Home.jsx(3 hunks)src/User/pages/UIShowcase/UIShowcase.jsx(1 hunks)src/User/pages/UserAuth/UserAuth.jsx(1 hunks)src/context/AuthContext.jsx(1 hunks)src/index.css(1 hunks)src/styles/enhanced-ui.css(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (13)
src/User/components/Popular_Categories/ProductGrid.jsx (3)
src/User/components/Navbar/UserNavbar.jsx (1)
isLoggedIn(22-22)src/User/pages/Dashboard/dashboard-cart.jsx (1)
isLoggedIn(168-168)src/AdminRoute.jsx (1)
isLoggedIn(5-17)
src/User/components/LoadingComponents/ProductCardSkeleton.jsx (1)
src/User/components/HomPageCard/LatestInMarketCard.jsx (1)
LatestInMarketCard(4-110)
src/User/components/EnhancedHero/EnhancedHero.jsx (2)
src/User/components/About/Header.jsx (1)
Header(4-28)src/User/components/About/ServiceHeader.jsx (1)
Header(4-25)
src/User/components/ModernNavbar/ModernNavbar.jsx (2)
src/User/components/Navbar/MobileMenu.jsx (1)
div(6-104)src/User/components/Navbar/UserNavbar.jsx (1)
isOpen(12-264)
src/User/pages/UserAuth/UserAuth.jsx (2)
src/User/pages/TermsAndCondition/TermsAndCondition.jsx (1)
useEffect(4-173)src/User/pages/Privacy-Policy/Privacy.jsx (1)
useEffect(7-384)
src/User/components/LoadingComponents/PageLoader.jsx (1)
src/User/components/progressbar/Loader.jsx (1)
TopLoader(4-14)
src/User/pages/Contributors/Contributors.jsx (2)
src/User/components/Team/Team.jsx (1)
contributor(323-341)src/User/pages/MeetTheMakers/MeetTheMakers.jsx (1)
MeetTheMakers(236-457)
src/User/components/ProductCard/ProductCard.jsx (2)
src/User/components/HomPageCard/LatestInMarketCard.jsx (1)
LatestInMarketCard(4-110)src/User/components/Products/ProductDetails.jsx (1)
dispatch(113-157)
src/User/components/EnhancedProductCard/EnhancedProductCard.jsx (1)
src/User/components/HomPageCard/LatestInMarketCard.jsx (1)
LatestInMarketCard(4-110)
src/User/components/EnhancedSearch/SmartSearchBar.jsx (4)
src/User/components/Dashboard/SearchBar.jsx (1)
div(4-15)src/User/components/SearchBar/SearchBar.jsx (1)
div(4-31)src/User/pages/Home/SearchBar.jsx (1)
div(4-28)src/Admin/Pages/AdminPanel.jsx (1)
searchTerm(11-93)
src/context/AuthContext.jsx (3)
src/AdminRoute.jsx (1)
isLoggedIn(5-17)src/User/components/Navbar/AuthButton.jsx (1)
isLoggedIn(6-25)src/User/components/Buttons/Logout.jsx (1)
Logout(4-32)
src/User/pages/Help/Help.jsx (1)
src/User/pages/FAQ/Faq.jsx (1)
App(99-134)
src/User/pages/Home/Home.jsx (1)
src/User/components/HomPageCard/LatestInMarketCard.jsx (1)
LatestInMarketCard(4-110)
🔇 Additional comments (9)
src/User/components/Footer/Footer.jsx (1)
9-9: Adding GitHub icon import: LGTM.src/User/pages/Help/Help.jsx (1)
178-178: Closing wrapper fixes prior layout bleed: LGTM.src/User/pages/Contributors/Contributors.jsx (1)
1-1: Importing useState: LGTM.src/User/components/ModernNavbar/ModernNavbar.jsx (2)
137-151: Hard-coded badge counts (“3”, “2”).Bind to real wishlist/cart counts (context/store) and hide when zero.
59-67: Scope alignment check.This component overlaps with existing Navbar components. Confirm intended replacement vs. coexistence and update routes/tests accordingly to avoid duplicate navs or inconsistent menus.
src/User/components/ProductCard/ProductCard.jsx (1)
7-9: AuthProvider wrapper confirmed
AuthProvider in src/App.jsx (lines 88–261) wraps the entire app, including ProductCard, so useAuth is safely provided.src/User/components/EnhancedProductCard/EnhancedProductCard.jsx (1)
1-4: No action required: ‘framer-motion’ is listed in dependencies (version ^12.0.5).src/User/pages/Home/Home.jsx (2)
140-149: Loading simulation is fine; ensure no leakage on unmountYou’ve already cleared the timer in cleanup. LGTM.
9-10: framer-motion dependency confirmed
package.json lists"framer-motion": "^12.0.5".
| const [isAdmin, setIsAdmin] = useState(false); | ||
| const [isLoggedIn, setIsLoggedIn] = useState(false); | ||
| const [user, setUser] = useState(null); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initialize from localStorage to avoid auth desyncs
Other parts of the app read isLoggedIn/isAdmin from localStorage. Without initializing from it, routes may mis-gate users after refresh.
- const [isAdmin, setIsAdmin] = useState(false);
- const [isLoggedIn, setIsLoggedIn] = useState(false);
- const [user, setUser] = useState(null);
+ const [isAdmin, setIsAdmin] = useState(() => localStorage.getItem('isAdmin') === 'true');
+ const [isLoggedIn, setIsLoggedIn] = useState(() => localStorage.getItem('isLoggedIn') === 'true');
+ const [user, setUser] = useState(() => {
+ try {
+ const raw = localStorage.getItem('user');
+ return raw ? JSON.parse(raw) : null;
+ } catch {
+ return null;
+ }
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const [isAdmin, setIsAdmin] = useState(false); | |
| const [isLoggedIn, setIsLoggedIn] = useState(false); | |
| const [user, setUser] = useState(null); | |
| const [isAdmin, setIsAdmin] = useState(() => localStorage.getItem('isAdmin') === 'true'); | |
| const [isLoggedIn, setIsLoggedIn] = useState(() => localStorage.getItem('isLoggedIn') === 'true'); | |
| const [user, setUser] = useState(() => { | |
| try { | |
| const raw = localStorage.getItem('user'); | |
| return raw ? JSON.parse(raw) : null; | |
| } catch { | |
| return null; | |
| } | |
| }); |
🤖 Prompt for AI Agents
In src/context/AuthContext.jsx around lines 6 to 9, the isAdmin/isLoggedIn/user
state variables are initialized to static defaults which can desync from values
stored in localStorage; change the useState initializers to read existing values
from localStorage (using lazy initializer functions) — parse booleans and JSON
safely, provide sensible fallbacks (false/null) if keys are missing or parsing
fails, and ensure any updates to these states also write back to localStorage to
keep them in sync.
| const loginAsAdmin = () => { | ||
| setIsAdmin(true); | ||
| setIsLoggedIn(true); | ||
| }; | ||
|
|
||
| const loginAsUser = (userData = null) => { | ||
| setIsAdmin(false); | ||
| setIsLoggedIn(true); | ||
| setUser(userData); | ||
| }; | ||
|
|
||
| const logout = () => { | ||
| setIsAdmin(false); | ||
| setIsLoggedIn(false); | ||
| setUser(null); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Persist changes to localStorage in login/logout handlers
Keeps AdminRoute, Navbar, and other consumers consistent across refreshes.
- const loginAsAdmin = () => {
- setIsAdmin(true);
- setIsLoggedIn(true);
- };
+ const loginAsAdmin = () => {
+ setIsAdmin(true);
+ setIsLoggedIn(true);
+ localStorage.setItem('isAdmin', 'true');
+ localStorage.setItem('isLoggedIn', 'true');
+ };
@@
- const loginAsUser = (userData = null) => {
- setIsAdmin(false);
- setIsLoggedIn(true);
- setUser(userData);
- };
+ const loginAsUser = (userData = null) => {
+ setIsAdmin(false);
+ setIsLoggedIn(true);
+ setUser(userData);
+ localStorage.setItem('isAdmin', 'false');
+ localStorage.setItem('isLoggedIn', 'true');
+ if (userData) localStorage.setItem('user', JSON.stringify(userData));
+ };
@@
- const logout = () => {
- setIsAdmin(false);
- setIsLoggedIn(false);
- setUser(null);
- };
+ const logout = () => {
+ setIsAdmin(false);
+ setIsLoggedIn(false);
+ setUser(null);
+ localStorage.setItem('isAdmin', 'false');
+ localStorage.setItem('isLoggedIn', 'false');
+ localStorage.removeItem('user');
+ };📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const loginAsAdmin = () => { | |
| setIsAdmin(true); | |
| setIsLoggedIn(true); | |
| }; | |
| const loginAsUser = (userData = null) => { | |
| setIsAdmin(false); | |
| setIsLoggedIn(true); | |
| setUser(userData); | |
| }; | |
| const logout = () => { | |
| setIsAdmin(false); | |
| setIsLoggedIn(false); | |
| setUser(null); | |
| }; | |
| const loginAsAdmin = () => { | |
| setIsAdmin(true); | |
| setIsLoggedIn(true); | |
| localStorage.setItem('isAdmin', 'true'); | |
| localStorage.setItem('isLoggedIn', 'true'); | |
| }; | |
| const loginAsUser = (userData = null) => { | |
| setIsAdmin(false); | |
| setIsLoggedIn(true); | |
| setUser(userData); | |
| localStorage.setItem('isAdmin', 'false'); | |
| localStorage.setItem('isLoggedIn', 'true'); | |
| if (userData) localStorage.setItem('user', JSON.stringify(userData)); | |
| }; | |
| const logout = () => { | |
| setIsAdmin(false); | |
| setIsLoggedIn(false); | |
| setUser(null); | |
| localStorage.setItem('isAdmin', 'false'); | |
| localStorage.setItem('isLoggedIn', 'false'); | |
| localStorage.removeItem('user'); | |
| }; |
🤖 Prompt for AI Agents
In src/context/AuthContext.jsx around lines 10 to 25, the loginAsAdmin,
loginAsUser, and logout handlers update state but do not persist those changes
to localStorage; update each handler to write the relevant keys to localStorage
(e.g., set "isAdmin" and "isLoggedIn" as strings and "user" as a JSON string in
loginAsAdmin/loginAsUser) and ensure logout clears or removes those keys
(removeItem or set to null) so state survives page refreshes and consumers stay
consistent.
| /* Animations */ | ||
| @keyframes float { | ||
| 0%, 100% { transform: translateY(0px); } | ||
| 50% { transform: translateY(-20px); } | ||
| } | ||
|
|
||
| @keyframes shimmer { | ||
| 0% { transform: translateX(-100%); } | ||
| 100% { transform: translateX(100%); } | ||
| } | ||
|
|
||
| @keyframes fadeInUp { | ||
| from { | ||
| opacity: 0; | ||
| transform: translateY(30px); | ||
| } | ||
| to { | ||
| opacity: 1; | ||
| transform: translateY(0); | ||
| } | ||
| } | ||
|
|
||
| /* Utility Classes */ | ||
| .animate-float { | ||
| animation: float 3s ease-in-out infinite; | ||
| } | ||
|
|
||
| .animate-shimmer { | ||
| animation: shimmer 2s infinite; | ||
| } | ||
|
|
||
| .animate-fade-in-up { | ||
| animation: fadeInUp 0.6s ease-out; | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Honor prefers-reduced-motion for animations and skeletons
Provide a motion-reduced experience to avoid accessibility issues.
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-20px); }
}
@@
.animate-float {
animation: float 3s ease-in-out infinite;
}
@@
.animate-shimmer {
animation: shimmer 2s infinite;
}
@@
.animate-fade-in-up {
animation: fadeInUp 0.6s ease-out;
}
@@
.skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
+
+@media (prefers-reduced-motion: reduce) {
+ .animate-float,
+ .animate-shimmer,
+ .animate-fade-in-up,
+ .skeleton {
+ animation: none !important;
+ }
+}Also applies to: 105-110
🤖 Prompt for AI Agents
In src/styles/enhanced-ui.css around lines 37-71 (also apply same change for
lines ~105-110), the CSS animations don’t respect users’ prefers-reduced-motion
setting; add a media query @media (prefers-reduced-motion: reduce) that targets
the animated key classes (.animate-float, .animate-shimmer, .animate-fade-in-up
and any skeleton/shimmer classes used at 105-110) and override their animations
by setting animation: none !important; transform: none !important; and any
animated background-position to a static value so motion is removed while
preserving layout and accessibility.
| const handleClick = (e) => { | ||
| if (disabled) return; | ||
|
|
||
| // Create ripple effect | ||
| const button = e.currentTarget; | ||
| const rect = button.getBoundingClientRect(); | ||
| const size = Math.max(rect.width, rect.height); | ||
| const x = e.clientX - rect.left - size / 2; | ||
| const y = e.clientY - rect.top - size / 2; | ||
|
|
||
| const ripple = document.createElement('span'); | ||
| ripple.style.cssText = ` | ||
| position: absolute; | ||
| left: ${x}px; | ||
| top: ${y}px; | ||
| width: ${size}px; | ||
| height: ${size}px; | ||
| background: rgba(255, 255, 255, 0.3); | ||
| border-radius: 50%; | ||
| transform: scale(0); | ||
| animation: ripple 0.6s ease-out; | ||
| pointer-events: none; | ||
| `; | ||
|
|
||
| button.appendChild(ripple); | ||
| setTimeout(() => ripple.remove(), 600); | ||
|
|
||
| if (onClick) onClick(e); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Center ripple for keyboard-initiated “clicks”; current math uses clientX/Y which are 0.
const handleClick = (e) => {
if (disabled) return;
// Create ripple effect
const button = e.currentTarget;
const rect = button.getBoundingClientRect();
const size = Math.max(rect.width, rect.height);
- const x = e.clientX - rect.left - size / 2;
- const y = e.clientY - rect.top - size / 2;
+ const isKeyboard = e.clientX === 0 && e.clientY === 0;
+ const x = isKeyboard ? rect.width / 2 - size / 2 : e.clientX - rect.left - size / 2;
+ const y = isKeyboard ? rect.height / 2 - size / 2 : e.clientY - rect.top - size / 2;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleClick = (e) => { | |
| if (disabled) return; | |
| // Create ripple effect | |
| const button = e.currentTarget; | |
| const rect = button.getBoundingClientRect(); | |
| const size = Math.max(rect.width, rect.height); | |
| const x = e.clientX - rect.left - size / 2; | |
| const y = e.clientY - rect.top - size / 2; | |
| const ripple = document.createElement('span'); | |
| ripple.style.cssText = ` | |
| position: absolute; | |
| left: ${x}px; | |
| top: ${y}px; | |
| width: ${size}px; | |
| height: ${size}px; | |
| background: rgba(255, 255, 255, 0.3); | |
| border-radius: 50%; | |
| transform: scale(0); | |
| animation: ripple 0.6s ease-out; | |
| pointer-events: none; | |
| `; | |
| button.appendChild(ripple); | |
| setTimeout(() => ripple.remove(), 600); | |
| if (onClick) onClick(e); | |
| }; | |
| const handleClick = (e) => { | |
| if (disabled) return; | |
| // Create ripple effect | |
| const button = e.currentTarget; | |
| const rect = button.getBoundingClientRect(); | |
| const size = Math.max(rect.width, rect.height); | |
| const isKeyboard = e.clientX === 0 && e.clientY === 0; | |
| - const x = e.clientX - rect.left - size / 2; | |
| const x = isKeyboard | |
| ? rect.width / 2 - size / 2 | |
| : e.clientX - rect.left - size / 2; | |
| const y = isKeyboard | |
| ? rect.height / 2 - size / 2 | |
| : e.clientY - rect.top - size / 2; | |
| const ripple = document.createElement('span'); | |
| ripple.style.cssText = ` | |
| position: absolute; | |
| left: ${x}px; | |
| top: ${y}px; | |
| width: ${size}px; | |
| height: ${size}px; | |
| background: rgba(255, 255, 255, 0.3); | |
| border-radius: 50%; | |
| transform: scale(0); | |
| animation: ripple 0.6s ease-out; | |
| pointer-events: none; | |
| `; | |
| button.appendChild(ripple); | |
| setTimeout(() => ripple.remove(), 600); | |
| if (onClick) onClick(e); | |
| }; |
🤖 Prompt for AI Agents
In src/User/components/EnhancedButtons/AnimatedButton.jsx around lines 52 to 80,
the ripple position calculation uses e.clientX/Y which are 0 for
keyboard-initiated clicks so the ripple appears off-center; detect when
clientX/clientY are 0 (or not present) and fall back to centering the ripple by
using (rect.width - size)/2 and (rect.height - size)/2 for x and y respectively
before creating the span, leaving the rest of the ripple creation/removal and
onClick call unchanged.
| return ( | ||
| <motion.button | ||
| className={`${baseClasses} ${variants[variant]} ${sizes[size]} ${className} ${ | ||
| disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer' | ||
| }`} | ||
| variants={buttonVariants} | ||
| initial="initial" | ||
| whileHover={!disabled ? "hover" : "initial"} | ||
| whileTap={!disabled ? "tap" : "initial"} | ||
| onClick={handleClick} | ||
| disabled={disabled} | ||
| {...props} | ||
| > |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Default button type should be “button” to prevent accidental form submits.
- <motion.button
+ <motion.button
+ type="button"
className={`${baseClasses} ${variants[variant]} ${sizes[size]} ${className} ${
disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'
}`}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return ( | |
| <motion.button | |
| className={`${baseClasses} ${variants[variant]} ${sizes[size]} ${className} ${ | |
| disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer' | |
| }`} | |
| variants={buttonVariants} | |
| initial="initial" | |
| whileHover={!disabled ? "hover" : "initial"} | |
| whileTap={!disabled ? "tap" : "initial"} | |
| onClick={handleClick} | |
| disabled={disabled} | |
| {...props} | |
| > | |
| return ( | |
| <motion.button | |
| type="button" | |
| className={`${baseClasses} ${variants[variant]} ${sizes[size]} ${className} ${ | |
| disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer' | |
| }`} | |
| variants={buttonVariants} | |
| initial="initial" | |
| whileHover={!disabled ? "hover" : "initial"} | |
| whileTap={!disabled ? "tap" : "initial"} | |
| onClick={handleClick} | |
| disabled={disabled} | |
| {...props} | |
| > |
🤖 Prompt for AI Agents
In src/User/components/EnhancedButtons/AnimatedButton.jsx around lines 82 to 94,
the rendered motion.button lacks an explicit type which can cause accidental
form submissions; set a default type of "button" on the element (e.g., pass
type={type || 'button'} or ensure props.type is respected while defaulting to
'button') so the button doesn't submit forms unless explicitly specified.
| return ( | ||
| <div className="fixed inset-0 bg-white/90 backdrop-blur-sm flex items-center justify-center z-50"> | ||
| <motion.div | ||
| variants={containerVariants} | ||
| animate="animate" | ||
| className="text-center" | ||
| > | ||
| {/* Animated Leaves */} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
A11y: announce loading, hide decoratives, and add reduced motion
Add role/aria attributes, remove ineffective style-based animationDelay, and respect prefers-reduced-motion.
-import React from 'react';
-import { motion } from 'framer-motion';
+import React from 'react';
+import { motion, useReducedMotion } from 'framer-motion';
@@
-const PageLoader = () => {
+const PageLoader = () => {
+ const prefersReducedMotion = useReducedMotion();
const containerVariants = {
animate: {
transition: {
staggerChildren: 0.2
}
}
};
@@
- const leafVariants = {
- animate: {
- y: [0, -20, 0],
- rotate: [0, 10, -10, 0],
- transition: {
- duration: 2,
- repeat: Infinity,
- ease: "easeInOut"
- }
- }
- };
+ const leafVariants = {
+ animate: (i = 0) => ({
+ y: prefersReducedMotion ? 0 : [0, -20, 0],
+ rotate: prefersReducedMotion ? 0 : [0, 10, -10, 0],
+ transition: {
+ duration: prefersReducedMotion ? 0 : 2,
+ repeat: prefersReducedMotion ? 0 : Infinity,
+ ease: "easeInOut",
+ delay: i * 0.2
+ }
+ })
+ };
@@
return (
- <div className="fixed inset-0 bg-white/90 backdrop-blur-sm flex items-center justify-center z-50">
+ <div
+ className="fixed inset-0 bg-white/90 backdrop-blur-sm flex items-center justify-center z-50"
+ role="status"
+ aria-live="polite"
+ aria-label="Loading"
+ aria-busy="true"
+ >
@@
- <div className="flex justify-center space-x-2 mb-6">
+ <div className="flex justify-center space-x-2 mb-6" aria-hidden="true">
{[...Array(3)].map((_, i) => (
<motion.div
key={i}
- variants={leafVariants}
- animate="animate"
- style={{ animationDelay: `${i * 0.2}s` }}
+ variants={leafVariants}
+ custom={i}
+ animate="animate"
className="text-green-600"
>
<FaLeaf size={24} />
</motion.div>
))}
</div>
@@
- <div className="w-64 h-2 bg-gray-200 rounded-full mt-6 overflow-hidden">
+ <div className="w-64 h-2 bg-gray-200 rounded-full mt-6 overflow-hidden" aria-hidden="true">
<motion.div
className="h-full bg-gradient-to-r from-green-400 to-green-600 rounded-full"
animate={{
- x: [-256, 256],
+ x: prefersReducedMotion ? 0 : [-256, 256],
transition: {
- duration: 2,
- repeat: Infinity,
+ duration: prefersReducedMotion ? 0 : 2,
+ repeat: prefersReducedMotion ? 0 : Infinity,
ease: "easeInOut"
}
}}
/>
</div>Also applies to: 59-76, 77-90
🤖 Prompt for AI Agents
In src/User/components/LoadingComponents/PageLoader.jsx around lines 37-44 (and
similarly for 59-76 and 77-90), the loader lacks accessibility attributes, uses
inline style-based animationDelay on decorative elements, and doesn't respect
reduced-motion preferences; update the top wrapper to include role="status",
aria-live="polite" and aria-busy="true", mark purely decorative elements
(animated leaves) with aria-hidden="true" and remove the ineffective style-based
animationDelay from JSX, implement staggering via motion variants or CSS classes
instead, and integrate prefers-reduced-motion handling (e.g., use
framer-motion's useReducedMotion or
window.matchMedia("(prefers-reduced-motion)") to disable/short-circuit
animations and fall back to a static display). Ensure the same changes are
applied to the other referenced blocks (lines 59-76 and 77-90).
| return ( | ||
| <div className="bg-white rounded-2xl shadow-lg overflow-hidden max-w-[280px] mx-auto"> | ||
| {/* Image Skeleton */} | ||
| <div className="relative h-48 bg-gray-200 overflow-hidden"> | ||
| <motion.div | ||
| variants={shimmerVariants} | ||
| animate="animate" | ||
| className="absolute inset-0 bg-gradient-to-r from-transparent via-white/60 to-transparent" | ||
| /> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add role and aria-live to announce loading state
Expose a single live region and hide decorative shimmers from AT.
- return (
- <div className="bg-white rounded-2xl shadow-lg overflow-hidden max-w-[280px] mx-auto">
+ return (
+ <div
+ className="bg-white rounded-2xl shadow-lg overflow-hidden max-w-[280px] mx-auto"
+ role="status"
+ aria-live="polite"
+ aria-label="Loading product"
+ >
@@
- <motion.div
+ <motion.div
variants={shimmerVariants}
animate="animate"
- className="absolute inset-0 bg-gradient-to-r from-transparent via-white/60 to-transparent"
+ aria-hidden="true"
+ className="absolute inset-0 bg-gradient-to-r from-transparent via-white/60 to-transparent"
/>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return ( | |
| <div className="bg-white rounded-2xl shadow-lg overflow-hidden max-w-[280px] mx-auto"> | |
| {/* Image Skeleton */} | |
| <div className="relative h-48 bg-gray-200 overflow-hidden"> | |
| <motion.div | |
| variants={shimmerVariants} | |
| animate="animate" | |
| className="absolute inset-0 bg-gradient-to-r from-transparent via-white/60 to-transparent" | |
| /> | |
| </div> | |
| return ( | |
| <div | |
| className="bg-white rounded-2xl shadow-lg overflow-hidden max-w-[280px] mx-auto" | |
| role="status" | |
| aria-live="polite" | |
| aria-label="Loading product" | |
| > | |
| {/* Image Skeleton */} | |
| <div className="relative h-48 bg-gray-200 overflow-hidden"> | |
| <motion.div | |
| variants={shimmerVariants} | |
| animate="animate" | |
| aria-hidden="true" | |
| className="absolute inset-0 bg-gradient-to-r from-transparent via-white/60 to-transparent" | |
| /> | |
| </div> |
🤖 Prompt for AI Agents
In src/User/components/LoadingComponents/ProductCardSkeleton.jsx around lines 16
to 25, the loading shimmers are currently exposed to assistive tech; add an
accessible live region and hide decorative elements: add a single visually
hidden element (e.g. a span with className="sr-only") at the top of the card
with role="status" aria-live="polite" aria-atomic="true" containing a short
message like "Loading product…" to announce the loading state, and mark the
decorative shimmer elements and the image skeleton container with
aria-hidden="true" so they are ignored by AT.
| const navItems = [ | ||
| { name: 'Home', path: '/' }, | ||
| { name: 'Categories', path: '/categories' }, | ||
| { name: 'Products', path: '/products' }, | ||
| { name: 'About', path: '/about-us' }, | ||
| { name: 'Contact', path: '/contact' } | ||
| ]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nav routes likely don’t exist in this app; align with existing UserNavbar/MobileMenu routes to avoid 404s.
Based on existing components, consider updating defaults:
- const navItems = [
- { name: 'Home', path: '/' },
- { name: 'Categories', path: '/categories' },
- { name: 'Products', path: '/products' },
- { name: 'About', path: '/about-us' },
- { name: 'Contact', path: '/contact' }
- ];
+ const navItems = [
+ { name: 'Home', path: '/' },
+ { name: 'Fashion', path: '/popularCategories/fashionAccessories' },
+ { name: 'Gifts', path: '/popularCategories/customizedGifts' },
+ { name: 'Furniture', path: '/popularCategories/furnitureDecor' },
+ { name: 'Stationary', path: '/popularCategories/printingStationery' },
+ { name: 'Body-Care', path: '/popularCategories/bodyCare' }
+ ];Also consider startsWith matching for nested routes (see next comment).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const navItems = [ | |
| { name: 'Home', path: '/' }, | |
| { name: 'Categories', path: '/categories' }, | |
| { name: 'Products', path: '/products' }, | |
| { name: 'About', path: '/about-us' }, | |
| { name: 'Contact', path: '/contact' } | |
| ]; | |
| const navItems = [ | |
| { name: 'Home', path: '/' }, | |
| { name: 'Fashion', path: '/popularCategories/fashionAccessories' }, | |
| { name: 'Gifts', path: '/popularCategories/customizedGifts' }, | |
| { name: 'Furniture', path: '/popularCategories/furnitureDecor' }, | |
| { name: 'Stationary', path: '/popularCategories/printingStationery' }, | |
| { name: 'Body-Care', path: '/popularCategories/bodyCare' } | |
| ]; |
🤖 Prompt for AI Agents
In src/User/components/ModernNavbar/ModernNavbar.jsx around lines 22–28, the
hardcoded navItems use routes (/, /categories, /products, /about-us, /contact)
that likely don’t exist in this app and will cause 404s; replace this array with
the exact route objects used by the existing UserNavbar and MobileMenu
components (open those files and copy their names/paths), and update any
active-link logic to use path.startsWith(route.path) for nested-route matching
so parent items stay active on child pages.
| const { isLoggedIn } = useAuth(); | ||
| const cartItems = useSelector((state) => state.cart.items); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Confirm AuthContext is available app-wide
Ensure isLoggedIn here is consistent with other places still using localStorage.
🏁 Script executed:
#!/bin/bash
# Find context provider and mixed sources of truth
rg -nP -C2 'AuthProvider|useAuth\(|localStorage\.getItem\(["\']isLoggedIn["\']\)' --type=tsx,ts,jsx,jsLength of output: 116
🏁 Script executed:
#!/bin/bash
# Search for AuthProvider definitions and usage
rg -n "AuthProvider" -C2 --type js,jsx,ts,tsx
# Search for useAuth() hook usage
rg -n "useAuth\s*\(" -C2 --type js,jsx,ts,tsx
# Search for localStorage.getItem/setItem for isLoggedIn
rg -n "localStorage\s*\.\s*(getItem|setItem)\(['\"]isLoggedIn['\"]\)" -C2 --type js,jsx,ts,tsxLength of output: 308
🏁 Script executed:
#!/bin/bash
# Search for AuthProvider references
rg -n "AuthProvider" -C2 .
# Search for AuthContext definitions
rg -n "AuthContext" -C2 .
# Search for any createContext calls (React context)
rg -n "createContext" -C2 .
# Search for useAuth hook implementation
rg -n "useAuth" -C2 .
# Search for direct localStorage getItem/setItem of 'isLoggedIn'
rg -n "localStorage.*isLoggedIn" -C2 .Length of output: 19886
Unify isLoggedIn state between Context and localStorage
- AuthProvider in App.jsx wraps the entire app, so useAuth() is available app-wide.
- AuthContext’s isLoggedIn isn’t initialized from or persisted to localStorage, yet numerous components (Navbar, PrivateRoute, UserAuth, Dashboard, etc.) still read directly from localStorage. Refactor AuthContext (e.g. in src/context/AuthContext.jsx) to load initial isLoggedIn from localStorage and write back on changes, then replace all direct localStorage.getItem/setItem("isLoggedIn") calls with useAuth().
| const handleAddToCart = () => { | ||
| if (!isLoggedIn) { | ||
| alert("Please login to add items to your cart!"); | ||
| navigate("/auth"); | ||
| return; | ||
| } | ||
|
|
||
| // Add to cart logic here | ||
| alert(`${title} added to cart!`); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Misleading “added to cart” UX: no cart state update happens
The handler only shows alerts and navigates; it never updates the Redux cart, so users get a success message without any effect.
Two safe paths—pick one and I can help wire it:
- Option A (preferred): Accept an onAddToCart(product) prop and invoke it here.
- Option B: Integrate Redux directly (import useDispatch and manageCartItem, dispatch with a proper product object).
If you choose Option B, confirm the expected product shape (id/title/image/price) before I propose a concrete diff.
Run to locate call sites and see how ProductCard is used:
🏁 Script executed:
#!/bin/bash
rg -nP --type=tsx,ts,jsx,js -C2 '\b<ProductCard\b'Length of output: 90
🏁 Script executed:
#!/bin/bash
# Locate the ProductCard component path
find . -type f -name "ProductCard.jsx"
# Search for any JSX usage of ProductCard in the codebase
rg -nC2 '<ProductCard' .Length of output: 3736
🏁 Script executed:
#!/bin/bash
# Display the first 200 lines of the ProductCard component
sed -n '1,200p' src/User/components/ProductCard/ProductCard.jsx
# Check for Redux dispatch usage
rg -nC2 'useDispatch' src/User/components/ProductCard/ProductCard.jsx
rg -nC2 'dispatch' src/User/components/ProductCard/ProductCard.jsxLength of output: 2032
Misleading “Add to Bag” UX: no cart state update
File: src/User/components/ProductCard/ProductCard.jsx Lines 10–19
const handleAddToCart = () => {
if (!isLoggedIn) {
alert("Please login to add items to your cart!");
navigate("/auth");
return;
}
// Add to cart logic here
alert(`${title} added to cart!`);
};handleAddToCart only shows alerts and navigation—it never updates any cart state. Fix by choosing one:
- Preferred: accept an onAddToCart(product) prop and invoke it here.
- Or: import useDispatch from react-redux and dispatch a manageCartItem action with the product (id/title/image/price).
Confirm your preferred path and I’ll draft the diff.
🤖 Prompt for AI Agents
In src/User/components/ProductCard/ProductCard.jsx around lines 10 to 19,
handleAddToCart currently only shows alerts and navigates but does not update
cart state; change it to call an onAddToCart(product) prop (preferred) — accept
onAddToCart in the component props, construct a product object (id, title,
image, price) or use the existing product prop, and invoke onAddToCart(product)
after the isLoggedIn check (keep the login alert/navigation behavior intact);
ensure you guard the call with typeof onAddToCart === 'function' before invoking
and remove or replace the temporary alert with a brief confirmation if desired.
🐛 Bug Fixes & UI Improvements
Issues Fixed:
Changes Made:
1. Contributors Page (
src/User/pages/Contributors/Contributors.jsx)useState2. User Authentication (
src/User/pages/UserAuth/UserAuth.jsx)3. Help Page (
src/User/pages/Help/Help.jsx)4. Footer (
src/User/components/Footer/Footer.jsx)5. Product Rating Display (
src/User/components/Popular_Categories/ProductGrid.jsx)Testing:
Type of Change:
Summary by CodeRabbit