diff --git a/backend/controller/contact.controller.js b/backend/controller/contact.controller.js new file mode 100644 index 00000000..c1636909 --- /dev/null +++ b/backend/controller/contact.controller.js @@ -0,0 +1,77 @@ +const { z } = require("zod"); +const nodemailer = require("nodemailer"); +require("dotenv").config(); + +// Define the Zod schema for contact form validation +const contactSchema = z.object({ + mail: z.string().email(), + subject: z.string().min(5, "Subject must be at least 5 characters long."), + message: z.string().min(5, "Message must be at least 5 characters long."), +}); + +const createContactUs = async (req, res) => { + const validation = contactSchema.safeParse(req.body); + + if (!validation.success) { + console.error("Error at validation"); + return res.status(400).json({ + status: "error", + errors: "contactSchema is not validate", + }); + } + + const { mail, subject, message } = req.body; + + try { + const transporter = nodemailer.createTransport({ + service: "gmail", + host: "smtp.gmail.com", + port: 587, + secure: false, + auth: { + user: process.env.EMAIL_USER, + pass: process.env.EMAIL_PASS, + }, + // Uncomment this if needed + // tls: { + // rejectUnauthorized: false, // Disable strict SSL verification + // }, + }); + + const mailOptions = { + // from: mail, + from: process.env.EMAIL_USER, ++ replyTo: mail, + to: process.env.EMAIL_USER, + subject: subject, + text: message, + }; + + // Send mail with defined transport object + + + await new Promise((resolve, reject) => { ++ transporter.sendMail(mailOptions, (error, info) => { ++ if (error) { ++ console.error("Error occurred: " + error.message); ++ reject(error); ++ } ++ resolve(info); ++ }); ++ }); ++ ++ res.status(200).json({ ++ status: "success", ++ message: "Your contact request has been successfully received.", ++ }); + + } catch (err) { + console.error(`Error at transport: ${err}`); + res.status(500).json({ + status: "error", + message: + "There was an error sending your message. Please try again later.", + }); + } +}; + +module.exports = { createContactUs }; diff --git a/backend/index.js b/backend/index.js index 1bb24c71..a17700f1 100644 --- a/backend/index.js +++ b/backend/index.js @@ -3,7 +3,9 @@ require("dotenv").config(); const cors = require("cors"); const mongoose = require("mongoose"); const logger = require("./config/logger"); -const errorMiddleware = require("./middlewares/errrorMiddleware"); // Corrected typo + +const errorMiddleware = require("../backend/middlewares/errorMiddleware"); // Corrected typo + const passport = require("passport"); const { handleGoogleOAuth } = require("./controller/googleOAuth.controller"); const app = express(); diff --git a/backend/routes/contactUsRouter.js b/backend/routes/contactUsRouter.js new file mode 100644 index 00000000..9beea1e2 --- /dev/null +++ b/backend/routes/contactUsRouter.js @@ -0,0 +1,7 @@ +const express = require("express"); +const router = express.Router(); +const { createContactUs } = require("../controller/contact.controller"); // Correct controller path + +router.post("/contactus", createContactUs); + +module.exports = router; diff --git a/backend/routes/index.js b/backend/routes/index.js index 27a2b8bd..096551dd 100644 --- a/backend/routes/index.js +++ b/backend/routes/index.js @@ -1,39 +1,25 @@ const express = require("express"); -const logger = require("../config/logger"); // Import your Winston logger +const logger = require("../config/logger"); // Import Winston logger require("dotenv").config(); -const config = { - JWT_SECRET: process.env.JWT_SECRET, - GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID, - GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET, -}; - const router = express.Router(); -let feedbackRouter; - -try { - feedbackRouter = require("./feedbackRouter"); -} catch (error) { - logger.error("Error loading feedbackRouter:", error); // Log the error with Winston - feedbackRouter = (req, res) => { - res - .status(500) - .json({ error: "Feedback functionality is currently unavailable" }); - }; -} +// Utility function to safely load modules and handle errors +const safeRequire = (modulePath, fallbackMessage) => { + try { + return require(modulePath); + } catch (error) { + logger.error(`Error loading ${modulePath}:`, error); + return (req, res) => { + res.status(500).json({ error: fallbackMessage }); + }; + } +}; -let eventRouter; -try { - eventRouter = require("./eventRouter"); -} catch (error) { - logger.error("Error loading eventRouter:", error); // Log the error with Winston - eventRouter = (req, res) => { - res - .status(500) - .json({ error: "Event functionality is currently unavailable" }); - }; -} +// Safely load routers with error handling +const feedbackRouter = safeRequire("./feedbackRouter", "Feedback functionality is currently unavailable"); +const contactUsRouter = safeRequire("./contactUsRouter", "Contact Us functionality is currently unavailable"); +const eventRouter = safeRequire("./eventRouter", "Event functionality is currently unavailable"); router.get("/", (req, res) => { return res.json({ @@ -41,17 +27,19 @@ router.get("/", (req, res) => { version: "1.0.0", endpoints: { Reservation: "/reservation", - Feedback: "/feedback", // Added feedback endpoint documentation + Feedback: "/feedback", }, documentation: "https://api-docs-url.com", }); }); router.use("/event", eventRouter); -router.use("/admin", require("./adminRouter")); +router.use("/admin", safeRequire("./adminRouter", "Admin functionality is currently unavailable")); router.use("/feedback", feedbackRouter); -router.use("/user", require("./customerRouter")); -router.use("/reservation", require("./reservationRouter")); -router.use("/newsletter", require("./newsletterRoute")); -router.use("/forgot", require("./forgotRouter")); +router.use("/user", safeRequire("./customerRouter", "User functionality is currently unavailable")); +router.use("/reservation", safeRequire("./reservationRouter", "Reservation functionality is currently unavailable")); +router.use("/newsletter", safeRequire("./newsletterRoute", "Newsletter functionality is currently unavailable")); +router.use("/forgot", safeRequire("./forgotRouter", "Forgot password functionality is currently unavailable")); +router.use("/contact", contactUsRouter); + module.exports = router; diff --git a/frontend/package.json b/frontend/package.json index e2a55a73..e1417841 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,6 +24,7 @@ "framer-motion": "^11.5.6", "gsap": "^3.12.5", "js-cookie": "^3.0.5", + "lucide-react": "^0.453.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-icons": "^5.2.1", diff --git a/frontend/src/components/Pages/ContactUs.jsx b/frontend/src/components/Pages/ContactUs.jsx new file mode 100644 index 00000000..5866f466 --- /dev/null +++ b/frontend/src/components/Pages/ContactUs.jsx @@ -0,0 +1,182 @@ +/* eslint-disable prettier/prettier */ +/* eslint-disable no-unused-vars */ +import { useState } from 'react'; +import { motion } from 'framer-motion'; +import { useInView } from 'react-intersection-observer'; +import chess from '../../assets/img/chess.gif'; + +const ContactUs = () => { + const { ref, inView } = useInView({ + threshold: 0.2, + triggerOnce: true, + }); + + const animationVariants = { + hidden: { opacity: 0, y: 50 }, + visible: { opacity: 1, y: 0, transition: { duration: 0.5 } }, + }; + + // Use an environment variable for backend URL + const API_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:3000'; + const [mail, setMail] = useState(''); + const [subject, setSubject] = useState(''); + const [message, setMessage] = useState(''); + const [submitted, setSubmitted] = useState(false); + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(false); + + const handleSubmit = async (e) => { + e.preventDefault(); + + // Basic client-side validation for security + + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; ++ if (!mail || !subject || !message) { ++ setError('All fields are required.'); ++ return; ++ } ++ if (!emailRegex.test(mail)) { ++ setError('Please enter a valid email address.'); ++ return; ++ } ++ if (subject.length > 100) { ++ setError('Subject must be less than 100 characters.'); ++ return; ++ } ++ if (message.length > 1000) { ++ setError('Message must be less than 1000 characters.'); ++ return; ++ } + + // Clear any previous errors + setError(null); + + setIsLoading(true); + try { + const response = await fetch(`${API_URL}/api/contact/contactus`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ mail, subject, message }), + }); ++ if (!response.ok) { ++ throw new Error('Network response was not ok'); ++ } + setSubmitted(true); + } + setSubmitted(true); + setTimeout(() => { + setMail(''); + setSubject(''); + setMessage(''); + setSubmitted(false); + }, 3000); + } catch (error) { + setError('An error occurred while sending Mail...'); + console.error('Mail sending failed : ', error); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+ +
+

