Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ Technologies Used :
- [Check it out here :eyes:](https://www.figma.com/design/sttuMcIvVuk7AORRMS70s5/VigyBag-Redesign?node-id=0-1&t=hA6fe3u7c9kflZzx-0)


We accpet 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 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.


## :handshake: Contributing <a name="contributing"></a>
Expand Down
121 changes: 121 additions & 0 deletions src/User/components/EnhancedButtons/AnimatedButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import React from 'react';
import { motion } from 'framer-motion';

const AnimatedButton = ({
children,
variant = 'primary',
size = 'md',
onClick,
disabled = false,
icon,
className = '',
...props
}) => {
const baseClasses = 'relative overflow-hidden font-semibold rounded-full transition-all duration-300 flex items-center justify-center gap-2';

const variants = {
primary: 'bg-gradient-to-r from-green-600 to-green-700 text-white hover:from-green-700 hover:to-green-800 shadow-lg hover:shadow-xl',
secondary: 'border-2 border-green-600 text-green-700 hover:bg-green-50 hover:border-green-700',
outline: 'border border-gray-300 text-gray-700 hover:border-gray-400 hover:bg-gray-50',
ghost: 'text-green-600 hover:bg-green-50',
danger: 'bg-gradient-to-r from-red-500 to-red-600 text-white hover:from-red-600 hover:to-red-700 shadow-lg hover:shadow-xl'
};

const sizes = {
sm: 'px-4 py-2 text-sm',
md: 'px-6 py-3 text-base',
lg: 'px-8 py-4 text-lg',
xl: 'px-10 py-5 text-xl'
};

const buttonVariants = {
initial: { scale: 1 },
hover: {
scale: 1.05,
transition: { type: 'spring', stiffness: 300, damping: 20 }
},
tap: {
scale: 0.95,
transition: { type: 'spring', stiffness: 300, damping: 20 }
}
};

const rippleVariants = {
initial: { scale: 0, opacity: 0.5 },
animate: {
scale: 4,
opacity: 0,
transition: { duration: 0.6, ease: 'easeOut' }
}
};

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);
};
Comment on lines +52 to +80
Copy link
Contributor

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.

Suggested change
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}
>
Comment on lines +82 to +94
Copy link
Contributor

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.

Suggested change
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.

{icon && (
<motion.span
initial={{ rotate: 0 }}
whileHover={{ rotate: 360 }}
transition={{ duration: 0.3 }}
>
{icon}
</motion.span>
)}
<span className="relative z-10">{children}</span>

{/* Shine effect */}
<div className="absolute inset-0 -translate-x-full bg-gradient-to-r from-transparent via-white/20 to-transparent hover:translate-x-full transition-transform duration-1000 ease-out" />

<style jsx>{`
@keyframes ripple {
to {
transform: scale(4);
opacity: 0;
}
}
`}</style>
</motion.button>
);
};

