-
Notifications
You must be signed in to change notification settings - Fork 419
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 (animated button, hero, product card, smart search, navbar, loaders, skeletons), a UI showcase page, global enhanced UI styles, and AuthContext login state; integrates login-gating into product actions and small UI/layout tweaks (Help, Contributors, Footer) plus a README typo fix. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as ProductGrid/ProductCard
participant Auth as AuthContext
participant Router
User->>UI: Click "Add to Cart"/"Buy Now"/"Wishlist"
UI->>Auth: read isLoggedIn
alt isLoggedIn = false
UI-->>User: show "Please login..." toast/alert
UI->>Router: navigate("/auth")
else isLoggedIn = true
UI-->>User: perform action (add to cart / proceed to checkout / toggle wishlist)
end
sequenceDiagram
participant User
participant Search as SmartSearchBar
participant LS as localStorage
participant Parent as onSearch callback
User->>Search: focus / type query
Search-->>Search: filter mockSuggestions, update dropdown
User->>Search: press Enter or click suggestion
Search->>LS: update recentSearches (dedupe, cap 5)
Search->>Parent: invoke onSearch(query)
Search-->>User: close dropdown, update input
Estimated code review effortπ― 4 (Complex) | β±οΈ ~60 minutes Notes for review:
Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touchesβ Failed checks (1 inconclusive)
β Passed checks (2 passed)
β¨ Finishing touches
π§ͺ Generate unit tests (beta)
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. Comment |
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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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: 1
Caution
Some comments are outside the diff and canβt be posted inline due to platform limitations.
β οΈ Outside diff range comments (2)
src/User/pages/UserAuth/UserAuth.jsx (2)
43-43: Phone validation function is unused.The
isPhoneValidfunction is defined but never called inhandleSignup. Consider validating the phone number before submitting the form to provide immediate feedback.Apply this diff to add phone validation in the signup handler:
const handleSignup = async (e) => { // Signup ka logic waise hi rahega e.preventDefault(); setLoading(true); if (signupPassword !== confirmPassword) { await Swal.fire({ title: "Passwords do not match", icon: "error" }); setLoading(false); return; } + if (!isPhoneValid(phone)) { + await Swal.fire({ title: "Invalid phone number", text: "Please enter a valid 10-digit phone number", icon: "error" }); + setLoading(false); + return; + } // ... baaki validation ...
167-172: Hardcoded localhost URL will break in production.The Google auth redirect uses a hardcoded
localhost:3000URL instead of the environment variable pattern used elsewhere (lines 89, 120). This will fail in non-local environments.Apply this diff to use the environment variable:
} else if (provider === "google") { - const apiUrl = - "http://localhost:3000"; + const apiUrl = import.meta.env.VITE_API_URL || "http://localhost:3000"; // Google ke liye backend par redirect karein window.location.href = `${apiUrl}/auth/google`; }
π§Ή Nitpick comments (1)
src/User/pages/UserAuth/UserAuth.jsx (1)
458-483: Consider managing checkbox state for consistency.While the checkbox relies on HTML5 validation (which works), all other form inputs use controlled component patterns with state. Adding state management would improve consistency and enable programmatic control.
If you'd like to add state management, apply these diffs:
- Add state near the other signup state declarations (after line 41):
const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [passwordStrength, setPasswordStrength] = useState(0); + const [termsAccepted, setTermsAccepted] = useState(false);
- Update the checkbox to be a controlled component:
<input type="checkbox" id="termsAccepted" + checked={termsAccepted} + onChange={(e) => setTermsAccepted(e.target.checked)} required className="mt-1 h-4 w-4 rounded border-gray-300 text-emerald-600 focus:ring-emerald-500" />
π Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
π Files selected for processing (4)
README.md(1 hunks)src/User/components/Footer/Footer.jsx(2 hunks)src/User/components/Popular_Categories/ProductGrid.jsx(6 hunks)src/User/pages/UserAuth/UserAuth.jsx(1 hunks)
π§ Files skipped from review as they are similar to previous changes (3)
- src/User/components/Popular_Categories/ProductGrid.jsx
- README.md
- src/User/components/Footer/Footer.jsx
| <Link | ||
| to="/terms-and-condition" | ||
| className="text-[#4caf50] hover:underline" | ||
| target="_blank" | ||
| > | ||
| Terms and Conditions | ||
| </Link>{" "} | ||
| and{" "} | ||
| <Link | ||
| to="/privacy-policy" | ||
| className="text-[#4caf50] hover:underline" | ||
| target="_blank" | ||
| > | ||
| Privacy Policy | ||
| </Link> |
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.
Add rel="noopener noreferrer" to external links.
The Links with target="_blank" are missing rel="noopener noreferrer", creating a security risk where the opened page can access window.opener and potentially redirect the parent page.
Apply this diff to secure the links:
<Link
to="/terms-and-condition"
className="text-[#4caf50] hover:underline"
target="_blank"
+ rel="noopener noreferrer"
>
Terms and Conditions
</Link>{" "}
and{" "}
<Link
to="/privacy-policy"
className="text-[#4caf50] hover:underline"
target="_blank"
+ rel="noopener noreferrer"
>
Privacy Policy
</Link>π 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.
| <Link | |
| to="/terms-and-condition" | |
| className="text-[#4caf50] hover:underline" | |
| target="_blank" | |
| > | |
| Terms and Conditions | |
| </Link>{" "} | |
| and{" "} | |
| <Link | |
| to="/privacy-policy" | |
| className="text-[#4caf50] hover:underline" | |
| target="_blank" | |
| > | |
| Privacy Policy | |
| </Link> | |
| <Link | |
| to="/terms-and-condition" | |
| className="text-[#4caf50] hover:underline" | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| > | |
| Terms and Conditions | |
| </Link>{" "} | |
| and{" "} | |
| <Link | |
| to="/privacy-policy" | |
| className="text-[#4caf50] hover:underline" | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| > | |
| Privacy Policy | |
| </Link> |
π€ Prompt for AI Agents
In src/User/pages/UserAuth/UserAuth.jsx around lines 467 to 481, the Link
components that use target="_blank" lack rel="noopener noreferrer"; update both
external Link elements (Terms and Conditions and Privacy Policy) to include
rel="noopener noreferrer" so the new tabs cannot access window.opener and to
mitigate reverse tabnabbing risks.
π 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
New Features
Bug Fixes
Style
Documentation
βοΈ Tip: You can customize this high-level summary in your review settings.