Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
50bb38f
Add react-router-dom in frontend package.json
Aug 15, 2025
e99cd44
Add axios to frontend package.json
Aug 15, 2025
e1805bc
Add zustand for global state in frontend package.json
Aug 15, 2025
66beb76
Remove index.css file from folder and remove import from main.jsx
Aug 15, 2025
0b184df
Import browserRouter and wrap app in BrowserRouter in main.jsx and de…
Aug 15, 2025
9fc85ae
Correct route path
Aug 15, 2025
31050c7
Add components and pages folder. Import pages in app.jsx
Aug 15, 2025
f576ae2
Add basic structure to pages
Aug 15, 2025
53f94b4
Add basic Signup page layout with form structure
Aug 18, 2025
750ee7c
Add useState hook for name, email, and password
Aug 18, 2025
0b9bfa6
Add handleSubmit function with axios POST to backend API
Aug 18, 2025
cdd4c54
Show loading state in button and display error messages
Aug 18, 2025
2d49b05
Redirect user to dashboard after successfull signup
Aug 18, 2025
a09b7a7
Add dotenv for environment variables and update server.js
Aug 18, 2025
017e920
Create models folder and User.js file for user schema
Aug 18, 2025
53c77eb
Add User model with schema and accessToken
Aug 18, 2025
37edf87
Add user signup route with bcrypt password hashing
Aug 18, 2025
ba30c47
Connect userRoutes to server.js for signup functionality
Aug 18, 2025
e07c4ed
Add type: module to package.json
Aug 18, 2025
d11c409
Change bcrypt import to bcryptjs in userRoutes.js
Aug 19, 2025
e1ad8f4
Add console.log to verify REACT_APP_API_URL and update .env
Aug 19, 2025
78b4c39
Hardcode API URL Signup.jsx for testing
Aug 19, 2025
969b3d1
Revert to using process.env.REACT_APP_API_URL in Signup.jsx
Aug 19, 2025
75c1682
Merge branch 'signup-page'
Aug 19, 2025
2d039f0
Merge branch 'user-routes'
Aug 19, 2025
e900f2c
Fix User import and update frontend to use env backend URL
Aug 19, 2025
d74e263
Add express-list-endpoints in package.json, import listendpoints and …
Aug 20, 2025
bcee90d
Add localStorage and store accessToken
Aug 20, 2025
27c52e8
Implement login functionality and improve signup validation
Aug 20, 2025
18b1708
Install styled-components and set up theme structure
Aug 20, 2025
138be2a
Add theme and themeProvider in App.jsx. Add theme.js and themes
Aug 20, 2025
d2e5ccb
Add globalStyles.js to styles folder.Import globalStyles in App.jsx a…
Aug 20, 2025
f2b7c23
Add Mascot.svg and basic styling
Aug 21, 2025
c8e4b41
Import Mascot and style marcot.img, mascotcontainer. Add StarsContain…
Aug 21, 2025
0e4f2dd
Add FallingStar and keyframe animation
Aug 21, 2025
6d0d5f4
Change and add additional styling to landing page
Aug 22, 2025
105cd95
Remove redundant code that is not needed
Aug 22, 2025
b0f045e
Change section text
Aug 22, 2025
70d5ea1
Prevent invalid props from being passed to DOM
Aug 22, 2025
b7d5b8d
Add SignupContainer, Navbar and styling
Aug 23, 2025
9d26881
Add LeftSection and RightSection ti Signup.jsx with styling and media…
Aug 24, 2025
f1b91e8
Add LeftSection and RightSection to Login.jsx with styling and media …
Aug 24, 2025
96f7b58
Add autocomplete to Signup.jsx and Login.jsx to remove warnings and i…
Aug 24, 2025
87701ec
Add meta description and change title in index.html
Aug 24, 2025
654c8e3
Add Navbar and and handlelogout to Dashboard.jsx
Aug 24, 2025
7454c3a
Install react icons. Add DashboardContainer, LeftPanel, RightContent …
Aug 25, 2025
1e5d70c
Add income management functionality for maninging income data
Aug 25, 2025
d7a0ff2
Persist income data using MongoDB to ensure income data persists rest…
Aug 25, 2025
0715660
Improve layout and responsiveness ot IncomeManagement page
Aug 25, 2025
d8520fa
Install recharts and add staple diagram and styling to Dashboard.jsx
Aug 26, 2025
92fa263
Remove deprecated MongoDB connection useNewUrlParser and useUnifiedTo…
Aug 26, 2025
a32ebfa
Add spendings Manamgent functionality
Aug 27, 2025
4cbfbcc
Enhance SpendingsManagement functionality and UI
Aug 27, 2025
749cf8a
Add VariableExpenses component to display expense categories and styling
Aug 28, 2025
d0f75cc
Add FixedExpenses components, new category to spendingsSchema and rea…
Aug 30, 2025
acd68f0
Add month and createdAt to Income schema, implement GET /income/month…
Aug 31, 2025
4954d96
Add delete functionality for spendings
Aug 31, 2025
16dba09
Display dynamic balance between income - spendings, and add dynamic l…
Aug 31, 2025
07cbf7a
Modularize dashboard by creating separate files for Navbar, LeftPanel…
Aug 31, 2025
f9fc0a6
Removed unnecessary images from assets
Aug 31, 2025
56507d4
Add and refine ProtectedRoute component to remove sensitive informati…
Aug 31, 2025
bc572ec
change to backend url in .env file and change console.log
Oct 26, 2025
323b841
change to point API calls to deployed backend URL
Oct 26, 2025
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
22 changes: 22 additions & 0 deletions backend/middleware/authMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { User } from "../models/User.js"

