Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Email is sent on successful reservation #121

Merged
merged 5 commits into from
Oct 8, 2024
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
40 changes: 28 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,26 +126,26 @@ Special thanks to our amazing mentors who are guiding this project! 🙌
</a>
</td>
<td align="center">
<a href="https://github.com/sajalbatra">
<img src="https://avatars.githubusercontent.com/u/125984550?v=4" width="100;" alt="sajalbatra"/>
<a href="https://github.com/vishnuprasad2004">
<img src="https://avatars.githubusercontent.com/u/116942066?v=4" width="100;" alt="vishnuprasad2004"/>
<br />
<sub><b>Sajal Batra</b></sub>
<sub><b>Vishnu Prasad Korada</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/vishnuprasad2004">
<img src="https://avatars.githubusercontent.com/u/116942066?v=4" width="100;" alt="vishnuprasad2004"/>
<a href="https://github.com/sajalbatra">
<img src="https://avatars.githubusercontent.com/u/125984550?v=4" width="100;" alt="sajalbatra"/>
<br />
<sub><b>Vishnu Prasad Korada</b></sub>
<sub><b>Sajal Batra</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Navneetdadhich">
<img src="https://avatars.githubusercontent.com/u/156535853?v=4" width="100;" alt="Navneetdadhich"/>
<a href="https://github.com/AbhijitMotekar99">
<img src="https://avatars.githubusercontent.com/u/109235675?v=4" width="100;" alt="AbhijitMotekar99"/>
<br />
<sub><b>Navneet Dadhich</b></sub>
<sub><b>Abhijit Motekar</b></sub>
</a>
</td>
<td align="center">
Expand All @@ -156,10 +156,10 @@ Special thanks to our amazing mentors who are guiding this project! 🙌
</a>
</td>
<td align="center">
<a href="https://github.com/AbhijitMotekar99">
<img src="https://avatars.githubusercontent.com/u/109235675?v=4" width="100;" alt="AbhijitMotekar99"/>
<a href="https://github.com/Navneetdadhich">
<img src="https://avatars.githubusercontent.com/u/156535853?v=4" width="100;" alt="Navneetdadhich"/>
<br />
<sub><b>Abhijit Motekar</b></sub>
<sub><b>Navneet Dadhich</b></sub>
</a>
</td>
<td align="center">
Expand All @@ -185,6 +185,13 @@ Special thanks to our amazing mentors who are guiding this project! 🙌
<sub><b>Jai Dhingra</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Mohitranag18">
<img src="https://avatars.githubusercontent.com/u/152625405?v=4" width="100;" alt="Mohitranag18"/>
<br />
<sub><b>Mohit Rana </b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/MutiatBash">
<img src="https://avatars.githubusercontent.com/u/108807732?v=4" width="100;" alt="MutiatBash"/>
Expand All @@ -199,13 +206,22 @@ Special thanks to our amazing mentors who are guiding this project! 🙌
<sub><b>Sapna Kul</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Shiva-Bajpai">
<img src="https://avatars.githubusercontent.com/u/141490705?v=4" width="100;" alt="Shiva-Bajpai"/>
<br />
<sub><b>Shiva Bajpai</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Syed-Farazuddin">
<img src="https://avatars.githubusercontent.com/u/119295880?v=4" width="100;" alt="Syed-Farazuddin"/>
<br />
<sub><b>Syed Faraz</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/Vaibhav-Kumar-K-R">
<img src="https://avatars.githubusercontent.com/u/132189791?v=4" width="100;" alt="Vaibhav-Kumar-K-R"/>
Expand Down
4 changes: 3 additions & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
MONGO_URI=enter_your_mongo_uri
MONGO_URI=enter_your_mongo_uri
EMAIL_USER=your_gmail
EMAIL_PASS=your_16_digit_pass
50 changes: 50 additions & 0 deletions backend/config/nodemailer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
require("dotenv").config();
const nodemailer = require("nodemailer");
const logger = require('./logger');

// Create a Nodemailer transporter using SMTP
const transporter = nodemailer.createTransport({
service: "gmail", // or your preferred email service
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});

// Function to send reservation confirmation via email
exports.sendReservationConfirmation = async (email, reservationDetails) => {
const { reservationDate, guests, time } = reservationDetails;

// Construct the email content
const emailText = `
Dear Customer,

We are pleased to confirm your reservation. Here are the details of your reservation:

Reservation Date: ${reservationDate}
Number of Guests: ${guests}
Reservation Time: ${time}

Thank you for choosing our service. We look forward to hosting you.

Best regards,
PlayCafe
`;
Comment on lines +15 to +32
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

Consider internationalizing the email content.

The email content is currently hardcoded in English. For a potentially global user base, consider implementing internationalization (i18n) for the email content. This would allow for sending emails in the user's preferred language.

Example implementation:

const i18next = require('i18next');
// ... (i18next setup)

const emailText = i18next.t('reservationConfirmation', {
  reservationDate,
  guests,
  time
});


try {
await transporter.sendMail({
from: process.env.EMAIL_USER,
to: email,
subject: "Reservation Confirmation",
text: emailText,
});
logger.info('Reservation confirmation sent successfully via email', { email });
} catch (error) {
logger.error('Failed to send reservation confirmation email', { error, email });
if (error.code === 'ECONNREFUSED') {
throw new Error('Failed to connect to email server. Please try again later.');
} else {
throw new Error(`Failed to send reservation confirmation email: ${error.message}`);
}
}
};
22 changes: 19 additions & 3 deletions backend/controller/reservation.controller.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
const { z } = require("zod");
const Reservation = require("../models/reservation.model");
const logger = require("../config/logger"); // Import your logger
const logger = require("../config/logger");
const { sendReservationConfirmation } = require("../config/nodemailer"); // Import your email function