export default AnimatedButton;
205 changes: 205 additions & 0 deletions src/User/components/EnhancedHero/EnhancedHero.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import React, { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import { FaLeaf, FaRecycle, FaHeart } from 'react-icons/fa';
Comment on lines +1 to +3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

Text rotation uses exit without AnimatePresence; exit won’t run. Wrap the heading with AnimatePresence.

-import { motion } from 'framer-motion';
+import { motion, AnimatePresence } from 'framer-motion';
-          <motion.div
-            variants={itemVariants}
-            className="h-20 mb-8 flex items-center justify-center"
-          >
-            <motion.h2
-              key={currentText}
-              initial={{ opacity: 0, y: 20 }}
-              animate={{ opacity: 1, y: 0 }}
-              exit={{ opacity: 0, y: -20 }}
-              className="text-2xl md:text-4xl lg:text-5xl font-semibold text-gray-800"
-            >
-              {heroTexts[currentText]}
-            </motion.h2>
-          </motion.div>
+          <motion.div
+            variants={itemVariants}
+            className="h-20 mb-8 flex items-center justify-center"
+          >
+            <AnimatePresence mode="wait">
+              <motion.h2
+                key={currentText}
+                initial={{ opacity: 0, y: 20 }}
+                animate={{ opacity: 1, y: 0 }}
+                exit={{ opacity: 0, y: -20 }}
+                transition={{ duration: 0.3 }}
+                className="text-2xl md:text-4xl lg:text-5xl font-semibold text-gray-800"
+              >
+                {heroTexts[currentText]}
+              </motion.h2>
+            </AnimatePresence>
+          </motion.div>

Also applies to: 112-125

πŸ€– Prompt for AI Agents
In src/User/components/EnhancedHero/EnhancedHero.jsx around lines 1-3 and
112-125, the text rotation uses motion.exit but is not wrapped with
AnimatePresence so exit animations will never run; import AnimatePresence from
'framer-motion' at the top, wrap the heading/text motion element(s) with an
<AnimatePresence> block, ensure the Animated child has a stable unique key that
changes on text swap so AnimatePresence can detect enter/exit, and keep
exit/initial/animate props on the motion element to allow the exit animation to
run.

import background from '../../../assets/background.png';

const EnhancedHero = ({ onShopNowClick }) => {
const [currentText, setCurrentText] = useState(0);

const heroTexts = [
"Your Eco-Friendly Shopping Heaven",
"Sustainable Products for Better Tomorrow",
"Green Living Made Simple"
];

useEffect(() => {
const interval = setInterval(() => {
setCurrentText((prev) => (prev + 1) % heroTexts.length);
}, 3000);
return () => clearInterval(interval);
}, []);

const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
duration: 0.8,
staggerChildren: 0.2
}
}
};

const itemVariants = {
hidden: { y: 50, opacity: 0 },
visible: {
y: 0,
opacity: 1,
transition: { duration: 0.6, ease: "easeOut" }
}
};

const floatingVariants = {
animate: {
y: [-10, 10, -10],
transition: {
duration: 3,
repeat: Infinity,
ease: "easeInOut"
}
}
};

