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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 154 additions & 53 deletions components/Contact/ContactForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,76 @@ function ContactForm() {

const handleSubmit = (e) => {
e.preventDefault();

// Frontend validation before sending
if (!formData.name.trim() || formData.name.trim().length < 2) {
toast.error("Name must be at least 2 characters long.", {
position: "top-center",
autoClose: 5000,
theme: "colored"
});
return;
}

if (!formData.email.trim()) {
toast.error("Email is required.", {
position: "top-center",
autoClose: 5000,
theme: "colored"
});
return;
}

if (emailError) {
toast.error("Please enter a valid email address.", {
position: "top-center",
autoClose: 5000,
theme: "colored"
});
return;
}

if (!formData.message.trim() || formData.message.trim().length < 10) {
toast.error("Message must be at least 10 characters long.", {
position: "top-center",
autoClose: 5000,
theme: "colored"
});
return;
}

if (formData.message.trim().length > 2000) {
toast.error("Message must be less than 2000 characters.", {
position: "top-center",
autoClose: 5000,
theme: "colored"
});
return;
}

setIsSubmitting(true);

// Log the data being sent for debugging
console.log("📤 Sending form data:", {
name: formData.name.trim(),
email: formData.email.trim(),
message: formData.message.trim(),
lengths: {
name: formData.name.trim().length,
email: formData.email.trim().length,
message: formData.message.trim().length
}
});

axios
.post("/api/v1/contact", formData)
.post("/api/v1/contact", {
name: formData.name.trim(),
email: formData.email.trim(),
message: formData.message.trim()
})
.then((response) => {
console.log("Response:", response.data);
toast.success("Your form has been submitted successfully!", {
console.log("Response:", response.data);
toast.success(response.data.message || "Your form has been submitted successfully!", {
position: "top-center",
autoClose: 5000,
hideProgressBar: false,
Expand All @@ -56,25 +119,39 @@ function ContactForm() {
})
.catch((error) => {
console.error("Error:", error);
toast.error(
"There was an error submitting your form. Please try again.",
{
position: "top-center",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "colored"
}
);

// Get specific error message from API response
let errorMessage = "There was an error submitting your form. Please try again.";

if (error.response && error.response.data) {
errorMessage = error.response.data.message || errorMessage;
console.log("API Error Details:", error.response.data);
}

toast.error(errorMessage, {
position: "top-center",
autoClose: 7000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "colored"
});
setIsSubmitting(false);
});
};

const isFormValid =
formData.name && formData.email && formData.message && !emailError;
const isFormValid = () => {
return (
formData.name.trim().length >= 2 &&
formData.name.trim().length <= 100 &&
formData.email.trim() &&
!emailError &&
formData.message.trim().length >= 10 &&
formData.message.trim().length <= 2000
);
};

return (
<>
Expand All @@ -94,44 +171,68 @@ function ContactForm() {
>
Send Us Your Queries
</p>
<input
required
type="text"
name="name"
placeholder="Name"
value={formData.name}
onChange={handleChange}
className="border-b-2 border-gray text-white font-dmSans bg-bg_black outline-none w-[90%] sm:w-[70%] my-8 mx-auto mt-10"
/>
<input
required
type="email"
name="email"
placeholder="Email"
value={formData.email}
onChange={handleChange}
className="border-b-2 border-gray text-white font-dmSans bg-bg_black outline-none w-[90%] sm:w-[70%] my-8 mx-auto"
/>
{emailError && (
<p className="text-red-500 mx-auto">{emailError}</p>
)}
<input
required
type="text"
name="message"
placeholder="Enter Your Query"
value={formData.message}
onChange={handleChange}
className="border-b-2 border-gray text-white font-dmSans bg-bg_black outline-none w-[90%] sm:w-[70%] my-8 mx-auto"
/>
<div className="w-[90%] sm:w-[70%] mx-auto">
<input
required
type="text"
name="name"
placeholder="Name"
value={formData.name}
onChange={handleChange}
className={`border-b-2 ${formData.name.length > 0 && formData.name.length < 2 ? 'border-red-500' : 'border-gray'} text-white font-dmSans bg-bg_black outline-none w-full my-8 mt-10`}
/>
{formData.name.length > 0 && formData.name.length < 2 && (
<p className="text-red-500 text-sm -mt-6 mb-4">Name must be at least 2 characters</p>
)}
<div className="text-right text-gray-400 text-xs -mt-6 mb-2">
{formData.name.length}/100
</div>
</div>

<div className="w-[90%] sm:w-[70%] mx-auto">
<input
required
type="email"
name="email"
placeholder="Email"
value={formData.email}
onChange={handleChange}
className={`border-b-2 ${emailError ? 'border-red-500' : 'border-gray'} text-white font-dmSans bg-bg_black outline-none w-full my-8`}
/>
{emailError && (
<p className="text-red-500 text-sm -mt-6 mb-4">{emailError}</p>
)}
</div>

<div className="w-[90%] sm:w-[70%] mx-auto">
<textarea
required
name="message"
placeholder="Enter Your Query (minimum 10 characters)"
value={formData.message}
onChange={handleChange}
rows={4}
className={`border-b-2 ${formData.message.length > 0 && formData.message.length < 10 ? 'border-red-500' : 'border-gray'} text-white font-dmSans bg-bg_black outline-none w-full my-8 resize-none`}
/>
{formData.message.length > 0 && formData.message.length < 10 && (
<p className="text-red-500 text-sm -mt-6 mb-2">Message must be at least 10 characters</p>
)}
{formData.message.length > 2000 && (
<p className="text-red-500 text-sm -mt-6 mb-2">Message must be less than 2000 characters</p>
)}
<div className="text-right text-gray-400 text-xs -mt-6 mb-4">
Copy link

Copilot AI Aug 15, 2025

Choose a reason for hiding this comment

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

The textarea element is missing accessibility attributes like 'aria-label' or 'aria-describedby' to properly describe the input field for screen readers, especially since it has validation requirements.

Copilot uses AI. Check for mistakes.
<span className={formData.message.length > 2000 ? 'text-red-500' : ''}>
{formData.message.length}/2000
</span>
</div>
</div>
<button
onClick={handleSubmit}
disabled={!isFormValid || isSubmitting}
className={`text-black bg-bright_green font-dmSans font-bold text-md md:text-lg rounded-full py-3 md:py-4 px-4 w-[40%] my-6 mx-auto ${
!isFormValid || isSubmitting
disabled={!isFormValid() || isSubmitting}
className={`text-black bg-bright_green font-dmSans font-bold text-md md:text-lg rounded-full py-3 md:py-4 px-4 w-[40%] my-6 mx-auto transition-opacity ${!isFormValid() || isSubmitting
? "opacity-50 cursor-not-allowed"
: ""
}`}
: "hover:bg-opacity-90"
}`}
>
{isSubmitting ? "Submitting..." : "Submit"}
</button>
Expand Down
12 changes: 8 additions & 4 deletions components/Home/Gallery.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import img8 from "../../public/Home/8.JPG";
import img9 from "../../public/Home/9.JPG";
import img10 from "../../public/Home/10.JPG";
import img11 from "../../public/Home/11.jpg";
import img12 from "../../public/Home/12.JPG";
import img13 from "../../public/Home/13.JPG";

const serviceData = [
img1,
Expand All @@ -32,7 +34,9 @@ const serviceData = [
img8,
img9,
img10,
img11
img11,
img12,
img13
];

const ServiceSlider = () => {
Expand Down Expand Up @@ -83,14 +87,14 @@ const ServiceSlider = () => {
// slideShadows: false
// }}
pagination={{
dynamicBullets: true
clickable: true
}}
autoplay={{
delay: 2000,
disableOnInteraction: true
pauseOnHover: true
}}
modules={[Autoplay, Pagination]}
className="overflow-hidden"
className="overflow-hidden pb-12"
>
{serviceData.map((item, index) => (
<SwiperSlide key={index}>
Expand Down
62 changes: 48 additions & 14 deletions components/Home/Sponsors.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,62 @@ import { useEffect, useState } from "react";

const Sponsors = () => {
const [sponsors, setSponsors] = useState([]);
const [loading, setLoading] = useState(true);

useEffect(() => {
const fetchSponsors = async () => {
try {
setLoading(true);
const response = await fetch("../api/v1/sponsers");
const result = await response.json();
const result = await response.json();
console.log("hello")
setSponsors(result.data);
} catch (error) {
console.error("Error fetching sponsors:", error);
} finally {
setLoading(false);
}
};

fetchSponsors();
}, []);

// Skeleton component for loading state
const SponsorSkeleton = () => (
<div className="border-gradient rounded-xl">
<div className="bg-bg_black p-4 rounded-xl shadow-md flex justify-center items-center h-36 w-36 lg:w-72 lg:h-64">
<div className="animate-pulse bg-gray-600 rounded-md w-24 h-12 lg:w-48 lg:h-24"></div>
</div>
</div>
);

return (
<div className="py-12 text-center mb-10">
<h2 className="text-4xl text-white mb-10 font-bold font-poppins">Sponsors</h2>
<div className="flex flex-wrap justify-center items-center gap-10 rounded-xl">
{sponsors.map((sponsor, index) => (
<div
key={index}
className="border-gradient rounded-xl transition-transform duration-300 hover:drop-shadow-glow"
>
{loading ? (
// Show 6 skeleton components while loading
Array.from({ length: 6 }, (_, index) => (
<SponsorSkeleton key={`skeleton-${index}`} />
))
) : (
sponsors.map((sponsor, index) => (
<div
className="bg-bg_black p-4 rounded-xl shadow-md flex justify-center items-center h-36 w-36 lg:w-72 lg:h-64"
key={index}
className="border-gradient rounded-xl transition-transform duration-300 hover:drop-shadow-glow"
>
<img
src={sponsor.logo}
alt={`${sponsor.name} Logo`}
className="object-contain max-w-full max-h-full"
/>
<div
className="bg-bg_black p-4 rounded-xl shadow-md flex justify-center items-center h-36 w-36 lg:w-72 lg:h-64"
>
<img
src={sponsor.logo}
alt={`${sponsor.name} Logo`}
className="object-contain max-w-full max-h-full"
/>
</div>
</div>
</div>
))}
))
)}
</div>

<style jsx>{`
Expand All @@ -56,6 +77,19 @@ const Sponsors = () => {
filter: drop-shadow(0 0 20px rgba(13, 255, 78, 1)); /* Stronger glow on hover */
transition: filter 0.3s ease-in-out; /* Smooth transition on hover */
}

@keyframes skeleton-pulse {
0%, 100% {
opacity: 0.6;
}
50% {
opacity: 0.3;
}
}

.animate-pulse {
animation: skeleton-pulse 2s ease-in-out infinite;
}
`}</style>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion components/Shared/Scroll.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const ScrollToTopButton = () => {
}, []);

return (
<div className="fixed bottom-20 right-16 max-md:right-4 max-md:bottom-4">
<div className="fixed bottom-20 right-16 max-md:right-4 max-md:bottom-4 z-50">
{isVisible && (
<button
onClick={scrollToTop}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"mongoose": "^8.2.1",
"next": "^14.1.0",
"next-sitemap": "^4.2.3",
"node-mailer": "^0.1.1",
"react": "^18",
"react-confetti": "^6.1.0",
"react-dom": "^18",
Expand Down
Loading