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
10 changes: 9 additions & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,12 @@ RAZORPAY_KEY_SECRET=your_razorpay_secret
# GEMINI KEY (Optional)
# ===========================================
# Get these from google ai studio
GEMINI_API_KEY=YOUR_GEMINI_API_KEY
GEMINI_API_KEY=YOUR_GEMINI_API_KEY

# ===========================================
# Email / OTP (Required for signup)
# ===========================================
# Use a Gmail account with an App Password:
# https://myaccount.google.com/apppasswords
EMAIL_USER=your_gmail@gmail.com
EMAIL_PASS=your_app_password
34 changes: 26 additions & 8 deletions backend/config/sendEmail.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,41 @@ import dotenv from "dotenv";

dotenv.config();

const emailConfigured =
Boolean(process.env.EMAIL_USER) && Boolean(process.env.EMAIL_PASS);

const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
connectionTimeout: 10000,
greetingTimeout: 10000,
socketTimeout: 15000,
});

transporter.verify((err) => {
if (err) {
console.log("SMTP ERROR =>", err);
} else {
console.log("SMTP SERVER READY");
}
});
if (!emailConfigured) {
console.error(
"EMAIL_USER and EMAIL_PASS must be set for OTP email delivery.",
);
} else {
transporter.verify((err) => {
if (err) {
console.log("SMTP ERROR =>", err);
} else {
console.log("SMTP SERVER READY");
}
});
}

export const isEmailConfigured = () => emailConfigured;

export const sendMail = async (email, htmlContent) => {
if (!emailConfigured) {
throw new Error("Email service is not configured");
}

try {
const info = await transporter.sendMail({
from: `"RIVETO" <${process.env.EMAIL_USER}>`,
Expand All @@ -33,4 +51,4 @@ export const sendMail = async (email, htmlContent) => {
console.error("Error sending email");
throw new Error("Email could not be sent");
}
};
};
12 changes: 11 additions & 1 deletion backend/controller/authcontroller.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import User from "../model/userModel.js";
import validator from "validator";
import bcrypt from "bcryptjs";
import { genToken, genToken1 } from "../config/Token.js";
import { sendMail } from "../config/sendEmail.js";
import { sendMail, isEmailConfigured } from "../config/sendEmail.js";
import generateOTP from "../utils/otp.js";
import TempUser from "../model/tempUserModel.js";
import { otpTemplate } from "../utils/otpTemplet.js";
Expand Down Expand Up @@ -40,6 +40,16 @@ export const sendOTP = async (req, res) => {
otp,
otpExpire: new Date(Date.now() + 5 * 60 * 1000),
});

if (!isEmailConfigured()) {
await TempUser.deleteOne({ email });

return res.status(503).json({
success: false,
message: "Email service is not configured",
});
}

try {
await sendMail(email, otpTemplate(otp));
} catch (_error) {
Expand Down
1 change: 1 addition & 0 deletions frontend/public/_redirects
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/* /index.html 200
1 change: 1 addition & 0 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ function App() {

{/* Public routes - Legal pages should be accessible without login */}
<Route path="/privacypolicy" element={<PrivacyPolicy />} />
<Route path="/privacy" element={<Navigate to="/privacypolicy" replace />} />
<Route path="/privicypolicy" element={<Navigate to="/privacypolicy" replace />} />
<Route path="/terms" element={<TermsAndServices />} />
<Route path="/termsandservices" element={<TermsAndServices />} />
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/LandingPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -1121,7 +1121,7 @@ function LandingPage() {
</h3>
<ul className="space-y-2">
{[
{ label: 'Privacy Policy', path: '/privacy' },
{ label: 'Privacy Policy', path: '/privacypolicy' },
{ label: 'Terms of Service', path: '/terms' },
{ label: 'Cookie Policy', path: '/cookie-policy' },
{ label: 'Size Guide', path: '/size-guide' },
Expand All @@ -1147,7 +1147,7 @@ function LandingPage() {
<div className="flex items-center gap-6">
<button
type="button"
onClick={() => navigate('/privacy')}
onClick={() => navigate('/privacypolicy')}
className="hover:text-[#2563EB] transition-colors"
>
Privacy
Expand Down
30 changes: 24 additions & 6 deletions frontend/src/pages/Registration.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
function Registration() {
const [show, setShow] = useState(false);
const [loading, setLoading] = useState(false);
const [submitError, setSubmitError] = useState('');
const [step, setStep] = useState('1');
const [otp, setOtp] = useState('');
const [otpLoading, setOtpLoading] = useState(false);
Expand Down Expand Up @@ -65,8 +66,8 @@ function Registration() {

if (!formData.name.trim()) {
newErrors.name = 'Name is required';
} else if (formData.name.length < 2) {
newErrors.name = 'Name must be at least 2 characters';
} else if (formData.name.trim().length < 3) {
newErrors.name = 'Name must be at least 3 characters';
}

if (!formData.email.trim()) {
Expand Down Expand Up @@ -106,14 +107,23 @@ function Registration() {
if (!validateForm()) return;

setLoading(true);
setSubmitError('');

try {
await apiConfig.post('/auth/send-otp', formData);
await apiConfig.post('/auth/send-otp', formData, {
skipAuthRedirect: true,
});

toast.success('OTP sent successfully 🎉');
setStep('2');
} catch {
// API errors are shown by the global interceptor.
} catch (error) {
if (error.code === 'ECONNABORTED') {
setSubmitError('Request timed out. Please check your connection and try again.');
} else if (error.response?.data?.message) {
setSubmitError(error.response.data.message);
} else {
setSubmitError('Unable to send OTP. Please try again.');
}
} finally {
setLoading(false);
}
Expand All @@ -126,6 +136,8 @@ function Registration() {
await apiConfig.post('/auth/verify-otp', {
email: formData.email,
otp,
}, {
skipAuthRedirect: true,
});

toast.success('Account verified successfully 🎉');
Expand Down Expand Up @@ -317,7 +329,7 @@ function Registration() {
</Link>{' '}
and{' '}
<Link
to="/privacy"
to="/privacypolicy"
className="text-cyan-400 hover:underline focus:outline-none focus:ring-1 focus:ring-cyan-400 rounded"
>
Privacy Policy
Expand All @@ -340,6 +352,12 @@ function Registration() {
'Create Account'
)}
</button>

{submitError && (
<p className="form-element text-red-400 text-sm text-center">
{submitError}
</p>
)}
</form>

{/* Login Link */}
Expand Down
1 change: 1 addition & 0 deletions frontend/src/utils/apiConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const MESSAGE_MAP = {
const apiConfig = axios.create({
baseURL: `${serverURL}/api`,
withCredentials: true,
timeout: 30000,
});

apiConfig.interceptors.response.use(
Expand Down