From b9b62c60215c4ca0ed95a305750aaa36d27703b0 Mon Sep 17 00:00:00 2001 From: mukeshdhadhariya Date: Thu, 13 Nov 2025 18:16:56 +0530 Subject: [PATCH] feat(frontend): frontend and backend both setup and connect to each other --- .env.example | 4 + package-lock.json | 13 ++ package.json | 1 + server/.env.example | 4 - server/controller/user.controller.js | 10 +- server/middleware/auth.middleware.js | 3 +- server/mock-socket-server.js | 57 +++++---- server/routes/user.route.js | 2 +- server/utils/connect-db.js | 32 ++--- src/App.jsx | 101 +++++++--------- src/components/Navigation.jsx | 28 +++++ src/context/AuthContext.jsx | 35 ++++++ src/context/SocketContext.jsx | 13 ++ src/pages/Login.jsx | 130 ++++++++++++++++++++ src/pages/Register.jsx | 170 +++++++++++++++++++++++++++ 15 files changed, 497 insertions(+), 106 deletions(-) create mode 100644 .env.example delete mode 100644 server/.env.example create mode 100644 src/context/AuthContext.jsx create mode 100644 src/pages/Login.jsx create mode 100644 src/pages/Register.jsx diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6d413b9 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +MONGO_URI= +JWT_SECRET= +NODE_ENV= +PORT=mongodb+srv://mukeshdhadhariya1_db_user:sKk4J4Rsmt1MzOEL@cluster0.fc8cebb.mongodb.net/ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a6815ee..bdbaa8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "cookie-parser": "^1.4.7", "cors": "^2.8.5", "date-fns": "^4.1.0", + "dotenv": "^17.2.3", "express": "^5.1.0", "express-async-handler": "^1.2.0", "framer-motion": "^12.23.22", @@ -3038,6 +3039,18 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", diff --git a/package.json b/package.json index cbfe266..de922f3 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "cookie-parser": "^1.4.7", "cors": "^2.8.5", "date-fns": "^4.1.0", + "dotenv": "^17.2.3", "express": "^5.1.0", "express-async-handler": "^1.2.0", "framer-motion": "^12.23.22", diff --git a/server/.env.example b/server/.env.example deleted file mode 100644 index 9e220fe..0000000 --- a/server/.env.example +++ /dev/null @@ -1,4 +0,0 @@ -MONGO_URI= -JWT_SECRET= -NODE_ENV= -PORT= \ No newline at end of file diff --git a/server/controller/user.controller.js b/server/controller/user.controller.js index e60eb95..c4619d6 100644 --- a/server/controller/user.controller.js +++ b/server/controller/user.controller.js @@ -36,6 +36,7 @@ export const registerUser = asyncHandler(async (req, res) => { }); export const loginUser = asyncHandler(async (req, res) => { + const { email, password } = req.body; if (!email || !password) { @@ -57,11 +58,12 @@ export const loginUser = asyncHandler(async (req, res) => { // Set token in HTTP-only cookie res.cookie("token", token, { httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", + secure: false, // ❗ localhost uses HTTP, not HTTPS + sameSite: "lax", maxAge: 24 * 60 * 60 * 1000, }); + return res.status(200).json({ success: true, message: "Login successful", @@ -78,8 +80,8 @@ export const loginUser = asyncHandler(async (req, res) => { export const logoutUser = asyncHandler(async (req, res) => { res.clearCookie("token", { httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", + secure: false, + sameSite: "lax", }); return res.status(200).json({ diff --git a/server/middleware/auth.middleware.js b/server/middleware/auth.middleware.js index 933964f..681414f 100644 --- a/server/middleware/auth.middleware.js +++ b/server/middleware/auth.middleware.js @@ -4,11 +4,10 @@ import { User } from "../models/user.model.js"; export const authMiddleware = async (req, res, next) => { try { - const token = req.headers.authorization?.split(" ")[1]; + const token = req.cookies?.token || req.headers.authorization?.split(" ")[1]; if (!token) { return res.status(401).json({ success: false, message: "No token provided" }); } - const decoded = jwt.verify(token, process.env.JWT_SECRET); const user = await User.findById(decoded.id); diff --git a/server/mock-socket-server.js b/server/mock-socket-server.js index f7b9e71..8512172 100644 --- a/server/mock-socket-server.js +++ b/server/mock-socket-server.js @@ -6,10 +6,21 @@ import http from 'http'; import { Server } from 'socket.io'; import cors from 'cors'; import cookieParser from "cookie-parser"; +import userrouter from "./routes/user.route.js" +import dotenv from "dotenv"; +import connectDB from "./utils/connect-db.js" + +dotenv.config(); const app = express(); -app.use(cors()); +app.use(cors({ + origin: ["http://localhost:5173"], // πŸ‘ˆ your React app + credentials: true, // πŸ‘ˆ allow cookies +})); +app.use(express.json()); app.use(cookieParser()); +app.use("/api",userrouter) + const server = http.createServer(app); const io = new Server(server, { @@ -257,30 +268,30 @@ app.get('/info', (req, res) => { }); const PORT = process.env.PORT || 3001; -dotenv.config(); -// connectDB().then( -// server.listen(PORT, () => { -// console.log(`πŸš€ InvertorGuard Mock Socket.IO server running on port ${PORT}`); -// console.log(`πŸ“ Health check: http://localhost:${PORT}/health`); -// console.log(`πŸ“ Server info: http://localhost:${PORT}/info`); -// console.log(`πŸ”Œ Connect your React app to: http://localhost:${PORT}`); -// console.log(`πŸ‘₯ Ready for client connections...`); -// }) -// ).catch((err)=> -// { -// console.log('database connection faield',err); -// }); - - -server.listen(PORT, () => { - console.log(`πŸš€ InvertorGuard Mock Socket.IO server running on port ${PORT}`); - console.log(`πŸ“ Health check: http://localhost:${PORT}/health`); - console.log(`πŸ“ Server info: http://localhost:${PORT}/info`); - console.log(`πŸ”Œ Connect your React app to: http://localhost:${PORT}`); - console.log(`πŸ‘₯ Ready for client connections...`); -}) +connectDB() + .then(() => { + server.listen(PORT, () => { + console.log(`πŸš€ InvertorGuard Mock Socket.IO server running on port ${PORT}`); + console.log(`πŸ“ Health check: http://localhost:${PORT}/health`); + console.log(`πŸ“ Server info: http://localhost:${PORT}/info`); + console.log(`πŸ”Œ Connect your React app to: http://localhost:${PORT}`); + console.log(`πŸ‘₯ Ready for client connections...`); + }) + }) + .catch((err) => { + console.error("❌ Database connection failed:", err); + }); + + +// server.listen(PORT, () => { +// console.log(`πŸš€ InvertorGuard Mock Socket.IO server running on port ${PORT}`); +// console.log(`πŸ“ Health check: http://localhost:${PORT}/health`); +// console.log(`πŸ“ Server info: http://localhost:${PORT}/info`); +// console.log(`πŸ”Œ Connect your React app to: http://localhost:${PORT}`); +// console.log(`πŸ‘₯ Ready for client connections...`); +// }) // Graceful shutdown process.on('SIGINT', () => { diff --git a/server/routes/user.route.js b/server/routes/user.route.js index 61e0a8d..b31db9e 100644 --- a/server/routes/user.route.js +++ b/server/routes/user.route.js @@ -4,7 +4,7 @@ import { authMiddleware } from "../middleware/auth.middleware.js"; const router = express.Router(); -router.post("/register", registerUser); +router.post("/register", authMiddleware, registerUser); router.post("/login", loginUser); router.post("/logout", authMiddleware, logoutUser); diff --git a/server/utils/connect-db.js b/server/utils/connect-db.js index 100359a..a47fb92 100644 --- a/server/utils/connect-db.js +++ b/server/utils/connect-db.js @@ -1,22 +1,22 @@ import mongoose from "mongoose"; +import dotenv from "dotenv"; +dotenv.config(); -const connectDB=async ()=>{ - try { - - if(!process.env.MONGO_URI){ - console.warn('MONGO_URI not found in .env - database features will be unavailable'); - return; - } - - await mongoose.connect(`${process.env.MONGO_URI}`) +const connectDB = async () => { + try { + + if (!process.env.MONGO_URI) { + throw new Error("❌ Missing MONGO_URI in .env file!"); + } - console.log(`\n mongodb connected`); + await mongoose.connect(process.env.MONGO_URI); - } catch (error) { + console.log(`βœ… MongoDB connected successfully`); + } catch (error) { + console.error("❌ MongoDB connection error:", error.message); - console.warn("Mongodb connection error - database features will be unavailable:",error.message); - - } -} + throw error; + } +}; -export default connectDB; \ No newline at end of file +export default connectDB; diff --git a/src/App.jsx b/src/App.jsx index fc14d96..6d7bc73 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,40 +1,3 @@ -// import { useState } from 'react' -// import reactLogo from './assets/react.svg' -// import viteLogo from '/vite.svg' -// import './App.css' - -// function App() { -// const [count, setCount] = useState(0) - -// return ( -// <> -//
-// -// Vite logo -// -// -// React logo -// -//
-//

