diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..c023163
Binary files /dev/null and b/.DS_Store differ
diff --git a/.gitignore b/.gitignore
index bf61aa7..dcbba63 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,5 @@
/kubernetes/backend-config.yaml
/kubernetes/tls-secret.yaml
/secrets/*.pem
-/secrets/*.sh
\ No newline at end of file
+/secrets/*.sh
+.DS_Store
\ No newline at end of file
diff --git a/backend/.prettierrc b/backend/.prettierrc
new file mode 100644
index 0000000..c34aef0
--- /dev/null
+++ b/backend/.prettierrc
@@ -0,0 +1,8 @@
+{
+ "semi": true,
+ "singleQuote": false,
+ "printWidth": 80,
+ "tabWidth": 2,
+ "endOfLine": "auto",
+ "trailingComma": "es5"
+}
diff --git a/backend/controller/authController.js b/backend/controller/authController.js
index 3387476..1e905b3 100644
--- a/backend/controller/authController.js
+++ b/backend/controller/authController.js
@@ -267,6 +267,20 @@ exports.login = catchAsync(async (req, res, next) => {
createSendToken(user, res);
});
+// check if the user is logged in
+exports.isLoggedIn = catchAsync(async (req, res, next) => {
+ if (req.user) {
+ res.status(200).json({
+ status: "success",
+ data: {
+ message: "User is logged in",
+ },
+ });
+ } else {
+ return next(new AppError("Please log in to access this page", 401));
+ }
+});
+
// middleware to protect routes
exports.protect = catchAsync(async (req, res, next) => {
let token;
@@ -283,7 +297,12 @@ exports.protect = catchAsync(async (req, res, next) => {
);
}
- const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET);
+ let decoded;
+ try {
+ decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET);
+ } catch (error) {
+ return next(new AppError("Invalid token. Please log in again.", 401));
+ }
const currentUser = await User.findByPk(decoded.id);
// check if the user is banned
diff --git a/backend/routes/userRoutes.js b/backend/routes/userRoutes.js
index 551f804..ec635e8 100644
--- a/backend/routes/userRoutes.js
+++ b/backend/routes/userRoutes.js
@@ -12,6 +12,8 @@ router.post("/signup-verify", authController.signUpVerify);
router.post("/resend-code", authController.resendCode);
// login route
router.post("/login", authController.login);
+// isLoggedin route
+router.get("/is-loggedin", authController.protect, authController.isLoggedIn);
//update notification settings route
router
diff --git a/frontend/public/bid-page.png b/frontend/public/bid-page.png
new file mode 100644
index 0000000..dc0f586
Binary files /dev/null and b/frontend/public/bid-page.png differ
diff --git a/frontend/public/main-page.png b/frontend/public/main-page.png
new file mode 100644
index 0000000..f32ba54
Binary files /dev/null and b/frontend/public/main-page.png differ
diff --git a/frontend/public/market-info.png b/frontend/public/market-info.png
new file mode 100644
index 0000000..cbae01d
Binary files /dev/null and b/frontend/public/market-info.png differ
diff --git a/frontend/public/match-page.png b/frontend/public/match-page.png
new file mode 100644
index 0000000..335efa0
Binary files /dev/null and b/frontend/public/match-page.png differ
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 7bea46f..92a51e4 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -13,6 +13,7 @@ import AdminPage from "./pages/AdminPage/AdminPage";
import Profile from "./pages/ProfilePage/Profile";
import PrivateRoute from "./PrivateRoute";
import { useTheme, ToggleThemeProvider } from "./context/ThemeContext";
+import LandingPage from "./pages/LandingPage/LandingPage";
function AppContent() {
const { darkMode } = useTheme();
@@ -76,6 +77,7 @@ function AppContent() {
/>
} />
+ } />
diff --git a/frontend/src/PrivateRoute.jsx b/frontend/src/PrivateRoute.jsx
index 85a927e..aeb3af6 100644
--- a/frontend/src/PrivateRoute.jsx
+++ b/frontend/src/PrivateRoute.jsx
@@ -4,23 +4,57 @@ import { useAuth } from "./context/AuthContext";
import { useConfig } from "./context/ConfigContext";
import { useDispatch } from "react-redux";
import { fetchInitialData } from "./features/bidSlice";
+import axios from "axios";
const PrivateRoute = ({ children, adminPage = false }) => {
- const { config, loading } = useConfig();
- const { isLoggedIn, isLoading, authToken, role } = useAuth();
+ const { config, loading: configLoading } = useConfig();
+ const {
+ isLoggedIn,
+ isLoading: authLoading,
+ authToken,
+ role,
+ logout,
+ } = useAuth();
const location = useLocation();
const dispatch = useDispatch();
useEffect(() => {
- if (isLoggedIn && !isLoading && !loading) {
- dispatch(fetchInitialData({ authToken, config }));
- }
- }, [isLoggedIn, isLoading, dispatch, authToken, config, loading]);
-
- if (!isLoggedIn && !isLoading && !loading) {
- // Redirect to auth, but store original location for later
- return ;
- } else if (isLoggedIn && adminPage && role !== "admin" && !loading) {
+ const checkToken = async () => {
+ if (isLoggedIn && !authLoading && !configLoading) {
+ try {
+ await axios.get(`${config.REACT_APP_API_URL}/v1/users/is-loggedin`, {
+ headers: { Authorization: `Bearer ${authToken}` },
+ });
+ // Token is valid, fetch initial data
+ dispatch(fetchInitialData({ authToken, config }));
+ } catch (error) {
+ // Token is invalid, call logout
+ logout();
+ }
+ }
+ };
+
+ checkToken();
+ }, [
+ isLoggedIn,
+ authLoading,
+ configLoading,
+ config,
+ authToken,
+ dispatch,
+ logout,
+ ]);
+
+ if (authLoading || configLoading) {
+ return null; // Render nothing while loading
+ }
+
+ if (!isLoggedIn) {
+ // Redirect to landing page, but store original location for later
+ return ;
+ }
+
+ if (adminPage && role !== "admin") {
return ;
}
diff --git a/frontend/src/pages/LandingPage/LandingPage.jsx b/frontend/src/pages/LandingPage/LandingPage.jsx
new file mode 100644
index 0000000..eb70c31
--- /dev/null
+++ b/frontend/src/pages/LandingPage/LandingPage.jsx
@@ -0,0 +1,268 @@
+import React, { useState, useEffect } from "react";
+import { NavLink } from "react-router-dom";
+import { ChevronRightIcon } from "@heroicons/react/24/outline";
+import {
+ CursorArrowRaysIcon,
+ ShieldCheckIcon,
+ ChartBarIcon,
+ CheckIcon,
+} from "@heroicons/react/24/outline";
+
+const contentSections = [
+ {
+ title: "Real-Time Market Insights",
+ description:
+ "Stay informed with up-to-the-minute market data. Our intuitive dashboard provides a comprehensive overview of current meal point prices, recent transactions, and market trends.",
+ features: [
+ "Live price updates",
+ "Historical price charts",
+ "Transaction volume indicators",
+ "Market recommendations",
+ ],
+ image: "/market-info.png",
+ },
+ {
+ title: "Effortless Bidding Process",
+ description:
+ "Place your bid with ease and confidence. Our streamlined bidding page allows you to set your desired price and quantity, while providing helpful market guidance to ensure you make informed decisions.",
+ features: [
+ "One-click bid placement",
+ "Market-based price suggestions",
+ "Flexible quantity options",
+ "Instant bid confirmation",
+ ],
+ image: "/bid-page.png",
+ },
+ {
+ title: "Seamless Match Notifications",
+ description:
+ "Experience the joy of a perfect match! When our system finds an ideal buyer or seller for your bid, you'll receive instant email with all the details you need to complete your transaction.",
+ features: [
+ "Real-time match alerts",
+ "Detailed transaction summaries",
+ "Easy transaction finalization",
+ ],
+ image: "/match-page.png",
+ },
+];
+
+const LandingPage = () => {
+ const [scrolled, setScrolled] = useState(false);
+
+ useEffect(() => {
+ const handleScroll = () => {
+ const isScrolled = window.scrollY > 50;
+ if (isScrolled !== scrolled) {
+ setScrolled(isScrolled);
+ }
+ };
+
+ window.addEventListener("scroll", handleScroll);
+
+ return () => {
+ window.removeEventListener("scroll", handleScroll);
+ };
+ }, [scrolled]);
+
+ return (
+
+
+
+ {/* Hero Section */}
+
+
+
+
+
+ Safe, Fair, and Easy
+
+ Meal Point Exchange
+
+ for WashU Students
+
+
+ We've revolutionized meal point exchange for WashU students. Our
+ secure marketplace eliminates haggling and prevents scams,
+ making transactions effortless.
+
+
+
+ Get Started
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Features Section */}
+
+
+
+ {[
+ {
+ title: "Automated Matching",
+ desc: "Say goodbye to endless haggling! Our algorithm instantly connects you with the best deal, saving you time and ensuring fair prices for everyone.",
+ icon: CursorArrowRaysIcon,
+ },
+ {
+ title: "Secure Authentication",
+ desc: "Trade with confidence knowing you're part of a verified WashU community. Our email verification keeps your transactions safe and scam-free.",
+ icon: ShieldCheckIcon,
+ },
+ {
+ title: "Transparent Transactions",
+ desc: "Make informed decisions with our real-time market insights. View anonymized transaction data to understand pricing trends and get the best value for your meal points.",
+ icon: ChartBarIcon,
+ },
+ ].map((feature, index) => (
+