return (
<section
className="relative min-h-screen flex items-center justify-center overflow-hidden"
style={{
backgroundImage: `url(${background})`,
backgroundPosition: "center",
backgroundSize: "cover",
backgroundRepeat: "no-repeat",
}}
>
<div className="absolute inset-0 bg-gradient-to-br from-green-900/20 via-transparent to-amber-900/20" />

<motion.div
variants={floatingVariants}
animate="animate"
className="absolute top-20 left-10 text-green-600 opacity-30"
>
<FaLeaf size={40} />
</motion.div>

<motion.div
variants={floatingVariants}
animate="animate"
className="absolute top-32 right-20 text-green-500 opacity-40"
style={{ animationDelay: '1s' }}
>
<FaRecycle size={35} />
</motion.div>

<motion.div
variants={floatingVariants}
animate="animate"
className="absolute bottom-32 left-20 text-amber-600 opacity-30"
style={{ animationDelay: '2s' }}
>
<FaHeart size={30} />
</motion.div>
Comment on lines +73 to +89
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

Floating icons animate in sync; style-based animationDelay doesn’t affect Framer Motion. Add per-element transition delays and mark icons decorative.
Use Framer Motion’s transition prop (or custom variants) instead of CSS animationDelay, and add aria-hidden to avoid screen reader noise.

-      <motion.div
-        variants={floatingVariants}
-        animate="animate"
-        className="absolute top-32 right-20 text-green-500 opacity-40"
-        style={{ animationDelay: '1s' }}
-      >
-        <FaRecycle size={35} />
-      </motion.div>
+      <motion.div
+        variants={floatingVariants}
+        animate="animate"
+        transition={{ delay: 1 }}
+        className="absolute top-32 right-20 text-green-500 opacity-40"
+        aria-hidden="true"
+      >
+        <FaRecycle size={35} aria-hidden="true" focusable="false" />
+      </motion.div>

-      <motion.div
-        variants={floatingVariants}
-        animate="animate"
-        className="absolute bottom-32 left-20 text-amber-600 opacity-30"
-        style={{ animationDelay: '2s' }}
-      >
-        <FaHeart size={30} />
-      </motion.div>
+      <motion.div
+        variants={floatingVariants}
+        animate="animate"
+        transition={{ delay: 2 }}
+        className="absolute bottom-32 left-20 text-amber-600 opacity-30"
+        aria-hidden="true"
+      >
+        <FaHeart size={30} aria-hidden="true" focusable="false" />
+      </motion.div>

Also add aria-hidden to the first icon:

-        <FaLeaf size={40} />
+        <FaLeaf size={40} aria-hidden="true" focusable="false" />

Committable suggestion skipped: line range outside the PR's diff.

πŸ€– Prompt for AI Agents
In src/User/components/EnhancedHero/EnhancedHero.jsx around lines 73 to 89, the
floating icons currently use style-based animationDelay which Framer Motion
ignores and they animate in sync; replace the inline style delays with Framer
Motion transitions (e.g., add transition={{ delay: 1 }} for the first motion.div
and transition={{ delay: 2 }} for the second, or use custom variant props and
pass a delay into the variant) and mark the icon elements as decorative by
adding aria-hidden="true" (on the motion container or the icon) so screen
readers ignore them.


<div className="container mx-auto px-4 relative z-10">
<motion.div
variants={containerVariants}
initial="hidden"
animate="visible"
className="max-w-4xl mx-auto text-center"
>
<motion.h1
variants={itemVariants}
className="text-4xl md:text-6xl lg:text-7xl font-bold mb-6"
>
Welcome to{' '}
<motion.span
className="text-green-700 inline-block"
whileHover={{ scale: 1.05 }}
transition={{ type: "spring", stiffness: 300 }}
>
VigyBag!
</motion.span>
</motion.h1>

<motion.div
variants={itemVariants}
className="h-20 mb-8 flex items-center justify-center"
>
<motion.h2
key={currentText}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
className="text-2xl md:text-4xl lg:text-5xl font-semibold text-gray-800"
>
{heroTexts[currentText]}
</motion.h2>
</motion.div>

<motion.p
variants={itemVariants}
className="text-lg md:text-xl lg:text-2xl text-gray-700 mb-10 max-w-3xl mx-auto leading-relaxed"
>
At VigyBag, we curate the finest earth-friendly essentials to help you reduce your environmental footprint without compromising on quality or style.
</motion.p>

<motion.div
variants={itemVariants}
className="flex flex-col sm:flex-row gap-4 justify-center items-center"
>
<motion.button
onClick={onShopNowClick}
className="bg-gradient-to-r from-green-600 to-green-700 text-white px-8 py-4 rounded-full text-lg font-semibold shadow-lg"
whileHover={{
scale: 1.05,
boxShadow: "0 10px 25px rgba(0,0,0,0.2)"
}}
whileTap={{ scale: 0.95 }}
transition={{ type: "spring", stiffness: 300 }}
>
Shop Now
</motion.button>

<motion.button
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>
</motion.div>

<motion.div
variants={itemVariants}
className="grid grid-cols-3 gap-8 mt-16 max-w-2xl mx-auto"
>
<div className="text-center">
<motion.div
className="text-3xl font-bold text-green-700"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ delay: 1, type: "spring" }}
>
1000+
</motion.div>
<div className="text-gray-600">Eco Products</div>
</div>
<div className="text-center">
<motion.div
className="text-3xl font-bold text-green-700"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ delay: 1.2, type: "spring" }}
>
50K+
</motion.div>
<div className="text-gray-600">Happy Customers</div>
</div>
<div className="text-center">
<motion.div
className="text-3xl font-bold text-green-700"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ delay: 1.4, type: "spring" }}
>
100%
</motion.div>
<div className="text-gray-600">Sustainable</div>
</div>
</motion.div>
</motion.div>
</div>
</section>
);
};

export default EnhancedHero;
Loading