export const authenticateUser = async (req, res, next) => {
try {
const accessToken = req.header("Authorization")
const user = await User.findOne({ accessToken: accessToken })
if (user) {
req.user = user
next()
} else {
res.status(401).json({
message: "Authentication missing or invalid.",
loggedOut: true
})
}
} catch (error) {
res.status(500).json({
message: "Internal server error",
error: error.message
});
}
}
19 changes: 19 additions & 0 deletions backend/models/Income.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import mongoose from 'mongoose'

const incomeSchema = new mongoose.Schema({
amount: {
type: Number,
required: true,
default: 0,
},
month: {
type: String,
required: true,
},
createdAt: {
type: Date,
default: Date.now,
},
});

export const Income = mongoose.model("Income", incomeSchema);
21 changes: 21 additions & 0 deletions backend/models/Spendings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import mongoose from 'mongoose'

const spendingsSchema = new mongoose.Schema({
category: {
type: String,
required: true,
enum: ["Rent", "Parking", "Insurance", "Broadband", "Phone", "Grocery", "Shopping", "Entertainment", "Restaurants", "Cafe", "Travel", "Others"]
},
amount: {
type: Number,
required: true,
default: 0,
},
createdAt: {
type: Date,
default: Date.now,
},
});


export const Spendings = mongoose.model("Spendings", spendingsSchema);
26 changes: 26 additions & 0 deletions backend/models/User.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import mongoose from 'mongoose'
import crypto from 'crypto'


const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true,
},
accessToken: {
type: String,
default: () => crypto.randomBytes(128).toString('hex')
}
});


export const User = mongoose.model("User", userSchema);
7 changes: 6 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "project-final-backend",
"version": "1.0.0",
"description": "Server part of final project",
"type": "module",
"scripts": {
"start": "babel-node server.js",
"dev": "nodemon server.js --exec babel-node"
Expand All @@ -12,9 +13,13 @@
"@babel/core": "^7.17.9",
"@babel/node": "^7.16.8",
"@babel/preset-env": "^7.16.11",
"bcryptjs": "^3.0.2",
"cors": "^2.8.5",
"dotenv": "^17.2.1",
"express": "^4.17.3",
"express-list-endpoints": "^7.1.1",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.4.0",
"nodemon": "^3.0.1"
}
}
}
71 changes: 71 additions & 0 deletions backend/routes/incomeRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import express from "express";
import { Income } from "../models/Income.js";
import mongoose from "mongoose";

const router = express.Router();

// GET /income - Fetch the current income
router.get("/", async (req, res) => {
try {
let income = await Income.findOne();
if (!income) {
income = await Income.create({ amount: 0 });
}
res.status(200).json({ income: income.amount });
} catch (error) {
console.error("Error fetching income:", error);
res.status(500).json({ error: "Server error" });
}
});

// GET /income/months
router.get("/months", async (req, res) => {
try {
const months = await Income.find({}, '_id month amount createdAt').sort({ createdAt: -1 });
res.json(months);
} catch (error) {
console.error("Error fetching months:", error);
res.status(500).json({ message: "Failed to fetch months"});
}
});

// POST /income - Add a new income
router.post("/", async (req, res) => {
const { amount, month } = req.body;

if (typeof amount !== "number" || amount < 0) {
return res.status(400).json({ error: "Invalid income value" });
}

if (!month || typeof month !== "string") {
return res.status(400).json({ error: "Invalid month value" });
}

try {
const newIncome = await Income.create({month, amount});
res.status(201).json(newIncome);
} catch (error) {
res.status(400).json({ message: "Failed to add income", error: error.message });
}
});