// Define the Zod schema for reservation validation
const reservationSchema = z.object({
guests: z.string(),
date: z.string(),
time: z.string(),
});
email: z.string().email(), // Include email validation in the schema
}).strict(); // Disallow unknown keys

async function createReservation(req, res) {
try {
Expand All @@ -25,8 +27,22 @@ async function createReservation(req, res) {
});
}

// Create the reservation in the database
const reservation = await Reservation.create(validationResult.data);
Comment on lines +30 to 31
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential security risk: Prevent extra unwanted fields in request data

The current schema may allow extra fields in req.body that are not defined in the schema, which could lead to overposting vulnerabilities. To mitigate this risk, consider using Zod's .strict() method to ensure only the defined fields are accepted.

Apply this diff to enforce strict schema validation:

-const reservationSchema = z.object({
+const reservationSchema = z.object({
   guests: z.string(),
   date: z.string(),
   time: z.string(),
   email: z.string().email(),
+}).strict(); // Disallow unknown keys

Committable suggestion was skipped due to low confidence.


// Send a confirmation email
try {
const { email, date, guests, time } = validationResult.data;
await sendReservationConfirmation(email, { reservationDate: date, guests, time });
logger.info(`Reservation confirmation email sent to ${email}`);
} catch (emailError) {
logger.error("Error sending reservation confirmation email:", {
message: emailError.message,
});
// Email error should not block the main reservation process, so no need to return a failure response
}

// Send the success response
res.status(201).json({
success: true,
message: "Reservation created successfully",
Expand All @@ -48,4 +64,4 @@ async function createReservation(req, res) {

module.exports = {
createReservation,
};
};
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"dotenv": "^16.4.5",
"express": "^4.21.0",
"mongoose": "^8.7.0",
"nodemailer": "^6.9.15",
"validator": "^13.12.0",
"winston": "^3.14.2",
"zod": "^3.23.8"
Expand Down
25 changes: 14 additions & 11 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,25 @@ import "./App.css";
import Navbar from "../src/components/Shared/Navbar";
import Footer from "../src/components/Shared/Footer";
import { Outlet } from "react-router-dom";

import { AuthProvider } from './components/Shared/AuthContext';
import { KindeProvider } from "@kinde-oss/kinde-auth-react";

function App() {
return (
<AuthProvider>
<KindeProvider
clientId={import.meta.env.VITE_KINDE_CLIENT_ID}
domain={import.meta.env.VITE_KINDE_DOMAIN}
redirectUri={import.meta.env.VITE_KINDE_REDIRECT_URI}
logoutUri={import.meta.env.VITE_KINDE_LOGOUT_REDIRECT_URI}
>
<Navbar />
<Outlet />
<Footer />
</KindeProvider>
clientId={import.meta.env.VITE_KINDE_CLIENT_ID}
domain={import.meta.env.VITE_KINDE_DOMAIN}
redirectUri={import.meta.env.VITE_KINDE_REDIRECT_URI}
logoutUri={import.meta.env.VITE_KINDE_LOGOUT_REDIRECT_URI}
>
<Navbar />
<Outlet />
<Footer />
</KindeProvider>
</AuthProvider>

);
}

export default App;
export default App;
6 changes: 5 additions & 1 deletion frontend/src/components/Pages/Register.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState } from "react";
import { useAuth } from "../Shared/AuthContext";
import pic from "../../assets/img/abt1.jpg";
import pic2 from "../../assets/img/abt1.png";
import pic3 from "../../assets/img/abt2.png";
Expand All @@ -9,6 +10,7 @@ export default function Register() {
const [date, setDate] = useState("");
const [time, setTime] = useState("");
const [guests, setGuests] = useState();
const { email } = useAuth();

const handleSubmit = (e) => {
console.log(guests);
Expand All @@ -22,11 +24,13 @@ export default function Register() {
"Content-Type": "application/json",
},
body: JSON.stringify({
email,
guests,
date,
time,
}),
})

.then((res) => res.json())
.then((data) => console.log(data))
.catch((error) => console.log(error));
Expand Down Expand Up @@ -175,4 +179,4 @@ export default function Register() {
</div>
</>
);
}
}
15 changes: 15 additions & 0 deletions frontend/src/components/Shared/AuthContext.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createContext, useState, useContext } from "react";

const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
const [email, setEmail] = useState(null); // Store user's email

return (
<AuthContext.Provider value={{ email, setEmail }}>
{children}
</AuthContext.Provider>
);
};

export const useAuth = () => useContext(AuthContext);
Loading
Loading