diff --git a/backend/auth-test-server.js b/backend/auth-test-server.js
new file mode 100644
index 00000000..8b01c049
--- /dev/null
+++ b/backend/auth-test-server.js
@@ -0,0 +1,160 @@
+import express from "express";
+import cors from "cors";
+import cookieParser from 'cookie-parser';
+import bcrypt from 'bcrypt';
+import jwt from 'jsonwebtoken';
+import "dotenv/config";
+
+const app = express();
+const port = 4000;
+
+// Middleware
+app.use(express.json());
+app.use(cookieParser());
+app.use(cors({
+ origin: ["http://localhost:5173", "http://localhost:3000"],
+ credentials: true
+}));
+
+// Simple in-memory user storage for testing (replace with DB later)
+let users = [];
+
+const generateToken = (id) => {
+ return jwt.sign({ id }, process.env.JWT_SECRET || "fallback-secret", { expiresIn: '7d' });
+};
+
+// Test registration endpoint
+app.post('/api/auth/register', async (req, res) => {
+ try {
+ const { name, email, password } = req.body;
+
+ console.log('๐ Registration attempt:', { name, email, passwordLength: password?.length });
+
+ // Validate required fields
+ if (!name || !email || !password) {
+ console.log('โ Missing required fields');
+ return res.status(400).json({ message: 'All fields are required' });
+ }
+
+ // Check if user already exists (in memory)
+ const userExists = users.find(user => user.email === email);
+ if (userExists) {
+ console.log('โ User already exists:', email);
+ return res.status(400).json({ message: 'User already exists' });
+ }
+
+ // Hash password
+ const hashedPassword = await bcrypt.hash(password, 10);
+
+ // Create user (in memory)
+ const newUser = {
+ _id: Date.now().toString(),
+ name,
+ email,
+ password: hashedPassword,
+ createdAt: new Date()
+ };
+
+ users.push(newUser);
+ console.log('โ
User created successfully:', email);
+
+ // Generate JWT
+ const token = generateToken(newUser._id);
+
+ // Set cookie
+ res.cookie('token', token, {
+ httpOnly: true,
+ sameSite: 'strict',
+ maxAge: 7 * 24 * 60 * 60 * 1000,
+ });
+
+ // Return success
+ res.status(201).json({
+ user: { _id: newUser._id, name: newUser.name, email: newUser.email },
+ token: token,
+ message: 'Registration successful',
+ });
+
+ } catch (error) {
+ console.error('๐ฅ Registration error:', error);
+ res.status(500).json({
+ message: 'Server error during registration',
+ error: error.message
+ });
+ }
+});
+
+// Test login endpoint
+app.post('/api/auth/login', async (req, res) => {
+ try {
+ const { email, password } = req.body;
+
+ console.log('๐ Login attempt:', { email });
+
+ if (!email || !password) {
+ return res.status(400).json({ message: 'Email and password are required' });
+ }
+
+ // Find user (in memory)
+ const user = users.find(u => u.email === email);
+ if (!user) {
+ console.log('โ User not found:', email);
+ return res.status(401).json({ message: 'Invalid email or password' });
+ }
+
+ // Compare password
+ const isMatch = await bcrypt.compare(password, user.password);
+ if (!isMatch) {
+ console.log('โ Invalid password for:', email);
+ return res.status(401).json({ message: 'Invalid email or password' });
+ }
+
+ // Generate JWT
+ const token = generateToken(user._id);
+
+ // Set cookie
+ res.cookie('token', token, {
+ httpOnly: true,
+ sameSite: 'strict',
+ maxAge: 7 * 24 * 60 * 60 * 1000,
+ });
+
+ console.log('โ
Login successful:', email);
+
+ res.json({
+ user: { _id: user._id, name: user.name, email: user.email },
+ token: token,
+ message: 'Login successful',
+ });
+
+ } catch (error) {
+ console.error('๐ฅ Login error:', error);
+ res.status(500).json({
+ message: 'Server error during login',
+ error: error.message
+ });
+ }
+});
+
+// Test endpoint
+app.get('/', (req, res) => {
+ res.json({
+ message: '๐ Test Auth Server is working!',
+ users: users.length,
+ timestamp: new Date().toISOString()
+ });
+});
+
+// Get all users (for testing)
+app.get('/api/users', (req, res) => {
+ res.json({
+ users: users.map(u => ({ id: u._id, name: u.name, email: u.email })),
+ count: users.length
+ });
+});
+
+app.listen(port, () => {
+ console.log(`๐งช Test server running on port ${port}`);
+ console.log(`๐ Test at: http://localhost:${port}`);
+ console.log(`๐ฅ Register at: http://localhost:${port}/api/auth/register`);
+});
\ No newline at end of file
diff --git a/backend/controllers/authController.js b/backend/controllers/authController.js
index 7209eaea..a70c629a 100644
--- a/backend/controllers/authController.js
+++ b/backend/controllers/authController.js
@@ -7,65 +7,114 @@ const generateToken = (id) => {
};
export const registerUser = async (req, res) => {
- const { name, email, password } = req.body;
-
- // Check if user already exists
- const userExists = await User.findOne({ email });
- if (userExists) {
- return res.status(400).json({ message: 'User already exists' });
+ try {
+ const { name, email, password } = req.body;
+
+ // Validate required fields
+ if (!name || !email || !password) {
+ return res.status(400).json({ message: 'All fields are required' });
+ }
+
+ // Check if user already exists
+ const userExists = await User.findOne({ email });
+ if (userExists) {
+ return res.status(400).json({ message: 'User already exists' });
+ }
+
+ // Hash password
+ const hashedPassword = await bcrypt.hash(password, 10);
+
+ // Create user with initial loyalty points and welcome achievement
+ const user = await User.create({
+ name,
+ email,
+ password: hashedPassword,
+ loyaltyPoints: 500,
+ totalPointsEarned: 500,
+ achievements: [{
+ id: 'welcome',
+ name: 'Welcome to Foodie!',
+ unlockedAt: new Date()
+ }]
+ });
+
+ // Generate JWT
+ const token = generateToken(user._id);
+
+ // Set JWT in secure, HTTP-only cookie
+ res.cookie('token', token, {
+ httpOnly: true,
+ sameSite: 'strict',
+ maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
+ });
+
+ res.status(201).json({
+ user: {
+ _id: user._id,
+ name: user.name,
+ email: user.email,
+ loyaltyPoints: user.loyaltyPoints,
+ totalPointsEarned: user.totalPointsEarned,
+ achievements: user.achievements,
+ rewardHistory: user.rewardHistory
+ },
+ token: token,
+ message: 'Registration successful! Welcome bonus: 500 loyalty points added! ๐',
+ });
+ } catch (error) {
+ console.error('Registration error:', error);
+ res.status(500).json({ message: 'Server error during registration' });
}
-
- // Hash password
- const hashedPassword = await bcrypt.hash(password, 10);
-
- // Create user
- const user = await User.create({ name, email, password: hashedPassword });
-
- // Generate JWT
- const token = generateToken(user._id);
-
- // Set JWT in secure, HTTP-only cookie
- res.cookie('token', token, {
- httpOnly: true, // Prevent JS access (XSS protection)
- sameSite: 'strict', // CSRF protection
- maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
- });
-
- res.status(201).json({
- user: { _id: user._id, name: user.name, email: user.email },
- message: 'Registration successful',
- });
};
export const loginUser = async (req, res) => {
- const { email, password } = req.body;
-
- // Find user
- const user = await User.findOne({ email });
- if (!user) {
- return res.status(401).json({ message: 'User not found' });
+ try {
+ const { email, password } = req.body;
+
+ // Validate required fields
+ if (!email || !password) {
+ return res.status(400).json({ message: 'Email and password are required' });
+ }
+
+ // Find user
+ const user = await User.findOne({ email });
+ if (!user) {
+ return res.status(401).json({ message: 'Invalid email or password' });
+ }
+
+ // Compare password
+ const isMatch = await bcrypt.compare(password, user.password);
+ if (!isMatch) {
+ return res.status(401).json({ message: 'Invalid email or password' });
+ }
+
+ // Generate JWT
+ const token = generateToken(user._id);
+
+ // Set JWT in secure, HTTP-only cookie
+ res.cookie('token', token, {
+ httpOnly: true,
+ sameSite: 'strict',
+ maxAge: 7 * 24 * 60 * 60 * 1000,
+ });
+
+ res.json({
+ user: {
+ _id: user._id,
+ name: user.name,
+ email: user.email,
+ loyaltyPoints: user.loyaltyPoints || 0,
+ totalPointsEarned: user.totalPointsEarned || 0,
+ achievements: user.achievements || [],
+ rewardHistory: user.rewardHistory || []
+ },
+ token: token,
+ message: 'Login successful',
+ });
+ } catch (error) {
+ console.error('Login error:', error);
+ res.status(500).json({ message: 'Server error during login' });
}
-
- // Compare password
- const isMatch = await bcrypt.compare(password, user.password);
- if (!isMatch) {
- return res.status(401).json({ message: 'Invalid password' });
- }
-
- // Generate JWT
- const token = generateToken(user._id);
-
- // Set JWT in secure, HTTP-only cookie
- res.cookie('token', token, {
- httpOnly: true,
- sameSite: 'strict',
- maxAge: 7 * 24 * 60 * 60 * 1000,
- });
-
- res.json({
- user: { _id: user._id, name: user.name, email: user.email },
- message: 'Login successful',
- });
};
export const logoutUser = async (req, res) => {
@@ -78,4 +127,4 @@ export const logoutUser = async (req, res) => {
});
res.status(200).json({ message: 'User logged out successfully' });
-};
+};
\ No newline at end of file
diff --git a/backend/controllers/foodController.js b/backend/controllers/foodController.js
index 1445165a..252e043f 100644
--- a/backend/controllers/foodController.js
+++ b/backend/controllers/foodController.js
@@ -15,6 +15,7 @@ const addFood = async (req, res) => {
price: body.price,
description: body.description,
category: body.category,
+ foodType: body.foodType || 'main', // Default to 'main' if not provided
restaurantId: body.restaurantId,
image: req.file?.filename,
});
@@ -52,5 +53,50 @@ const removeFood = async (req, res) => {
}
};
+// Get all available food types/categories
+const getFoodTypes = async (req, res) => {
+ try {
+ const foodTypes = ['appetizer', 'main', 'dessert', 'beverage', 'snack', 'side'];
+ res.status(200).json({ success: true, foodTypes });
+ } catch (error) {
+ res.status(500).json({ success: false, message: error.message });
+ }
+};
+
+// Get foods by food type
+const getFoodsByType = async (req, res) => {
+ try {
+ const { foodType } = req.params;
+ const { restaurantId } = req.query;
+
+ let query = { foodType };
+ if (restaurantId) {
+ query.restaurantId = restaurantId;
+ }
+
+ const foods = await Food.find(query).populate('restaurantId', 'name');
+ res.status(200).json({ success: true, foods });
+ } catch (error) {
+ res.status(500).json({ success: false, message: error.message });
+ }
+};
+
+// Get all foods with optional filtering
+const getAllFoods = async (req, res) => {
+ try {
+ const { foodType, category, restaurantId } = req.query;
+ let query = {};
+
+ if (foodType) query.foodType = foodType;
+ if (category) query.category = category;
+ if (restaurantId) query.restaurantId = restaurantId;
+
+ const foods = await Food.find(query).populate('restaurantId', 'name');
+ res.status(200).json({ success: true, foods });
+ } catch (error) {
+ res.status(500).json({ success: false, message: error.message });
+ }
+};
+
// โ
Export all at once (no duplicates)
-export { addFood, getFoodByRestaurant, removeFood };
+export { addFood, getFoodByRestaurant, removeFood, getFoodTypes, getFoodsByType, getAllFoods };
diff --git a/backend/models/userModel.js b/backend/models/userModel.js
index 7ec70e93..97789d03 100644
--- a/backend/models/userModel.js
+++ b/backend/models/userModel.js
@@ -6,7 +6,25 @@ const userSchema = new mongoose.Schema({
password: { type: String, required: true },
role: { type: String, enum: ["user", "admin"], default: "user" },
favoriteRestaurant: { type: mongoose.Schema.Types.ObjectId, ref: "Restaurant" },
- favoriteFoods: [{ type: mongoose.Schema.Types.ObjectId, ref: "food" }]
+ favoriteFoods: [{ type: mongoose.Schema.Types.ObjectId, ref: "food" }],
+
+ // Loyalty Points System
+ loyaltyPoints: { type: Number, default: 500 }, // Give 500 points initially
+ totalPointsEarned: { type: Number, default: 500 }, // Track lifetime points earned
+ achievements: [{
+ id: String,
+ name: String,
+ unlockedAt: { type: Date, default: Date.now }
+ }],
+ rewardHistory: [{
+ id: String,
+ rewardId: String,
+ rewardName: String,
+ pointsCost: Number,
+ redeemedAt: { type: Date, default: Date.now },
+ used: { type: Boolean, default: false },
+ usedAt: Date
+ }]
}, { timestamps: true });
diff --git a/backend/package.json b/backend/package.json
index 01102f57..bcccfe41 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -7,7 +7,8 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"server": "nodemon server.js",
- "start": "node server.js"
+ "start": "node server.js",
+ "migrate:loyalty": "node utils/migrateLoyaltyPoints.js"
},
"author": "",
"license": "ISC",
diff --git a/backend/routes/foodRoute.js b/backend/routes/foodRoute.js
index 192a6667..0cbbfdff 100644
--- a/backend/routes/foodRoute.js
+++ b/backend/routes/foodRoute.js
@@ -1,5 +1,5 @@
import express from "express"
-import { addFood, getFoodByRestaurant , removeFood} from "../controllers/foodController.js"
+import { addFood, getFoodByRestaurant , removeFood, getFoodTypes, getFoodsByType, getAllFoods} from "../controllers/foodController.js"
import multer from "multer"
const foodRouter = express.Router();
@@ -20,4 +20,7 @@ const upload = multer({storage:storage})
foodRouter.post("/add", upload.single('image'), addFood);
foodRouter.post("/remove", removeFood)
foodRouter.get("/restaurant/:restaurantId", getFoodByRestaurant);
+foodRouter.get("/types", getFoodTypes); // Get all available food types
+foodRouter.get("/type/:foodType", getFoodsByType); // Get foods by specific type
+foodRouter.get("/all", getAllFoods); // Get all foods with optional filtering
export default foodRouter;
\ No newline at end of file
diff --git a/backend/server.js b/backend/server.js
index 90e679ae..35488360 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -8,16 +8,13 @@ import reviewRoutes from "./routes/reviewRoutes.js";
import paymentRoute from "./routes/paymentRoute.js";
import restaurantRoutes from "./routes/restaurantRoutes.js";
import authRoutes from './routes/authRoute.js';
-
import userRoutes from './routes/userRoute.js';
-
import cookieParser from 'cookie-parser';
-
-
import "dotenv/config";
+
// app config
const app = express();
-const port = 4000;
+const port = process.env.PORT || 4000;
// middleware
app.use(express.json());
@@ -25,13 +22,16 @@ app.use(cookieParser());
// CORS setup for cookies
app.use(cors({
- origin: "http://localhost:5173", // frontend URL
+ origin: ["http://localhost:5173", "http://localhost:3000"], // frontend URLs
credentials: true // allow cookies to be sent
}));
-
// db connection
-await connectDB();
+try {
+ await connectDB();
+} catch (error) {
+ console.log('Database connection failed:', error.message);
+}
// api endpoints
app.use("/api/food", foodRouter);
@@ -43,11 +43,19 @@ app.use("/api/restaurant", restaurantRoutes);
app.use("/api/auth", authRoutes);
app.use("/api/user", userRoutes);
-
app.get("/", (req, res) => {
- res.send("api working");
+ res.json({
+ message: "Foodie API is working!",
+ status: "success"
+ });
+});
+
+// Error handling middleware
+app.use((err, req, res, next) => {
+ console.error('โ Server Error:', err.stack);
+ res.status(500).json({ message: 'Something went wrong!' });
});
app.listen(port, () => {
- console.log(`server started on port ${port}`);
+ console.log(`Server started on port ${port}`);
});
diff --git a/backend/utils/migrateLoyaltyPoints.js b/backend/utils/migrateLoyaltyPoints.js
new file mode 100644
index 00000000..dbdc9c80
--- /dev/null
+++ b/backend/utils/migrateLoyaltyPoints.js
@@ -0,0 +1,84 @@
+import mongoose from 'mongoose';
+import User from '../models/userModel.js';
+import { connectDB } from '../config/db.js';
+import 'dotenv/config';
+
+/**
+ * Migration script to add loyalty points to existing users
+ * Run this once to give all existing users 500 initial points
+ */
+const migrateLoyaltyPoints = async () => {
+ try {
+ console.log('๐ Starting loyalty points migration...');
+
+ // Connect to database
+ await connectDB();
+ console.log('โ
Connected to database');
+
+ // Find all users without loyalty points
+ const usersToUpdate = await User.find({
+ $or: [
+ { loyaltyPoints: { $exists: false } },
+ { loyaltyPoints: { $eq: null } },
+ { totalPointsEarned: { $exists: false } }
+ ]
+ });
+
+ console.log(`๐ Found ${usersToUpdate.length} users to update`);
+
+ if (usersToUpdate.length === 0) {
+ console.log('โ
All users already have loyalty points');
+ return;
+ }
+
+ // Update users with initial loyalty points
+ const updateResult = await User.updateMany(
+ {
+ $or: [
+ { loyaltyPoints: { $exists: false } },
+ { loyaltyPoints: { $eq: null } },
+ { totalPointsEarned: { $exists: false } }
+ ]
+ },
+ {
+ $set: {
+ loyaltyPoints: 500,
+ totalPointsEarned: 500,
+ achievements: [{
+ id: 'legacy_welcome',
+ name: 'Loyalty Program Member',
+ unlockedAt: new Date()
+ }],
+ rewardHistory: []
+ }
+ }
+ );
+
+ console.log(`โ
Updated ${updateResult.modifiedCount} users with 500 loyalty points`);
+ console.log('๐ Migration completed successfully!');
+
+ // Show some stats
+ const totalUsers = await User.countDocuments();
+ const usersWithPoints = await User.countDocuments({ loyaltyPoints: { $gte: 0 } });
+
+ console.log(`๐ Summary:`);
+ console.log(` Total users: ${totalUsers}`);
+ console.log(` Users with loyalty points: ${usersWithPoints}`);
+ console.log(` Coverage: ${((usersWithPoints / totalUsers) * 100).toFixed(1)}%`);
+
+ } catch (error) {
+ console.error('โ Migration failed:', error);
+ } finally {
+ // Close database connection
+ await mongoose.connection.close();
+ console.log('๐ Database connection closed');
+ process.exit(0);
+ }
+};
+
+// Run migration if this file is executed directly
+if (import.meta.url === `file://${process.argv[1]}`) {
+ migrateLoyaltyPoints();
+}
+
+export default migrateLoyaltyPoints;
\ No newline at end of file
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 386c35f6..21b09922 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from "react";
+import React, { useState, useEffect, useContext } from "react";
import Navbar from "./components/Navbar/Navbar";
import { Routes, Route } from "react-router-dom";
import Home from "./pages/Home/Home";
@@ -8,6 +8,7 @@ import Footer from "./components/Footer/Footer";
import AppDownload from "./components/AppDownlad/AppDownload";
import LoginPopup from "./components/LoginPopup/LoginPopup";
import ThemeContextProvider from "./components/context/ThemeContext";
+import LoyaltyContextProvider from "./components/context/LoyaltyContext";
import FoodDetail from "./components/FoodDetail/FoodDetail";
import CartSummaryBar from "./components/CartSummaryBar/CartSummaryBar";
import ScrollToTopButton from "./components/ScrollToTopButton/ScrollToTopButton";
@@ -22,21 +23,91 @@ import LoadingAnimation from "./components/LoadingAnimation";
import ScrollToTop from "../utility/ScrollToTop";
import "./components/FoodDetail/print.css";
import NotFound from "./pages/Notfound";
-import StoreContextProvider from "./components/context/StoreContext";
+import StoreContextProvider, { StoreContext } from "./components/context/StoreContext";
import ScrollToBottom from "./components/ScrollToBottomButton/ScrollToBottomButton";
import ReferralProgram from "./components/Referrals/ReferralProgram";
import AboutUs from "./components/Aboutus/Aboutus";
import FAQ from "./components/FAQ/FAQ";
import Privacy from "./components/Privacy/privacy";
import FeedbackReviews from "./components/FeedbackReviews/FeedbackReviews";
+import Rewards from "./pages/Rewards/Rewards";
+import AchievementNotification from "./components/AchievementNotification/AchievementNotification";
-const App = () => {
+const AppContent = () => {
const [showLogin, setShowLogin] = useState(false);
+ const { isAuthenticated } = useContext(StoreContext);
+
+ return (
+ <>
+
+ Your journey continues after login ๐ +
+- Your journey continues after login ๐ -
-{currentAchievement.description}
+ +๐ก Visit Rewards to redeem your points anytime!
+Available Points
+Orders Placed
+Reviews Written
+Total Spent
+{action.description}
+Experience how you earn points with every order!
+Hello {user?.name}! Track your points and unlock amazing rewards
+๐ Visit the Rewards page to redeem your points for discounts and free items!
+{reward.description}
+ {!canApply && {reasonText}} +Referrals Pending
Rewards Earned
+ Redeem points for discounts, free delivery, and exclusive rewards! +
+Please login to view your rewards and achievements
+Earn points with every order and unlock amazing rewards!
+{reward.description}
+{achievement.description}
+Redeemed on {new Date(redemption.redeemedAt).toLocaleDateString()}
+ Ready to use +No active rewards. Redeem some rewards to see them here!
+ )} +Redeemed on {new Date(redemption.redeemedAt).toLocaleDateString()}
+ + {redemption.used ? 'Used' : 'Active'} + +No reward history yet. Start redeeming rewards!
+ )} +Earn 1 point for every $1 spent
+Get 25 points for each review
+Bonus points for milestones
+200 points for each referral
+