├── .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 |
21 | 27 | 33 | 39 |
40 | 41 | Back to Login 42 | 43 | 70 |
71 |
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 |
98 | 104 |

POPUP

105 |
106 | {/*

Peter and Andy are great too!

*/} 107 |
108 |

Add New Expense

109 | 118 | 129 | 138 | 146 | 147 |
148 |
153 | 154 | 155 | 156 | 166 | 176 | 186 | 196 | 206 | 207 | 208 |
164 | Expense: 165 | 174 | Amount: 175 | 184 | Category: 185 | 194 | Date: 195 | 204 | Action: 205 |
209 | 210 | {props.transactions && 211 | props.transactions.map((item, index) => { 212 | return ( 213 | 214 | 215 | {item.item} 216 | 217 | 218 | ${shorten(item.amount)} 219 | 220 | {item.category} 221 | 222 | {convertDate(item.date)} 223 | 224 | 228 | 236 | 243 | 244 | 245 | ); 246 | })} 247 | 248 |
249 |
250 |
251 | 252 | 253 |
254 |
255 |
256 | ); 257 | }; 258 | export default MainPage; 259 | 260 | { 261 | /* ; */ 266 | } 267 | --------------------------------------------------------------------------------