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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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