├── .gitignore
├── .babelrc
├── client
├── .DS_Store
├── actions
│ ├── .DS_Store
│ └── actions.js
├── components
│ ├── Navbar.jsx
│ ├── App.jsx
│ ├── Login.jsx
│ ├── Doughnut.jsx
│ ├── Popup.jsx
│ ├── Signup.jsx
│ └── Chart.jsx
├── styles
│ ├── index.scss
│ ├── popup.scss
│ ├── login.scss
│ ├── signup.scss
│ └── mainpage.scss
├── reducers
│ ├── index.js
│ └── mainReducer.js
├── constants
│ └── actiontypes.js
├── store.js
├── index.html
├── index.js
└── container
│ └── mainPage.jsx
├── dist
├── index.html
└── bundle.js.LICENSE.txt
├── server
├── models
│ ├── budgetModel.js
│ ├── cipher.js
│ └── encrypt.js
├── routes
│ ├── users.js
│ └── transactions.js
├── controllers
│ ├── cryptoController.js
│ ├── usersController.js
│ └── transactionsController.js
└── server.js
├── package.json
├── postgres_create.sql
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env", "@babel/preset-react"]
3 | }
4 |
--------------------------------------------------------------------------------
/client/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/batfish-ecri36/Budget/HEAD/client/.DS_Store
--------------------------------------------------------------------------------
/client/actions/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/batfish-ecri36/Budget/HEAD/client/actions/.DS_Store
--------------------------------------------------------------------------------
/client/components/Navbar.jsx:
--------------------------------------------------------------------------------
1 | export default function Navbar() {
2 | return (
3 |
6 | )
7 | }
--------------------------------------------------------------------------------
/client/styles/index.scss:
--------------------------------------------------------------------------------
1 | #update-displaybox {
2 | position: fixed;
3 | background-color: blanchedalmond;
4 | top: 50px;
5 | right: 50px;
6 | height: 400px;
7 | width: 400px;
8 | }
9 |
10 |
--------------------------------------------------------------------------------
/client/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import mainReducer from './mainReducer';
4 |
5 | const reducers = combineReducers({
6 | budget: mainReducer,
7 | });
8 |
9 | export default reducers;
10 |
--------------------------------------------------------------------------------
/client/constants/actiontypes.js:
--------------------------------------------------------------------------------
1 | export const ADD_TRANSACTION = 'ADD_TRANSACTION';
2 | export const DELETE_TRANSACTION = 'DELETE_TRANSACTION';
3 | export const LOAD_TRANSACTIONS = 'LOAD_TRANSACTIONS';
4 | export const UPDATE_TRANSACTION = 'UPDATE_TRANSACTION';
5 | export const LOGIN = 'LOGIN';
6 |
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
Budget
--------------------------------------------------------------------------------
/client/store.js:
--------------------------------------------------------------------------------
1 | import reducers from './reducers/index.js';
2 | import { applyMiddleware } from 'redux';
3 | import { configureStore } from '@reduxjs/toolkit';
4 | import { composeWithDevTools } from 'redux-devtools-extension';
5 | import thunkMiddleware from 'redux-thunk';
6 |
7 | const store = configureStore({
8 | reducer: reducers,
9 | middleware: [thunkMiddleware],
10 | });
11 |
12 | export default store;
13 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Budget
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/server/models/budgetModel.js:
--------------------------------------------------------------------------------
1 | const { Pool } = require('pg');
2 |
3 | const PG_URI = 'postgres://aakfylry:vxroi9Csqtt8ybr4Tdmpky6kjjTIHdpz@peanut.db.elephantsql.com/aakfylry'
4 |
5 | const pool = new Pool({
6 | connectionString: PG_URI
7 | });
8 |
9 | module.exports = {
10 | query: (text, params, callback) => {
11 | console.log('executed query', text);
12 | return pool.query(text, params, callback);
13 | }
14 | };
--------------------------------------------------------------------------------
/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import App from './components/app.jsx';
4 | import { Provider } from 'react-redux';
5 | import store from './store';
6 | import { BrowserRouter } from 'react-router-dom';
7 | import Navbar from './components/Navbar.jsx';
8 |
9 | import styles from './styles/popup.scss';
10 | const container = document.getElementById('root');
11 | const root = ReactDOM.createRoot(container);
12 |
13 | root.render(
14 |
15 |
16 |
17 |
18 |
19 | );
20 |
--------------------------------------------------------------------------------
/client/styles/popup.scss:
--------------------------------------------------------------------------------
1 | #popup {
2 | position: fixed;
3 | background: rgba(244, 243, 243, 0.5);
4 | top: 0;
5 | left: 0;
6 | width: 100%;
7 | height: 100vh;
8 |
9 | display: flex;
10 | justify-content: center;
11 | align-items: center;
12 | }
13 |
14 | #popup-inner {
15 | position: relative;
16 | padding: 40px;
17 | background: rgb(255, 255, 255, 0.8);
18 | opacity: 100%;
19 | height: 100px;
20 | }
21 |
22 | .popup-inner .close-btn {
23 | top: 16px;
24 | right: 16px;
25 | }
26 |
27 | h3 {
28 | margin-bottom: 20px;
29 | color: #1d2523;
30 | font-weight: bold;
31 | }
32 |
33 |
34 |
--------------------------------------------------------------------------------
/client/actions/actions.js:
--------------------------------------------------------------------------------
1 | import * as types from '../constants/actiontypes';
2 |
3 | //delete
4 | export const deleteTransaction = (transaction) => ({
5 | type: types.DELETE_TRANSACTION,
6 | payload: transaction,
7 | });
8 |
9 | //create
10 | export const addTransaction = (transaction) => ({
11 | type: types.ADD_TRANSACTION,
12 | payload: transaction,
13 | });
14 |
15 | //update
16 | export const updateTransactions = (transaction) => ({
17 | type: types.UPDATE_TRANSACTION,
18 | payload: transaction,
19 | });
20 |
21 | //read
22 | export const loadTransactions = (transaction) => ({
23 | type: types.LOAD_TRANSACTIONS,
24 | payload: transaction,
25 | });
26 |
27 | export const login = (username) => ({
28 | type: types.LOGIN,
29 | payload: username,
30 | });
31 |
--------------------------------------------------------------------------------
/server/routes/users.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const usersController = require('../controllers/usersController');
3 | const cryptoController = require('../controllers/cryptoController');
4 | const router = express.Router();
5 |
6 | router.get('/', usersController.getUsers, (req, res) =>
7 | res.status(200).send(res.locals.users)
8 | );
9 |
10 | router.post(
11 | '/login',
12 | usersController.verifyUser,
13 | usersController.addTransactions,
14 | cryptoController.decryptData,
15 | (req, res) => {
16 | return res.status(200).json(res.locals.user);
17 | }
18 | );
19 |
20 | router.post('/signup', usersController.createUser,
21 | usersController.sendUser,
22 | (req, res) =>
23 | res.status(200).json(res.locals.user)
24 | );
25 |
26 |
27 | module.exports = router;
28 |
--------------------------------------------------------------------------------
/server/models/cipher.js:
--------------------------------------------------------------------------------
1 | const crypto = require('crypto');
2 |
3 | const cipher = {};
4 |
5 | cipher.stretchString = (s, salt, outputLength) => {
6 | return crypto.pbkdf2Sync(s, salt, 100000, outputLength, 'sha512');
7 | }
8 |
9 | cipher.keyFromPassword = (password) => {
10 | const keyPlusHashingSalt = cipher.stretchString(password, 'salt', 24 + 48);
11 | return {
12 | cipherKey: keyPlusHashingSalt.slice(0,24),
13 | hashingSalt: keyPlusHashingSalt.slice(24)
14 | };
15 | }
16 |
17 | cipher.encrypt = (key, sourceData) => {
18 | const iv = Buffer.alloc(16, 0); // Initialization vector
19 | const cipher = crypto.createCipheriv('aes-192-cbc', key.cipherKey, iv);
20 | let encrypted = cipher.update(sourceData, 'binary', 'binary');
21 | encrypted += cipher.final('binary');
22 | return encrypted;
23 | }
24 |
25 | cipher.decrypt = (key, encryptedData) => {
26 | const iv = Buffer.alloc(16, 0); // Initialization vector
27 | const decipher = crypto.createDecipheriv('aes-192-cbc', key.cipherKey, iv);
28 | let decrypted = decipher.update(encryptedData, 'binary', 'binary');
29 | decrypted += decipher.final('binary');
30 | return decrypted;
31 | }
32 |
33 | module.exports = cipher;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "scratch-project",
3 | "version": "1.0.0",
4 | "description": "codesmith scratch project",
5 | "main": "webpack.config.js",
6 | "scripts": {
7 | "start": "NODE_ENV=production nodemon server/server.js",
8 | "build": "NODE_ENV=production webpack",
9 | "dev": "NODE_ENV=development webpack serve --open & nodemon ./server/server.js"
10 | },
11 | "author": "andy,emily,peter,wendy",
12 | "license": "ISC",
13 | "dependencies": {
14 | "@reduxjs/toolkit": "^1.8.6",
15 | "axios": "^1.1.3",
16 | "bcrypt": "^5.1.0",
17 | "chart.js": "^3.9.1",
18 | "cors": "^2.8.5",
19 | "css-loader": "^6.7.1",
20 | "html-webpack-plugin": "^5.5.0",
21 | "node": "^19.0.0",
22 | "nodemon": "^2.0.20",
23 | "pg": "^8.8.0",
24 | "react-chartjs-2": "^4.3.1",
25 | "react-dom": "^18.2.0",
26 | "react-redux": "^8.0.4",
27 | "react-router-dom": "^6.4.2",
28 | "redux": "^4.2.0",
29 | "redux-devtools-extension": "^2.13.9",
30 | "redux-thunk": "^2.4.1",
31 | "sass": "^1.55.0",
32 | "sass-loader": "^13.1.0",
33 | "style-loader": "^3.3.1"
34 | },
35 | "devDependencies": {
36 | "@babel/core": "^7.19.6",
37 | "@babel/preset-env": "^7.19.4",
38 | "@babel/preset-react": "^7.18.6",
39 | "babel-loader": "^8.2.5",
40 | "webpack": "^5.74.0",
41 | "webpack-cli": "^4.10.0",
42 | "webpack-dev-server": "^4.11.1"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/server/models/encrypt.js:
--------------------------------------------------------------------------------
1 | const db = require('../models/budgetModel');
2 | const cipher = require('../models/cipher')
3 | const update = async () => {
4 | let admin = [1];
5 | let password;
6 | const userquery = `SELECT password from users where _id = $1`;
7 | await db.query(userquery, admin)
8 | .then((data) => {
9 | password = data.rows[0]['password'];
10 | })
11 | .catch((error) => {
12 | console.log("Error encryptData user query",error);
13 | })
14 | const key = cipher.keyFromPassword(password);
15 |
16 |
17 | let plaintext;
18 | user = [1]
19 | const text =`SELECT * FROM encrypted_transactions WHERE user_id=$1;`
20 | const result = await db.query(text, user)
21 | .then((data) => {
22 | plaintext = data.rows;
23 | })
24 | .catch((error) => {
25 | console.log("Error select query",error);
26 | })
27 |
28 |
29 | plaintext.forEach((element, i) => {
30 | setTimeout(() => {
31 | element.item = cipher.encrypt(key, element.item);
32 | let param = [element._id, element.item];
33 | let updatequery = `UPDATE encrypted_transactions set item=$2 where _id=$1`
34 | db.query(updatequery, param)
35 | .catch((error) => {
36 | console.log("Error encryptData user query",error);
37 | })
38 | }, i * 1000)
39 | })
40 |
41 | }
42 |
43 | update();
--------------------------------------------------------------------------------
/postgres_create.sql:
--------------------------------------------------------------------------------
1 | -- CREATE TABLE public.users (
2 | -- "_id" serial NOT NULL,
3 | -- "username" varchar NOT NULL,
4 | -- "password" varchar NOT NULL,
5 | -- "token" varchar,
6 | -- CONSTRAINT "users_pk" PRIMARY KEY ("_id")
7 | -- ) WITH (
8 | -- OIDS=FALSE
9 | -- );
10 |
11 | -- CREATE TABLE public.keys (
12 | -- "_id" serial NOT NULL,
13 | -- "user_id" int NOT NULL,
14 | -- "key" varchar NOT NULL,
15 | -- "uv" varchar NOT NULL,
16 | -- CONSTRAINT "keys_pk" PRIMARY KEY ("_id"),
17 | -- CONSTRAINT "keys_fk" FOREIGN KEY ("user_id") REFERENCES public.users("_id")
18 | -- ) WITH (
19 | -- OIDS=FALSE
20 | -- );
21 |
22 | -- CREATE TABLE public.transactions (
23 | -- "_id" serial NOT NULL,
24 | -- "user_id" int NOT NULL,
25 | -- "item" varchar NOT NULL,
26 | -- "amount" DECIMAL(19,4) not NULL,
27 | -- "date" DATE,
28 | -- "category" varchar NOT NULL,
29 | -- CONSTRAINT "transactions_pk" PRIMARY KEY ("_id"),
30 | -- CONSTRAINT "transactions_fk0" FOREIGN KEY ("user_id") REFERENCES public.users("_id")
31 | -- ) WITH (
32 | -- OIDS=FALSE
33 | -- );
34 |
35 | CREATE TABLE public.encrypted_transactions (
36 | "_id" serial NOT NULL,
37 | "user_id" int NOT NULL,
38 | "item" varchar NOT NULL,
39 | "amount" DECIMAL(19,4) not NULL,
40 | "date" DATE,
41 | "category" varchar NOT NULL,
42 | CONSTRAINT "encrypted_transactions_pk" PRIMARY KEY ("_id"),
43 | CONSTRAINT "encrypted_transactions_fk0" FOREIGN KEY ("user_id") REFERENCES public.users("_id")
44 | ) WITH (
45 | OIDS=FALSE
46 | );
47 |
48 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 | const e = require('express');
5 |
6 | module.exports = {
7 | mode: process.env.NODE_ENV,
8 | entry: './client/index.js',
9 | devServer: {
10 | host: 'localhost',
11 | port: 8080,
12 | historyApiFallback: true,
13 |
14 | proxy: {
15 | // '/signup/': 'http://localhost:3000/signup',
16 | // '/main/': 'http://localhost:3000/main',
17 | '/users': {
18 | target: 'http://localhost:8080/',
19 | router: () => 'http://localhost:3000',
20 | },
21 | '/transactions': {
22 | target: 'http://localhost:8080/',
23 | router: () => 'http://localhost:3000',
24 | }
25 | },
26 | },
27 | output: {
28 | path: path.resolve(__dirname, 'dist'),
29 | filename: 'bundle.js',
30 | },
31 | plugins: [
32 | new HtmlWebpackPlugin({
33 | template: path.resolve(__dirname, './client/index.html'),
34 | filename: 'index.html',
35 | }),
36 | ],
37 | module: {
38 | rules: [
39 | {
40 | test: /\.jsx?/,
41 | exclude: /node_modules/,
42 | use: {
43 | loader: 'babel-loader',
44 | options: { presets: ['@babel/preset-env', '@babel/preset-react'] },
45 | },
46 | },
47 | {
48 | test: /\.scss?/,
49 | exclude: /node_modules/,
50 | use: ['style-loader', 'css-loader', 'sass-loader'],
51 | },
52 | ],
53 | },
54 | };
55 |
--------------------------------------------------------------------------------
/server/controllers/cryptoController.js:
--------------------------------------------------------------------------------
1 | const db = require('../models/budgetModel');
2 | const cipher = require('../models/cipher');
3 |
4 | const cryptoController = {};
5 |
6 | cryptoController.encryptData = async (req, res, next) => {
7 | const user = [req.params.id];
8 | console.log(req.body);
9 | let password;
10 | const text = `SELECT password from users where _id = $1`;
11 | await db
12 | .query(text, user)
13 | .then((data) => {
14 | password = data.rows[0]['password'];
15 | })
16 | .catch((error) => {
17 | console.log('Error encryptData user query', error);
18 | });
19 |
20 | const key = cipher.keyFromPassword(password);
21 | req.body.item = cipher.encrypt(key, req.body.item);
22 |
23 | next();
24 | };
25 |
26 | cryptoController.decryptData = async (req, res, next) => {
27 | const user = [req.params.id];
28 | let password;
29 | const text = `SELECT password from users where _id = $1`;
30 | await db
31 | .query(text, user)
32 | .then((data) => {
33 | password = data.rows[0]['password'];
34 | })
35 | .catch((error) => {
36 | console.log('Error decryptData user query', error);
37 | });
38 |
39 | const key = cipher.keyFromPassword(password);
40 | res.locals.transactions.forEach((element) => {
41 | element.item = cipher.decrypt(key, element.item);
42 | });
43 | if (res.locals.user) {
44 | // console.log('decrypting locals data');
45 | res.locals.user[1] = res.locals.transactions;
46 | }
47 | next();
48 | };
49 |
50 | module.exports = cryptoController;
51 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const express = require('express');
3 | const cors = require('cors');
4 | const transactionsRouter = require('./routes/transactions.js');
5 | const userRouter = require('./routes/users');
6 |
7 | const app = express();
8 | const PORT = 3000;
9 |
10 | const corsOptions = {
11 | origin: '*',
12 | credentials: true,
13 | optionSuccessStatus: 200,
14 | };
15 | app.use(express.json());
16 | app.use(express.urlencoded({ extended: true }));
17 |
18 | app.use("/", express.static(path.resolve(__dirname, "/style.scss")));
19 | if (process.env.NODE_ENV) {
20 | app.use('/', express.static(path.join(__dirname, '../dist')));
21 | // app.get('/', (req, res) => {
22 | // return res.status(200).sendFile(path.join(__dirname, './index.html'));
23 | // });
24 | }
25 |
26 | app.use('/users', userRouter);
27 |
28 | app.use('/transactions', transactionsRouter);
29 |
30 | app.get('/main', (req, res) => {
31 | return res.status(200).sendFile(path.join(__dirname, '../dist/index.html'));
32 | });
33 |
34 | app.use((req, res) => res.status(404).send('No page found'));
35 |
36 | app.use((err, req, res, next) => {
37 | const defaultErr = {
38 | log: 'Express error handler caught unknown middleware error',
39 | status: 500,
40 | message: { err: 'An error occurred' },
41 | };
42 | const errorObj = Object.assign({}, defaultErr, err);
43 | console.log(errorObj.log);
44 | return res.status(errorObj.status).json(errorObj.message);
45 | });
46 |
47 | //start app on port
48 | app.listen(PORT, () => {
49 | console.log(`Server listening on port: ${PORT}...`);
50 | });
51 |
52 | module.exports = app;
53 |
--------------------------------------------------------------------------------
/client/components/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { BrowserRouter, Routes, Route, useNavigate } from 'react-router-dom';
3 | import Login from './Login.jsx';
4 | import { connect } from 'react-redux';
5 | import * as actions from '../actions/actions';
6 | import MainPage from '../container/mainPage.jsx';
7 | import Signup from './Signup.jsx';
8 | import MonthlyBarChart from './Chart.jsx';
9 | import DoughnutChart from './Doughnut.jsx';
10 |
11 | const mapStateToProps = (state) => ({
12 | user: state.budget.user,
13 | transactions: state.budget.transactions,
14 | });
15 |
16 | const mapDispatchToProps = (dispatch) => ({
17 | login: (user) => dispatch(actions.login(user)),
18 | addTrans: (transaction) => dispatch(actions.addTransaction(transaction)),
19 | deleteTrans: (transaction) =>
20 | dispatch(actions.deleteTransaction(transaction)),
21 | updateTrans: (transaction) =>
22 | dispatch(actions.updateTransactions(transaction)),
23 | });
24 |
25 | const App = (props) => {
26 | const navigate = useNavigate();
27 | const [authenticated, changeAuthenticated] = useState(false);
28 | return (
29 |
30 | }
33 | />
34 |
44 | }
45 | />
46 | } />
47 | } />
48 | } />
49 |
50 | );
51 | };
52 |
53 | export default connect(mapStateToProps, mapDispatchToProps)(App);
54 |
--------------------------------------------------------------------------------
/client/styles/login.scss:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Playfair+Display&display=swap');
2 |
3 | * {
4 | margin: 0;
5 | }
6 |
7 | body {
8 | font-family: 'Playfair Display', serif;
9 | color:#202124;
10 | background-color: #333333;
11 | }
12 |
13 | #login {
14 | display: flex;
15 | flex-direction: column;
16 | justify-content: center;
17 | align-items: center;
18 | text-align: center;
19 | min-height: 100vh;
20 | background-color: #333333;
21 | }
22 |
23 | #login h1 {
24 | color:#4be7b9;
25 | }
26 |
27 | #inner-login p {
28 | text-align: center;
29 | margin-bottom: 20px;
30 | }
31 |
32 | #inner-login {
33 | background-color: #222222;
34 | padding: 100px 150px 100px 150px;
35 | color:#fcfcfc;
36 | border-radius: 3%;
37 | box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
38 | }
39 |
40 | #inner-login h1 {
41 | margin-bottom: 40px;
42 | }
43 |
44 | #login-form {
45 | // display: flex;
46 | flex-direction: column;
47 | justify-content: center;
48 | align-items: center;
49 | }
50 |
51 | #login-form input {
52 | margin-top: 10px;
53 | width: 190px;
54 | height: 23px;
55 | border: none;
56 | border-radius: 3%;
57 | }
58 |
59 | #signup-div {
60 | display: flex;
61 | align-items: center;
62 | margin-top: 10px;
63 | }
64 |
65 | #signup-div button {
66 | margin-top: 6px;
67 | padding: 3px;
68 | width: 50px;
69 | border: none;
70 | border-radius: 5%;
71 | color: #4a4848;
72 | background-color:#4be7b9;
73 | }
74 |
75 | #signup-div button:hover {
76 | background-color: #34d2a3;
77 | }
78 |
79 | #signup-anchor {
80 | color: #4be7b9
81 | }
82 |
83 | #signup-anchor:hover{
84 | color: #34d2a3;
85 | }
86 | #signup-anchor:visited{
87 | color: #37b18c
88 | }
89 |
90 | th {
91 | width: 40px;
92 | height: 30px;
93 | padding-right: 100px;
94 | }
95 |
--------------------------------------------------------------------------------
/client/styles/signup.scss:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Playfair+Display&display=swap');
2 |
3 | * {
4 | margin: 0;
5 | }
6 |
7 | body {
8 | font-family: 'Playfair Display', serif;
9 | color:#202124;
10 | }
11 |
12 | #signup {
13 | display: flex;
14 | flex-direction: column;
15 | justify-content: center;
16 | align-items: center;
17 | text-align: center;
18 | min-height: 100vh;
19 | background-color: #333333;
20 |
21 | }
22 |
23 | #signup h1 {
24 | color:#4be7b9;
25 | }
26 |
27 | #inner-signup p {
28 | text-align: center;
29 | margin-bottom: 20px;
30 | }
31 |
32 | #inner-signup {
33 | background-color: #222222;
34 | padding: 100px 150px 100px 150px;
35 | color:#fcfcfc;
36 | border-radius: 3%;
37 | box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
38 | }
39 |
40 | #inner-signup h1 {
41 | margin-bottom: 40px;
42 | }
43 |
44 | #signup-form {
45 | display: flex;
46 | flex-direction: column;
47 | justify-content: center;
48 | align-items: center;
49 | }
50 |
51 | #signup-form input {
52 | margin-top: 10px;
53 | width: 190px;
54 | height: 23px;
55 | border: none;
56 | border-radius: 3%;
57 | }
58 |
59 | // #signup-form input:hover {
60 | // box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
61 | // }
62 |
63 |
64 | #signup-div {
65 | display: flex;
66 | justify-content: space-between;
67 | align-items: center;
68 | margin-top: 10px;
69 | }
70 |
71 | button {
72 | margin-top: 15px;
73 | margin-left: 130px;
74 | padding: 3px;
75 | width: 60px;
76 | border: none;
77 | border-radius: 5%;
78 | color: #4a4848;
79 | background-color: #4be7b9;
80 | }
81 |
82 | button:hover {
83 | background-color:#34d2a3;
84 | cursor: pointer;
85 | }
86 |
87 | #login-anchor {
88 | color: #4be7b9
89 | }
90 |
91 | #login-anchor:hover{
92 | color: #34d2a3;
93 | }
94 |
95 | #login-anchor:visited{
96 | color: #37b18c
97 | }
98 |
99 |
--------------------------------------------------------------------------------
/server/routes/transactions.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 |
3 | const transactionsController = require('../controllers/transactionsController');
4 | const cryptoController = require('../controllers/cryptoController');
5 |
6 | const router = express.Router();
7 |
8 | //fixed the endpoint to request an endpoint with an id
9 | router.get(
10 | '/:id',
11 | transactionsController.getTransactions,
12 | cryptoController.decryptData,
13 | (req, res) => {
14 | res.status(200).json(res.locals.transactions);
15 | }
16 | );
17 |
18 | router.get(
19 | '/custom/:id',
20 | transactionsController.getCustomTransactions,
21 | (req, res) => {
22 | res.status(200).json(res.locals.transactions);
23 | }
24 | );
25 |
26 | router.get(
27 | '/weekly/:id',
28 | transactionsController.getWeeklyTransactions,
29 | (req, res) => {
30 | res.status(200).json(res.locals.transactions);
31 | }
32 | );
33 | router.get(
34 | '/monthly/:id',
35 | transactionsController.getMonthlyTransactions,
36 | (req, res) => {
37 | res.status(200).json(res.locals.transactions);
38 | }
39 | );
40 |
41 | router.get(
42 | '/quarterly/:id',
43 | transactionsController.getQuarterlyTransactions,
44 | (req, res) => {
45 | res.status(200).json(res.locals.transactions);
46 | }
47 | );
48 | router.get(
49 | '/yearly/:id',
50 | transactionsController.getYearlyTransactions,
51 | (req, res) => {
52 | res.status(200).json(res.locals.transactions);
53 | }
54 | );
55 |
56 | //fixed the endpoint to request an endpoint with an id
57 | router.post(
58 | '/:id',
59 | cryptoController.encryptData,
60 | transactionsController.addTransactions,
61 | (req, res) => res.status(200).send('added')
62 | );
63 |
64 | router.put(
65 | '/:id',
66 | cryptoController.encryptData,
67 | transactionsController.updateTransactions,
68 | (req, res) => res.status(200).json(res.locals.transactions)
69 | );
70 |
71 | router.delete('/:id', transactionsController.deleteTransactions, (req, res) =>
72 | res.status(200).send('deleted')
73 | );
74 |
75 | module.exports = router;
76 |
--------------------------------------------------------------------------------
/client/components/Login.jsx:
--------------------------------------------------------------------------------
1 | const React = require('react');
2 | import { useState } from 'react';
3 | import { connect } from 'react-redux';
4 | import axios from 'axios';
5 | import style from '../styles/login.scss';
6 |
7 | // const navigate = useNavigate();
8 |
9 | const sendUser = async (user, pass, dispatch, navigate) => {
10 | const userData = { username: user, password: pass };
11 | const response = await axios.post('/users/login', userData);
12 | // all the data associated with that username
13 | const data = response.data;
14 | console.log(data, 'data');
15 | // create route to main page
16 | navigate('/main');
17 | dispatch(data);
18 | return;
19 | };
20 |
21 | const Login = (props) => {
22 | return (
23 |
24 |
25 |
Stop Spending All Me Money
26 | {/*
Wendy and Emily are the best
*/}
27 |
Login
28 |
29 |
30 |
31 |
32 |
33 |
34 | Signup
35 |
36 |
55 |
56 |
57 |
58 | );
59 | };
60 |
61 | export default Login;
62 |
--------------------------------------------------------------------------------
/client/reducers/mainReducer.js:
--------------------------------------------------------------------------------
1 | import * as types from '../constants/actiontypes';
2 |
3 | const initialState = {
4 | transactions: [],
5 | user: '',
6 | };
7 |
8 | const mainReducer = (state = initialState, action) => {
9 | let transactions;
10 |
11 | switch (action.type) {
12 | case types.LOGIN: {
13 | console.log(action.payload, 'payload');
14 | const user = action.payload[0];
15 | transactions = action.payload[1];
16 | return { ...state, user, transactions };
17 | }
18 | case types.ADD_TRANSACTION: {
19 | transactions = state.transactions.slice();
20 | transactions.push(action.payload);
21 | transactions = transactions.sort((a, b) => {
22 | return new Date(a.date) - new Date(b.date);
23 | });
24 | return {
25 | ...state,
26 | transactions,
27 | };
28 | }
29 | case types.DELETE_TRANSACTION: {
30 | transactions = state.transactions.slice();
31 | let cut;
32 | for (let i = 0; i < transactions.length; i++) {
33 | if (transactions[i]._id === action.payload) {
34 | cut = i;
35 | }
36 | }
37 | transactions = transactions
38 | .slice(0, cut)
39 | .concat(transactions.slice(cut + 1));
40 | return {
41 | ...state,
42 | transactions,
43 | };
44 | }
45 | case types.UPDATE_TRANSACTION: {
46 | transactions = state.transactions.slice();
47 | console.log(action.payload);
48 | let current;
49 | for (let i = 0; i < transactions.length; i++) {
50 | if (transactions[i]._id === action.payload._id) {
51 | current = i;
52 | }
53 | }
54 | const updated = Object.assign({}, transactions[current], action.payload);
55 | transactions[current] = updated;
56 | transactions = transactions.sort((a, b) => {
57 | return new Date(a.date) - new Date(b.date);
58 | });
59 | return {
60 | ...state,
61 | transactions,
62 | };
63 | }
64 | default: {
65 | return { ...state };
66 | }
67 | }
68 | };
69 |
70 | export default mainReducer;
71 |
--------------------------------------------------------------------------------
/client/components/Doughnut.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js";
3 | import { Doughnut } from "react-chartjs-2";
4 |
5 | ChartJS.register(ArcElement, Tooltip, Legend);
6 |
7 |
8 | const DoughnutChart = ({transactions}) => {
9 | const transData = {};
10 | const trans = transactions.forEach(item => {
11 | if(!transData.hasOwnProperty(item.category)){
12 | transData[item.category] = Number(item.amount)
13 | }
14 | transData[item.category] += Number(item.amount)
15 | })
16 | const label = Object.keys(transData);
17 | const tableData = Object.values(transData);
18 |
19 | console.log('label:',label)
20 | console.log('data:', tableData);
21 |
22 | const data = {
23 | labels: label,
24 | datasets: [
25 | {
26 | label: "# of Votes",
27 | data: tableData,
28 | backgroundColor: [
29 | "rgb(255, 99, 132)",
30 | "rgb(54, 162, 235)",
31 | "rgb(255, 206, 86)",
32 | "rgb(75, 192, 192)",
33 | "rgb(153, 102, 255)",
34 | "rgb(255, 159, 64)",
35 | ],
36 | borderColor: [
37 | "rgba(255, 99, 132, 1)",
38 | "rgba(54, 162, 235, 1)",
39 | "rgba(255, 206, 86, 1)",
40 | "rgba(75, 192, 192, 1)",
41 | "rgba(153, 102, 255, 1)",
42 | "rgba(255, 159, 64, 1)",
43 | ],
44 | borderWidth: 1,
45 | },
46 | ],
47 | };
48 |
49 | const options = {
50 | responsive: true,
51 | plugins: {
52 | legend: {
53 | labels: {
54 | color: 'rgb(53,162,235)',
55 | },
56 | position: "top",
57 | },
58 | title: {
59 | display: true,
60 | text: "Transaction Summary",
61 | color:'rgb(53,162,235)',
62 | },
63 | },
64 | };
65 |
66 | return (
67 |
68 |
Doughnut Chart Display
69 |
70 |
71 | );
72 | };
73 |
74 | export default DoughnutChart;
75 |
--------------------------------------------------------------------------------
/client/components/Popup.jsx:
--------------------------------------------------------------------------------
1 | const React = require('react');
2 | import axios from 'axios';
3 |
4 | const Popup = (props) => {
5 | console.log(props.data);
6 |
7 | const updateExpense = async (data) => {
8 | try {
9 | const item = document.getElementById('update-item').value;
10 | const category = document.getElementById('update-cat').value;
11 | const date = document.getElementById('update-date').value;
12 | const amount = document.getElementById('update-amt').value;
13 | const sendObj = {};
14 | if (item) {
15 | sendObj.item = item;
16 | }
17 | if (category) {
18 | sendObj.category = category;
19 | }
20 | if (date) {
21 | sendObj.date = date;
22 | }
23 | if (amount) {
24 | sendObj.amount = amount;
25 | }
26 | data = Object.assign({}, data, sendObj);
27 | console.log(data, 'hello');
28 | console.log(data.user_id, 'id');
29 | const response = await axios.put(`/transactions/${data.user_id}`, data);
30 | props.update(data);
31 | } catch (error) {
32 | console.log(error);
33 | }
34 | };
35 |
36 | return props.trigger ? (
37 |
69 | ) : (
70 | ''
71 | );
72 | };
73 |
74 | export default Popup;
75 |
--------------------------------------------------------------------------------
/client/styles/mainpage.scss:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Playfair+Display&display=swap');
2 |
3 | * {
4 | margin: 0;
5 | font-weight: bold;
6 | }
7 |
8 | h1, p {
9 | color:#4be7b9;
10 | }
11 |
12 | body {
13 | font-family: 'Playfair Display', serif;
14 | // background-color: #333333
15 | }
16 |
17 | #mainPage h1 {
18 | background-color: #222222;
19 | padding: 30px;
20 | font-size: 45px;
21 | text-align: center;
22 | align-content: center;
23 | box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
24 | }
25 |
26 | #siteName {
27 | display: flex;
28 | justify-content: space-around;
29 | align-items: center;
30 | }
31 |
32 | #mainPage{
33 | justify-content: space-between;
34 | align-items: center;
35 | }
36 |
37 | #trans-form {
38 | margin-top: 50px;
39 | overflow-y: scroll;
40 | height: 800px;
41 | }
42 |
43 | #charts {
44 | display: flex;
45 | flex-direction: column;
46 | justify-content: space-between;
47 | height: 600px;
48 | margin-right: 40px;
49 | margin-top: -190px;
50 | }
51 |
52 | #expense div {
53 | margin-left: 50px;
54 | }
55 |
56 | #expense-table th {
57 | color:#4be7b9;
58 | }
59 |
60 | #updateform {
61 | display: flex;
62 | flex-direction: column;
63 | margin-right: 10px;
64 | padding: 25px 50px;
65 | }
66 |
67 | input {
68 | width: 200px;
69 | height: 20px;
70 | margin-top: 10px;
71 | border: none;
72 | border-radius: 2%;
73 | margin-right: 5px;
74 | // box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
75 | }
76 |
77 | #submit {
78 | margin-top: 6px;
79 | padding: 4px;
80 | width: 55px;
81 | height: 22px;
82 | border: none;
83 | border-radius: 5%;
84 | color: #4a4848;
85 | background-color:#4be7b9;
86 | }
87 |
88 | #submit:hover {
89 | background-color: #34d2a3;
90 | box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px;
91 |
92 | }
93 |
94 | #trans-form button {
95 | margin-left: 10px;
96 | }
97 |
98 | table {
99 | border-collapse: collapse;
100 | table-layout: fixed;
101 |
102 | }
103 |
104 | td {
105 | color: #4be7b9;
106 | padding-left: 35px;
107 | }
108 |
--------------------------------------------------------------------------------
/client/components/Signup.jsx:
--------------------------------------------------------------------------------
1 | const React = require('react');
2 | import { useState } from 'react';
3 | import style from '../styles/signup.scss';
4 | import axios from 'axios';
5 |
6 | const createUser = async (user, pass) => {
7 | const response = await axios.post('/users/signup', {
8 | username: user,
9 | password: pass,
10 | });
11 | console.log(response.data);
12 | };
13 |
14 | const Signup = (props) => {
15 | return (
16 |
17 |
18 |
Stop Spending All Me Money
19 |
Signup
20 |
72 |
73 |
74 | );
75 | };
76 |
77 | export default Signup;
78 |
--------------------------------------------------------------------------------
/dist/bundle.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*!
2 | * @kurkle/color v0.2.1
3 | * https://github.com/kurkle/color#readme
4 | * (c) 2022 Jukka Kurkela
5 | * Released under the MIT License
6 | */
7 |
8 | /*!
9 | * Chart.js v3.9.1
10 | * https://www.chartjs.org
11 | * (c) 2022 Chart.js Contributors
12 | * Released under the MIT License
13 | */
14 |
15 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
16 |
17 | /**
18 | * @license React
19 | * react-dom.production.min.js
20 | *
21 | * Copyright (c) Facebook, Inc. and its affiliates.
22 | *
23 | * This source code is licensed under the MIT license found in the
24 | * LICENSE file in the root directory of this source tree.
25 | */
26 |
27 | /**
28 | * @license React
29 | * react-is.production.min.js
30 | *
31 | * Copyright (c) Facebook, Inc. and its affiliates.
32 | *
33 | * This source code is licensed under the MIT license found in the
34 | * LICENSE file in the root directory of this source tree.
35 | */
36 |
37 | /**
38 | * @license React
39 | * react.production.min.js
40 | *
41 | * Copyright (c) Facebook, Inc. and its affiliates.
42 | *
43 | * This source code is licensed under the MIT license found in the
44 | * LICENSE file in the root directory of this source tree.
45 | */
46 |
47 | /**
48 | * @license React
49 | * scheduler.production.min.js
50 | *
51 | * Copyright (c) Facebook, Inc. and its affiliates.
52 | *
53 | * This source code is licensed under the MIT license found in the
54 | * LICENSE file in the root directory of this source tree.
55 | */
56 |
57 | /**
58 | * @license React
59 | * use-sync-external-store-shim.production.min.js
60 | *
61 | * Copyright (c) Facebook, Inc. and its affiliates.
62 | *
63 | * This source code is licensed under the MIT license found in the
64 | * LICENSE file in the root directory of this source tree.
65 | */
66 |
67 | /**
68 | * @license React
69 | * use-sync-external-store-shim/with-selector.production.min.js
70 | *
71 | * Copyright (c) Facebook, Inc. and its affiliates.
72 | *
73 | * This source code is licensed under the MIT license found in the
74 | * LICENSE file in the root directory of this source tree.
75 | */
76 |
77 | /**
78 | * @remix-run/router v1.0.2
79 | *
80 | * Copyright (c) Remix Software Inc.
81 | *
82 | * This source code is licensed under the MIT license found in the
83 | * LICENSE.md file in the root directory of this source tree.
84 | *
85 | * @license MIT
86 | */
87 |
88 | /**
89 | * React Router v6.4.2
90 | *
91 | * Copyright (c) Remix Software Inc.
92 | *
93 | * This source code is licensed under the MIT license found in the
94 | * LICENSE.md file in the root directory of this source tree.
95 | *
96 | * @license MIT
97 | */
98 |
99 | /** @license React v16.13.1
100 | * react-is.production.min.js
101 | *
102 | * Copyright (c) Facebook, Inc. and its affiliates.
103 | *
104 | * This source code is licensed under the MIT license found in the
105 | * LICENSE file in the root directory of this source tree.
106 | */
107 |
--------------------------------------------------------------------------------
/client/components/Chart.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import {
3 | Chart as ChartJS,
4 | CategoryScale,
5 | LinearScale,
6 | BarElement,
7 | Title,
8 | Tooltip,
9 | Legend,
10 | } from "chart.js";
11 | import { Bar } from "react-chartjs-2";
12 | import axios from "axios";
13 |
14 | ChartJS.register(
15 | CategoryScale,
16 | LinearScale,
17 | BarElement,
18 | Title,
19 | Tooltip,
20 | Legend
21 | );
22 |
23 | //dataset's data would be pull in from the backend
24 |
25 | const MonthlyBarChart = ({ transactions }) => {
26 | //not necessary
27 | // const [chartData, setChartData] = useState({
28 | // datasets: [],
29 | // });
30 |
31 | // const [data, setData] = useState([]);
32 |
33 | // //data will fetch when page loads
34 | // useEffect(() => {
35 | // async (data) => {
36 | // const response = await axios.get(
37 | // "http://localhost:3000/transactions/",
38 | // data
39 | // );
40 | // if (response.status === 200) {
41 | // setData(data);
42 | // }
43 | // };
44 | // }, []);
45 |
46 | const convertDate = (date) => {
47 | const toConvert = new Date(date);
48 | let converted = toConvert.toLocaleDateString("en-US", { timeZone: "UTC" });
49 | return converted;
50 | };
51 |
52 | const transData = {};
53 |
54 | // const addToTrans = transactions.forEach(item => {
55 | // if(!transData.hasOwnProperty(item.date)){
56 | // transData[convertDate(item.date)] = Number(item.amount);
57 | // } else {
58 | // transData[convertDate(item.date)] = +Number(item.amount);
59 | // }
60 | // })
61 | //had to do two forEach in order to add value to the property || not sure why the first method didn't work
62 | const setDate = transactions.forEach((item) => {
63 | if (!transData.hasOwnProperty(item.date)) {
64 | transData[convertDate(item.date)] = 0;
65 | }
66 | });
67 | const setAmount = transactions.forEach((item) => {
68 | transData[convertDate(item.date)] += Number(item.amount);
69 | });
70 |
71 | const labels = Object.keys(transData);
72 | const tableData = Object.values(transData);
73 |
74 | const [chartOptions, setChartOptions] = useState({});
75 |
76 | //data has to be a one dimensional array
77 | //in this case we can show the monthly total spending
78 | const options = {
79 | responsive: true,
80 | plugins: {
81 | legend: {
82 | labels: {
83 | color: 'rgb(53,162,235)',
84 | },
85 | position: "bottom",
86 | },
87 | title: {
88 | display: true,
89 | text: "Transaction Summary",
90 | color:'rgb(53,162,235)',
91 | },
92 | },
93 | scales: {
94 | y: {
95 | ticks: {color: '#4be7b9'}
96 | },
97 | x: {
98 | ticks: {color: '#4be7b9'}
99 | }
100 | }
101 | };
102 |
103 | const barData = {
104 | labels: labels,
105 | datasets: [
106 | {
107 | label: "Daily Spending",
108 | data: tableData,
109 | borderColor: "rgb(53,162,235)",
110 | backgroundColor: "rgb(52,162,235)",
111 | },
112 | ],
113 | }
114 |
115 | return (
116 |
117 |
Bar Chart Display
118 |
119 |
120 | );
121 | };
122 |
123 | export default MonthlyBarChart;
124 |
--------------------------------------------------------------------------------
/server/controllers/usersController.js:
--------------------------------------------------------------------------------
1 | const db = require('../models/budgetModel');
2 | const bcrypt = require('bcrypt');
3 |
4 | const usersController = {};
5 |
6 | usersController.getUsers = async (req, res, next) => {
7 | const text = `SELECT * FROM users`;
8 | const login = await db
9 | .query(text)
10 | .then((data) => {
11 | res.locals.user = data.rows;
12 | })
13 | .catch((error) => {
14 | console.log('Error getUser', error);
15 | });
16 |
17 | next();
18 | };
19 |
20 | usersController.addTransactions = async (req, res, next) => {
21 | const text = `SELECT * FROM encrypted_transactions WHERE user_id=${res.locals.userID} ORDER BY date ASC`;
22 |
23 | const transactions = await db
24 | .query(text)
25 | .then((data) => {
26 | // console.log(res.locals.user);
27 | res.locals.user.push(data.rows);
28 | // console.log(res.locals.user);
29 | res.locals.transactions = data.rows;
30 | req.params.id = res.locals.userID;
31 | })
32 | .catch((error) => {
33 | next(error);
34 | });
35 |
36 | next();
37 | };
38 |
39 | usersController.verifyUser = async (req, res, next) => {
40 | const array = [req.body.username];
41 | console.log(array);
42 | const text = `SELECT _id, username, password, token FROM users WHERE username=$1`;
43 | const login = await db
44 | .query(text, array)
45 | .then((data) => {
46 | const hash = data.rows[0].password;
47 | const correctPassword = bcrypt.compare(
48 | req.body.password,
49 | hash,
50 | function (err, result) {
51 | if (data.rows.length > 0 && result) {
52 | res.locals.user = [data.rows[0]];
53 | res.locals.userID = data.rows[0]._id;
54 | next();
55 | } else {
56 | console.log('wrong password or username');
57 | next(err);
58 | }
59 | }
60 | );
61 | })
62 | .catch((error) => {
63 | console.log('Error getUser', error);
64 | next(error);
65 | });
66 |
67 | //next();
68 | };
69 |
70 | usersController.createUser = async (req, res, next) => {
71 | console.log(req);
72 | const saltRounds = 10;
73 | const password = req.body.password;
74 | bcrypt.genSalt(saltRounds, function (err, salt) {
75 | bcrypt.hash(password, salt, function (err, hash) {
76 | const user = {
77 | username: req.body.username,
78 | password: hash,
79 | token: req.body.token,
80 | };
81 |
82 | res.locals.username = user.username;
83 | console.log('aa');
84 | console.log(res.locals.username);
85 | const newEncryptedUser = [];
86 |
87 | for (let key in user) {
88 | newEncryptedUser.push(user[key]);
89 | }
90 |
91 | console.log(newEncryptedUser);
92 | res.locals.username = req.body.username;
93 |
94 | const text = `INSERT INTO users (username, password, token) VALUES ($1, $2, $3)`;
95 | db.query(text, newEncryptedUser, (err, res) => {
96 | if (err) {
97 | next(err);
98 | } else {
99 | console.log('added to users database encrypted');
100 | }
101 | });
102 |
103 | next();
104 | });
105 | });
106 |
107 | //next();
108 | };
109 |
110 | usersController.sendUser = async (req, res, next) => {
111 | const username = [req.body.username];
112 | const text = `SELECT * FROM users WHERE username=$1`;
113 | const result = await db
114 | .query(text, username)
115 | .then((data) => {
116 | res.locals.user = [data.rows[0]];
117 | res.locals.user.push([]);
118 | })
119 | .catch((error) => {
120 | console.log('Error getTransactions', error);
121 | });
122 |
123 | next();
124 | };
125 |
126 | module.exports = usersController;
127 |
--------------------------------------------------------------------------------
/server/controllers/transactionsController.js:
--------------------------------------------------------------------------------
1 | const db = require('../models/budgetModel');
2 |
3 | const transactionsController = {};
4 |
5 | transactionsController.addTransactions = (req, res, next) => {
6 | //add params id so we can target which user to add the expense to
7 | const { id } = req.params;
8 | const { item, amount, date, category } = req.body;
9 | //adding the id as the first element to newTransactions
10 | const newTransactions = [id, item, amount, date, category];
11 | console.log(newTransactions);
12 | const text = `INSERT INTO encrypted_transactions (user_id, item, amount, date, category) VALUES ($1, $2, $3, $4, $5)`;
13 | db.query(text, newTransactions, (err, res) => {
14 | if (err) {
15 | console.log(err.stack);
16 | } else {
17 | console.log('added to database');
18 | }
19 | });
20 | next();
21 | };
22 |
23 | transactionsController.getTransactions = async (req, res, next) => {
24 | //fixed the query string to accept passed in params
25 | const id = req.params.id;
26 | //cannot use req.body because we can't send a response body from the front
27 | //end with a get request (only from a post request)
28 |
29 | // const queryString = req.body.user_id;
30 |
31 |
32 | const text =`SELECT * FROM encrypted_transactions WHERE user_id=${id}
33 | ORDER BY date ASC;`
34 | const result = await db.query(text)
35 | .then((data) => {
36 | res.locals.transactions = data.rows;
37 | })
38 | .catch((error) => {
39 | console.log("Error getTransactions",error);
40 | })
41 |
42 | next();
43 | };
44 |
45 | transactionsController.getCustomTransactions = async (req, res, next) => {
46 | const id = req.params.id;
47 | const { time, category } = req.body;
48 |
49 | const text = `
50 | select date_trunc('${time}', date) as ${time}, SUM(amount) AS total_${time}_net_expense
51 | from encrypted_transactions
52 | WHERE user_id = ${id} and category='${category}'
53 | group by ${time}
54 | order by ${time} ASC;`;
55 |
56 | const result = await db
57 | .query(text)
58 | .then((data) => {
59 | res.locals.transactions = data.rows;
60 | })
61 | .catch((error) => {
62 | console.log('Error getTransactions', error);
63 | });
64 |
65 | next();
66 | };
67 |
68 | transactionsController.getMonthlyTransactions = async (req, res, next) => {
69 | const id = req.params.id;
70 |
71 | //const { time, category } = req.body;
72 |
73 | const text = `
74 | select date_trunc('month', date) as month, SUM(amount) AS total_month_net_expense
75 | from encrypted_transactions
76 | WHERE user_id = ${id}
77 | group by month
78 | order by month ASC;`;
79 |
80 | const result = await db
81 | .query(text)
82 | .then((data) => {
83 | res.locals.transactions = data.rows;
84 | })
85 | .catch((error) => {
86 | console.log('Error getTransactions', error);
87 | });
88 |
89 | next();
90 | };
91 |
92 | transactionsController.getQuarterlyTransactions = async (req, res, next) => {
93 | const id = req.params.id;
94 |
95 | const text = `
96 | select date_trunc('quarter', date) as quarter, SUM(amount) AS total_quarter_net_expense
97 | from encrypted_transactions
98 | WHERE user_id = ${id}
99 | group by quarter
100 | order by quarter ASC;`;
101 |
102 | const result = await db
103 | .query(text)
104 | .then((data) => {
105 | res.locals.transactions = data.rows;
106 | })
107 | .catch((error) => {
108 | console.log('Error getTransactions', error);
109 | });
110 |
111 | next();
112 | };
113 |
114 | transactionsController.getWeeklyTransactions = async (req, res, next) => {
115 | const id = req.params.id;
116 |
117 | const text = `
118 | select date_trunc('week', date) as week, SUM(amount) AS total_week_net_expense
119 | from encrypted_transactions
120 | WHERE user_id = ${id}
121 | group by week
122 | order by week ASC;`;
123 |
124 | const result = await db
125 | .query(text)
126 | .then((data) => {
127 | res.locals.transactions = data.rows;
128 | })
129 | .catch((error) => {
130 | console.log('Error getTransactions', error);
131 | });
132 |
133 | next();
134 | };
135 |
136 | transactionsController.getYearlyTransactions = async (req, res, next) => {
137 | const id = req.params.id;
138 |
139 | const text = `
140 | select date_trunc('year', date) as year, SUM(amount) AS total_year_net_expense
141 | from encrypted_transactions
142 | WHERE user_id = ${id}
143 | group by year
144 | order by year ASC;`;
145 |
146 | const result = await db
147 | .query(text)
148 | .then((data) => {
149 | res.locals.transactions = data.rows;
150 | })
151 | .catch((error) => {
152 | console.log('Error getTransactions', error);
153 | });
154 |
155 | next();
156 | };
157 |
158 | transactionsController.deleteTransactions = async (req, res, next) => {
159 | const { id } = req.params;
160 | const { item, date } = req.body;
161 | const text = `DELETE FROM encrypted_transactions WHERE _id=${id}`;
162 | const result = await db
163 | .query(text)
164 | .then((data) => {
165 | res.locals.transactions = data;
166 | })
167 | .catch((error) => {
168 | console.log('Error delete transactions', error);
169 | });
170 |
171 | next();
172 | };
173 |
174 | transactionsController.updateTransactions = async (req, res, next) => {
175 | const { item, amount, date, category, _id } = req.body;
176 | const text = `UPDATE transactions SET item = '${item}', amount = '${amount}', date = '${date}', category = '${category}' WHERE _id = ${_id}`;
177 | const result = await db
178 | .query(text)
179 | .then((data) => {
180 | res.locals.transactions = data;
181 | })
182 | .catch((error) => {
183 | console.log('Error updateTransactions', error);
184 | });
185 |
186 | next();
187 | };
188 |
189 | module.exports = transactionsController;
190 |
--------------------------------------------------------------------------------
/client/container/mainPage.jsx:
--------------------------------------------------------------------------------
1 | const React = require('react');
2 | import { useState, useEffect } from 'react';
3 | import Popup from '../components/Popup.jsx';
4 | import axios from 'axios';
5 | import MonthlyBarChart from '../components/Chart.jsx';
6 | import DoughnutChart from '../components/Doughnut.jsx';
7 | import styles from '../styles/mainpage.scss';
8 |
9 | const MainPage = (props) => {
10 | const [newData, setNewData] = useState({
11 | item: '',
12 | category: '',
13 | amount: '',
14 | date: '',
15 | });
16 |
17 | // const [display, setDisplay] = useState('all');
18 | //will find a way to access user id, but for now i hard coded it
19 | const id = props.user.id;
20 |
21 | // post request to add new transactions to the database
22 | const addExpense = async (data) => {
23 | try {
24 | const response = await axios.post(
25 | `/transactions/${props.user._id}`,
26 | data
27 | );
28 | if (response.status === 200) {
29 | window.alert('Expense added successfully!');
30 | }
31 | props.addTrans(data);
32 | const arrBox = Array.from(document.getElementsByClassName('addbox'));
33 | arrBox.forEach((ele) => {
34 | ele.value = '';
35 | });
36 | } catch (err) {
37 | console.log(err);
38 | }
39 | };
40 |
41 | //handleSubmit form to invoke addExpense function (post request) passing in the new state
42 | const handleSubmit = (e) => {
43 | e.preventDefault();
44 | if (
45 | !e.target[0].value ||
46 | !e.target[1].value ||
47 | !e.target[3].value ||
48 | !e.target[2].value
49 | ) {
50 | window.alert('Please provide value into each input fields.');
51 | } else {
52 | addExpense(newData);
53 | }
54 | };
55 |
56 | // handle input onChange
57 | const handleInputChange = (e) => {
58 | const target = {};
59 | target[e.target.id] = e.target.value;
60 | // let { name, value } = e.target;
61 | const newObj = Object.assign({}, newData, target);
62 | setNewData(newObj);
63 | };
64 |
65 | const convertDate = (date) => {
66 | const toConvert = new Date(date);
67 | let converted = toConvert.toLocaleDateString('en-US', { timeZone: 'UTC' });
68 | return converted;
69 | };
70 |
71 | const shorten = (amount) => {
72 | const cut = amount.indexOf('.');
73 | if (cut === -1) {
74 | return amount + '.00';
75 | }
76 | return amount.slice(0, cut + 3);
77 | };
78 |
79 | const deleteExpense = async (data) => {
80 | try {
81 | const response = await axios.delete(`/transactions/${data}`, {
82 | data,
83 | });
84 | props.deleteTrans(data);
85 | } catch (error) {
86 | console.log(error);
87 | }
88 | };
89 |
90 | const [buttonPopup, setButtonPopup] = useState(false);
91 | const [updateData, setUpdateData] = useState({});
92 |
93 | return (
94 |
95 |
Stop Spending All Me Money
96 |
97 |
250 |
251 |
252 |
253 |
254 |
255 |
256 | );
257 | };
258 | export default MainPage;
259 |
260 | {
261 | /* ; */
266 | }
267 |
--------------------------------------------------------------------------------