// DELETE /income - Delete the income
router.delete("/:id", async (req, res) => {
const { id } = req.params;

if (!mongoose.Types.ObjectId.isValid(id)) {
return res.status(400).json({ error: "Invalid income ID" });
}
try {
const deletedIncome = await Income.findByIdAndDelete(id);
if (!deletedIncome) {
return res.status(404).json({ message: "Income not found" });
}
res.status(200).json({ message: "Income deleted successfully" });
} catch (error) {
console.error("Error deleting income:", error.message, error.stack);
return res.status(500).json({ error: "Server error" });
}
});

export default router;
57 changes: 57 additions & 0 deletions backend/routes/spendingsRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import express from "express";
import { Spendings } from "../models/Spendings.js";

const router = express.Router();

// POST - Add income
router.post("/", async (req, res) => {
const { category, amount } = req.body;

try {
let newSpending = await Spendings.create({ category, amount});
res.status(201).json(newSpending);
} catch (error) {
res.status(400).json({ message: "Failed to add spending", error: error.message });
}
});

// GET - Get all spendings
router.get("/", async (req, res) => {
try {
const spendings = await Spendings.find();
res.status(200).json(spendings);
} catch (error) {
res.status(500).json({ message: "Failed to fetch spendings", error: error.message });
}
});

// GET - Get total spendings
router.get("/total", async (req, res) => {
try {
const total = await Spendings.aggregate([
{ $group: {_id: null, totalAmount: { $sum: "$amount" } } },
]);
console.log("Total Spendings Calculation:", total);
res.status(200).json({ total: total[0]?.totalAmount || 0 });
} catch (error) {
console.error("Error Fetching Total Spendings:", error.message);
res.status(500).json({ message: "Failed to fetch total spendings", error: error.message });
}
});

router.delete("/:id", async (req, res) => {
const { id } = req.params;

try {
const deletedSpending = await Spendings.findByIdAndDelete(id);
if (!deletedSpending) {
return res.status(404).json({ message: "Spending not found" });
}
res.status(200).json({ message: "Spending deleted successfully" });
} catch (error) {
console.error("Error deleting spending:", error.message, error.stack);
return res.status(500).json({ error: "Server error" });
}
});

export default router;
131 changes: 131 additions & 0 deletions backend/routes/userRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import express from 'express';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import { User } from '../models/User.js';
import { authenticateUser } from '../middleware/authMiddleware.js';

const router = express.Router()
const JWT_SECRET = process.env.JWT_SECRET || 'your_secure_secret_key'; // Use environment variable for the secret key

// POST - Register a new user
router.post("/signup", async (req, res) => {
try {
const { name, email, password } = req.body

if (!name || !email || !password) {
return res.status(400).json({
success: false,
message: "Name, email, and password are required.",
});
}

const salt = bcrypt.genSaltSync()
const hashedPassword = bcrypt.hashSync(password, salt);
const user = new User({ name, email, password: hashedPassword
})

//await to not send response before database finished saving
await user.save()

// Generate a JWT token
const token = jwt.sign({ id: user._id, email: user.email }, JWT_SECRET, {
expiresIn: '1h', // Token expires in 1 hour
});

res.status(201).json({
success: true,
message: "User created successfully.",
response: {
id: user._id,
accessToken: token,
}
})

} catch (error) {
console.error("Signup error:", error)
res.status(400).json({
success: false,
message: "Failed to create user.",
response: error.message
})
}
})


// POST - Login route
router.post("/login", async (req, res) => {
try {
const { email, password } = req.body

if (!email || !password) {
return res.status(400).json({
success: false,
message: "Email and password are required.",
});
}

// Find user by email
const user = await User.findOne({ email })
if (!user) {
return res.status(404).json({
success: false,
message: "User not found with provided email.",
})
}

// Compare the provided password with the stored hashed password
const isPasswordValid = bcrypt.compareSync(password, user.password)
if (!isPasswordValid) {
return res.status(401).json({
success: false,
message: "Incorrect password. Please try again.",
})
}
// Generate a JWT token
const token = jwt.sign({ id: user._id, email: user.email }, JWT_SECRET, {
expiresIn: '1h', // Token expires in 1 hour
})

res.status(200).json({
success: true,
message: "Login successfull.",
response: {
id: user._id,
accessToken: token,
}
})

} catch (error) {
console.error("Login error:", error)
res.status(500).json({
success: false,
message: "Failed to login. Please try again.",
response: error.message
})
}
})

// GET - Protected dashboard route
router.get("/dashboard", authenticateUser, async (req, res) => {
try {
res.status(200).json({
success: true,
message: "User authenticated successfully.",
response: {
id: req.user._id,
name: req.user.name,
email: req.user.email,
}
})
} catch (error) {
console.error("Dashboard error:", error)
res.status(500).json({
success: false,
message: "Failed to access dashboard.",
response: error.message
})
}
})

export default router

Loading