Vite + React

-//
-// -//

-// Edit src/App.jsx and save to test HMR -//

-//
-//

-// Click on the Vite and React logos to learn more -//

-// -// ) -// } - -// export default App - - import React from 'react'; import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; import { ToastContainer } from 'react-toastify'; @@ -42,40 +5,66 @@ import 'react-toastify/dist/ReactToastify.css'; import { AppProvider, useApp } from './context/AppContext'; import { SocketProvider } from './context/SocketContext'; import { NotificationProvider } from './context/NotificationContext'; +import { AuthProvider, useAuth } from './context/AuthContext'; import Dashboard from './pages/Dashboard'; import Logs from './pages/Logs'; import Settings from './pages/Settings'; import Navigation from './components/Navigation'; +import Login from './pages/Login'; +import Register from './pages/Register'; function App() { return ( - - - - - - - - - + + + + + + + + + + + ); } function InnerApp() { const { state } = useApp(); + const { user } = useAuth(); const toastTheme = state.theme === 'dark' ? 'dark' : 'light'; + const isAuthenticated = !!user; return ( -
- -
- - } /> - } /> - } /> - } /> - -
+
+ + {/* Public Routes */} + : } /> + : } /> + + {/* Protected Routes */} + {isAuthenticated ? ( + + +
+ + } /> + } /> + } /> + } /> + +
+ + } + /> + ) : ( + } /> + )} +
+ { const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isDesktop, setIsDesktop] = useState(typeof window !== 'undefined' ? window.innerWidth >= 1024 : false); const location = useLocation(); const { state } = useApp(); + const { logoutUser } = useAuth(); + const navigate = useNavigate(); + useEffect(() => { const handleResize = () => { @@ -125,6 +131,28 @@ const Navigation = () => { {/* System Info */}

System Info

+ + {/* Logout Button */} +
+ +
Battery diff --git a/src/context/AuthContext.jsx b/src/context/AuthContext.jsx new file mode 100644 index 0000000..779d2b9 --- /dev/null +++ b/src/context/AuthContext.jsx @@ -0,0 +1,35 @@ +import React, { createContext, useContext, useState, useEffect } from 'react'; + +const AuthContext = createContext(); + +export const AuthProvider = ({ children }) => { + const [user, setUser] = useState(null); + + // Load user from localStorage (persist session) + useEffect(() => { + const savedUser = localStorage.getItem('user'); + if (savedUser) setUser(JSON.parse(savedUser)); + }, []); + + // Save user to localStorage + useEffect(() => { + if (user) localStorage.setItem('user', JSON.stringify(user)); + else localStorage.removeItem('user'); + }, [user]); + + const loginUser = (userData) => { + setUser(userData); + }; + + const logoutUser = () => { + setUser(null); + }; + + return ( + + {children} + + ); +}; + +export const useAuth = () => useContext(AuthContext); diff --git a/src/context/SocketContext.jsx b/src/context/SocketContext.jsx index f9cc225..45d38f5 100644 --- a/src/context/SocketContext.jsx +++ b/src/context/SocketContext.jsx @@ -2,6 +2,7 @@ import React, { createContext, useContext, useEffect, useState } from 'react'; import { io } from 'socket.io-client'; import { useApp } from './AppContext'; import { useNotification } from './NotificationContext'; +import { useAuth } from './AuthContext'; const SocketContext = createContext(); @@ -10,8 +11,20 @@ export function SocketProvider({ children }) { const [isConnected, setIsConnected] = useState(false); const { dispatch } = useApp(); const { addNotification } = useNotification(); + const { user } = useAuth(); + const isAuthenticated = !!user; useEffect(() => { + + if (!isAuthenticated) { + if (socket) { + socket.disconnect(); + setSocket(null); + setIsConnected(false); + } + return; + } + // Connect to mock server - in production, replace with actual server URL const newSocket = io('http://localhost:3001', { transports: ['websocket'], diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx new file mode 100644 index 0000000..b3d331c --- /dev/null +++ b/src/pages/Login.jsx @@ -0,0 +1,130 @@ +import React, { useState } from "react"; +import { Link, useNavigate } from "react-router-dom"; +import { toast } from "react-toastify"; +import { useApp } from "../context/AppContext"; +import { useAuth } from "../context/AuthContext"; // πŸ‘ˆ import AuthContext hook + +const Login = () => { + const [credentials, setCredentials] = useState({ email: "", password: "" }); + const [loading, setLoading] = useState(false); + const navigate = useNavigate(); + const { state } = useApp(); + const { loginUser } = useAuth(); // πŸ‘ˆ get loginUser from context + + const handleChange = (e) => { + setCredentials({ ...credentials, [e.target.name]: e.target.value }); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + setLoading(true); + try { + const response = await fetch("http://localhost:3001/api/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + credentials: "include", // βœ… send/receive cookies + body: JSON.stringify(credentials), + }); + console.log("click1") + const data = await response.json(); + console.log("click1") + if (response.ok) { + // βœ… Save user data to AuthContext and localStorage + loginUser(data.user || { email: credentials.email }); + + toast.success("Login successful! πŸ”‹", { autoClose: 2000 }); + console.log("click1") + // βœ… Redirect to dashboard + setTimeout(() => navigate("/dashboard"), 1000); + } else { + toast.error(data.message || "Invalid email or password."); + } + } catch (err) { + console.error("Login error:", err); + toast.error("Server not responding. Please try again."); + } finally { + setLoading(false); + } + }; + + return ( +
+
+

+ Smart Energy Tracker ⚑ +

+

+ Monitor your inverter & energy usage with ease. +

+ +
+
+ + +
+ +
+ + +
+ + +
+ +

+ Don’t have an account?{" "} + + Register + +

+
+
+ ); +}; + +export default Login; diff --git a/src/pages/Register.jsx b/src/pages/Register.jsx new file mode 100644 index 0000000..566b855 --- /dev/null +++ b/src/pages/Register.jsx @@ -0,0 +1,170 @@ +import React, { useState } from "react"; +import { Link, useNavigate } from "react-router-dom"; +import { toast } from "react-toastify"; +import { useApp } from "../context/AppContext"; +import { useAuth } from "../context/AuthContext"; + +const Register = () => { + const [formData, setFormData] = useState({ + fullname: "", + username: "", + email: "", + password: "", + }); + const [loading, setLoading] = useState(false); + const navigate = useNavigate(); + const { state } = useApp(); + const { loginUser } = useAuth(); + + const handleChange = (e) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + setLoading(true); + + try { + const response = await fetch("http://localhost:3001/api/register", { + method: "POST", + headers: { "Content-Type": "application/json" }, + credentials: "include", // βœ… to send & receive cookies + body: JSON.stringify(formData), + }); + + const data = await response.json(); + + if (response.ok) { + // βœ… Save user in AuthContext + loginUser(data.user || { email: formData.email }); + + toast.success("Account created successfully! ⚑", { autoClose: 2000 }); + + // βœ… Redirect after successful register + setTimeout(() => navigate("/dashboard"), 1000); + } else { + toast.error(data.message || "Registration failed."); + } + } catch (err) { + console.error("Register Error:", err); + toast.error("Server not responding. Please try again."); + } finally { + setLoading(false); + } + }; + + return ( +
+
+

+ Create Account ⚑ +

+

+ Join Smart Energy Tracker and monitor power like never before. +

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ +

+ Already have an account?{" "} + + Login + +

+
+
+ ); +}; + +export default Register;