Skip to content

Commit

Permalink
add feat/contactus-page branch
Browse files Browse the repository at this point in the history
  • Loading branch information
PriyanshuValiya committed Oct 24, 2024
1 parent 5627c2f commit b563fe2
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 37 deletions.
74 changes: 74 additions & 0 deletions backend/controller/contact.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
const { z } = require("zod");
const nodemailer = require("nodemailer");
require("dotenv").config();

// data require form .env file : EMAIL_USER, EMAIL_PASS

// 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: validation.error.errors,
});
}

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,
},
tls: {
rejectUnauthorized: false, // Disable strict SSL verification
},
});

const mailOptions = {
from: 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 };
7 changes: 7 additions & 0 deletions backend/routes/contactUsRouter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const express = require("express");
const router = express.Router();
const { createContactUs } = require("../controller/contact.controller");

router.post("/contactus", createContactUs);

module.exports = router;
60 changes: 24 additions & 36 deletions backend/routes/index.js
Original file line number Diff line number Diff line change
@@ -1,57 +1,45 @@
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({
message: "Welcome to the restaurant API!",
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;
185 changes: 185 additions & 0 deletions frontend/src/components/Pages/ContactUs.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
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;
}

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: ${response.status}`);
}

setSubmitted(true);
setTimeout(() => {
setMail('');
setSubject('');
setMessage('');
setSubmitted(false);
}, 4000);
} catch (error) {
setError('An error occurred while sending Mail...');
console.error('Mail sending failed : ', error);
} finally {
setIsLoading(false);
}
};

return (
<div className="bg-amber-100 h-full py-24 px-4 sm:px-6 lg:px-8">
<div className="max-w-7xl mx-auto">
<motion.div
ref={ref}
initial="hidden"
animate={inView ? 'visible' : 'hidden'}
variants={animationVariants}
className="lg:grid lg:grid-cols-2 lg:gap-8 lg:items-center"
>
<div className="mt-8 mb-8 lg:mb-0 relative">
<h2 className="text-5xl font-black text-[#004D43]">
Feel Free To Mail Us..
</h2>
<p className="mt-5 text-lg text-gray-700 pb-3">
Have questions or need assistance ? Reach out to us, and we'll be
happy to help !!
</p>
<div className="flex md:h-[40vh] md:w-[60vh] ml-20 mt-20 items-center justify-center mt-12">
<img
src={chess}
alt="Chess"
loading="lazy"
className="md:p-10 p-5 object-contain bg-[#004D43] rounded-full shadow-2xl"
/>
</div>
</div>

<div className="bg-[#004D43] rounded-xl p-3 pt-4 mt-40 h-fit">
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="mail" className="sr-only">
Email Address
</label>
<input
type="email"
id="mail"
value={mail}
placeholder="Email ID"
aria-label="Email Address"
onChange={(e) => 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]"
/>
</div>
<div>
<input
type="text"
id="text"
placeholder="Subject"
value={subject}
onChange={(e) => 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]"
/>
</div>
<div>
<textarea
id="message"
placeholder="Write your message..."
rows="6"
value={message}
onChange={(e) => setMessage(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] resize-none"
></textarea>
</div>
<div>
<button
type="submit"
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-[#09342e] hover:bg-[#072d28] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[#004D43]"
disabled={isLoading}
>
{isLoading ? 'Sending...' : 'Send Mail'}
</button>
</div>
</form>
{submitted && (
<motion.div
initial={{ opacity: 0, y: -10, display: 'none', height: 0 }}
animate={{ opacity: 1, y: 0, display: 'block', height: 'auto' }}
className="mt-4 p-4 bg-green-100 border border-green-400 text-green-700 rounded"
>
Thank you, We will reply you soon...
</motion.div>
)}
{error && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
className="mt-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded"
>
{error}
</motion.div>
)}
</div>
</motion.div>
</div>
</div>
);
};

export default ContactUs;
1 change: 1 addition & 0 deletions frontend/src/components/Shared/Navbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/router/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ 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(
<Route path="/" element={<App />}>
Expand All @@ -41,7 +43,7 @@ const router = createBrowserRouter(
<Route path="/email-verify" element={<EmailVerify />} />
<Route path="/membership" element={<Membership />} />
<Route path="/help" element={<HelpAndSupport />} />

<Route path="/contactus" element={<ContactUs />} />
</Route>
)
);
Expand Down

0 comments on commit b563fe2

Please sign in to comment.