+ Feel Free To Mail Us.. +

+

+ Have questions or need assistance ? Reach out to us, and we'll be + happy to help !! +

+
+ Chess +
+
+ +
+
+
++ + setMail(e.target.value)} + required + className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-[#004D43] focus:border-[#004D43]" + /> +
+
+ setSubject(e.target.value)} + required + className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-[#004D43] focus:border-[#004D43]" + /> +
+
+ +
+
+ +
+
+ {submitted && ( + + Thank you, We will reply you soon... + + )} + {error && ( + + {error} + + )} +
+
+
+
+ ); +}; + +export default ContactUs; diff --git a/frontend/src/components/Shared/Navbar.jsx b/frontend/src/components/Shared/Navbar.jsx index 260a1386..da3c47b0 100644 --- a/frontend/src/components/Shared/Navbar.jsx +++ b/frontend/src/components/Shared/Navbar.jsx @@ -20,6 +20,7 @@ const Navbar = () => { { name: 'RESERVATION', path: '/reservation' }, { name: 'BOARDGAMES', path: '/boardgame' }, { name: 'MEMBERSHIP', path: '/membership' }, // Add Membership here + { name: 'CONTACTUS', path: '/contactus'} ]; useEffect(() => { diff --git a/frontend/src/router/index.jsx b/frontend/src/router/index.jsx index 964706ca..7a802aff 100644 --- a/frontend/src/router/index.jsx +++ b/frontend/src/router/index.jsx @@ -21,7 +21,9 @@ import Admin from '../components/Pages/Admin'; import VerifyOtp from '../components/Pages/VerifyOtp'; import EmailVerify from '../components/Pages/EmailVerify'; import Membership from '../components/Membership'; -import HelpAndSupport from '../components/Pages/HelpAndSupport'; + +import ContactUs from '../components/Pages/ContactUs'; + const router = createBrowserRouter( createRoutesFromElements( }> @@ -40,8 +42,7 @@ const router = createBrowserRouter( } /> } /> } /> - } /> - + } /> ) ); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..a0c27c60 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "PlayCafe", + "lockfileVersion": 3, + "requires": true, + "packages": {} +}