diff --git a/package-lock.json b/package-lock.json index 7caf384..2efb4ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1317,6 +1317,11 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" }, + "@scarf/scarf": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.0.5.tgz", + "integrity": "sha512-9WKaGVpQH905Aqkk+BczFEeLQxS07rl04afFRPUG9IcSlOwmo5EVVuuNu0d4M9LMYucObvK0LoAe+5HfMW2QhQ==" + }, "@sheerun/mutationobserver-shim": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz", @@ -4889,6 +4894,11 @@ "next-tick": "~1.0.0" } }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" + }, "es6-iterator": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", @@ -6595,9 +6605,9 @@ "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=" }, "http-proxy": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz", - "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "requires": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", @@ -9368,9 +9378,9 @@ "integrity": "sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ==" }, "node-sass": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.13.1.tgz", - "integrity": "sha512-TTWFx+ZhyDx1Biiez2nB0L3YrCZ/8oHagaDalbuBSlqXgUPsdkUSzJsVxeDO9LtPB49+Fh3WQl3slABo6AotNw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.14.1.tgz", + "integrity": "sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g==", "requires": { "async-foreach": "^0.1.3", "chalk": "^1.1.1", @@ -9386,7 +9396,7 @@ "node-gyp": "^3.8.0", "npmlog": "^4.0.0", "request": "^2.88.0", - "sass-graph": "^2.2.4", + "sass-graph": "2.2.5", "stdout-stream": "^1.4.0", "true-case-path": "^1.0.2" }, @@ -11654,6 +11664,18 @@ "warning": "^4.0.2" } }, + "react-redux": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.0.tgz", + "integrity": "sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA==", + "requires": { + "@babel/runtime": "^7.5.5", + "hoist-non-react-statics": "^3.3.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^16.9.0" + } + }, "react-resize-detector": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-2.3.0.tgz", @@ -11953,6 +11975,36 @@ "balanced-match": "^1.0.0" } }, + "redux": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz", + "integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==", + "requires": { + "loose-envify": "^1.4.0", + "symbol-observable": "^1.2.0" + } + }, + "redux-form": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/redux-form/-/redux-form-8.3.6.tgz", + "integrity": "sha512-PfDdi+9JtNdr29Pv+9123TiojJlFB6+qczCRfP++cVT4nO2tEv4tDJHSV1l/Ol3Qh9z2cRg1QeLMhShNtRZ3+g==", + "requires": { + "@babel/runtime": "^7.9.2", + "@scarf/scarf": "^1.0.5", + "es6-error": "^4.1.1", + "hoist-non-react-statics": "^3.3.2", + "invariant": "^2.2.4", + "is-promise": "^2.1.0", + "lodash": "^4.17.15", + "prop-types": "^15.6.1", + "react-is": "^16.4.2" + } + }, + "redux-thunk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz", + "integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==" + }, "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", @@ -12418,219 +12470,14 @@ "integrity": "sha512-vTxrZz4dX5W86M6oVWVdOVe72ZiPs41Oi7Z6Km4W5Turyz28mrXSJhhEBZoRtzJWIv3833WKVwLSDWWkEfupMg==" }, "sass-graph": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", - "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.5.tgz", + "integrity": "sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag==", "requires": { "glob": "^7.0.0", "lodash": "^4.0.0", "scss-tokenizer": "^0.2.3", - "yargs": "^7.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "requires": { - "invert-kv": "^1.0.0" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "requires": { - "lcid": "^1.0.0" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "requires": { - "error-ex": "^1.2.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - } - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "requires": { - "is-utf8": "^0.2.0" - } - }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - } - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" - }, - "yargs": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", - "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", - "requires": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.0" - } - }, - "yargs-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", - "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", - "requires": { - "camelcase": "^3.0.0" - } - } + "yargs": "^13.3.2" } }, "sass-loader": { @@ -13697,6 +13544,11 @@ "util.promisify": "~1.0.0" } }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" + }, "symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", diff --git a/package.json b/package.json index 746c5b3..c91c647 100644 --- a/package.json +++ b/package.json @@ -8,14 +8,18 @@ "@testing-library/user-event": "^7.2.1", "dexie": "^2.0.4", "jwt-decode": "^2.2.0", - "node-sass": "^4.13.1", + "node-sass": "^4.14.1", "react": "^16.13.1", "react-csv": "^2.0.3", "react-datepicker": "^2.14.1", "react-dom": "^16.13.1", + "react-redux": "^7.2.0", "react-router-dom": "^5.2.0", "react-scripts": "3.4.1", - "recharts": "^1.8.5" + "recharts": "^1.8.5", + "redux": "^4.0.5", + "redux-form": "^8.3.6", + "redux-thunk": "^2.3.0" }, "scripts": { "start": "react-scripts start", diff --git a/src/AddExpenseForm.js b/src/AddExpenseForm.js index 687a036..7533ff1 100644 --- a/src/AddExpenseForm.js +++ b/src/AddExpenseForm.js @@ -1,33 +1,64 @@ import React, { Component } from 'react'; +import { connect } from 'react-redux'; + +import { addExpense } from './actions'; import Calendar from './Calendar'; import './styles/AddExpenseForm.css'; -export default class AddExpenseForm extends Component { +class AddExpenseForm extends Component { + constructor(props) { + super(props); + this.state = { + expense_name: '', + price: '', + category: '', + paid_to: '', + startDate: new Date(), + }; + } + handleChange = (e) => { e.preventDefault(); - this.props.handleFormChange(e); + this.setState({ + [e.target.name]: e.target.value, + }); + }; + + clearForm = () => { + this.setState({ + expense_name: '', + price: '', + category: '', + paid_to: '', + }); + }; + + setCalendar = (date) => { + this.setState({ + startDate: date, + }); }; handleSubmit = (e) => { e.preventDefault(); - this.props.addExpense(); + const { expense_name, price, category, paid_to, startDate } = this.state; + + this.props.addExpense(expense_name, price, category, paid_to, startDate); + this.props.toggleAddForm(); + this.clearForm(); }; render() { - const { - startDate, - setCalendar, - expense_name, - price, - category, - paid_to, - } = this.props; + const { expense_name, price, category, paid_to } = this.state; return (
- +
{ + return { + startDate: state.startDate, + }; +}; + +export default connect(mapStateToProps, { addExpense })(AddExpenseForm); diff --git a/src/Calendar.js b/src/Calendar.js index e2788d1..a709fbe 100644 --- a/src/Calendar.js +++ b/src/Calendar.js @@ -1,19 +1,26 @@ import React, { Component } from 'react'; +import { connect } from 'react-redux'; + import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; +import { setCalendar } from './actions'; import './styles/Calendar.css'; // CSS Modules, react-datepicker-cssmodules.css // import 'react-datepicker/dist/react-datepicker-cssmodules.css'; -export default class Calendar extends Component { +class Calendar extends Component { handleChange = (date) => { this.props.setCalendar(date); }; render() { const { startDate } = this.props; + console.log('startDate from Redux: ', startDate); + return ; } } + +export default Calendar; diff --git a/src/Dashboard.js b/src/Dashboard.js index 665b4ea..f6ec9ea 100644 --- a/src/Dashboard.js +++ b/src/Dashboard.js @@ -1,8 +1,10 @@ import React, { Component } from 'react'; - +import { connect } from 'react-redux'; import axios from 'axios'; import jwt from 'jwt-decode'; +import { getCurrentUser, logout, getAllExpenses } from './actions'; + import Expenses from './Expenses'; import Login from './Login'; import SignUp from './SignUp'; @@ -16,22 +18,12 @@ import './styles/Dashboard.css'; const API_URL = process.env.REACT_APP_API_URL; -export default class Dashboard extends Component { +class Dashboard extends Component { constructor(props) { super(props); this.state = { - email: '', - username: '', - password: '', allExpenses: [], - expenses: [], - currentUser: '', - startDate: new Date(), - expense_name: '', - price: '', - category: '', - paid_to: '', graphData: [], toggleAddExpense: false, toggleEditOn: false, @@ -58,8 +50,8 @@ export default class Dashboard extends Component { try { const token = JSON.parse(localStorage.getItem('token')); if (token) { - this.getCurrentUser(); - this.getAllExpenses(); + this.props.getCurrentUser(); + this.props.getAllExpenses(); this.getIncome(); this.hasLoaded(); } else { @@ -73,6 +65,7 @@ export default class Dashboard extends Component { componentDidUpdate() { console.log('Component Updated'); + // this.props.getCurrentUser(); } componentWillUnmount() {} @@ -85,86 +78,86 @@ export default class Dashboard extends Component { console.log(result); }; - getCurrentUser = () => { - try { - const token = JSON.parse(localStorage.getItem('token')); - if (token) { - const decodedToken = jwt(token); - const decodedUser = decodedToken.username; - const expireDate = decodedToken.exp; - const currentTime = Date.now() / 1000; - - //If token is expired, remove token from localstorage & set - //currentUser to '' - if (expireDate < currentTime) { - this.logout(); - } else { - this.setState({ - currentUser: decodedUser, - }); - } - } - } catch (e) { - console.log(e); - } - }; + // getCurrentUser = () => { + // try { + // const token = JSON.parse(localStorage.getItem('token')); + // if (token) { + // const decodedToken = jwt(token); + // const decodedUser = decodedToken.username; + // const expireDate = decodedToken.exp; + // const currentTime = Date.now() / 1000; + + // //If token is expired, remove token from localstorage & set + // //currentUser to '' + // if (expireDate < currentTime) { + // this.logout(); + // } else { + // this.setState({ + // currentUser: decodedUser, + // }); + // } + // } + // } catch (e) { + // console.log(e); + // } + // }; handleFormChange = (event) => { this.setState({ [event.target.name]: event.target.value }); }; - getAllExpenses = async () => { - try { - const token = JSON.parse(localStorage.getItem('token')); - - //Data from API - const result = await axios({ - method: 'get', - url: `${API_URL}/expenses`, - headers: { Authorization: `Bearer ${token}` }, - }); - - // Save prices for graphs - const prices = result.data.map((val) => { - const obj = {}; - const date = new Date(val.expense_date * 1000); - - obj['price'] = Number(val.price); - obj['date'] = `${date}`.slice(0, 15); - return obj; - }); - - //Format data to be saved to CSV - const csvData = result.data.map((val) => { - let obj = {}; - let dateObj = new Date(val.expense_date * 1000); - let year = dateObj.getFullYear().toString(); - let month = (dateObj.getMonth() + 1).toString(); - let day = dateObj.getDate().toString(); - - obj['expense_name'] = val.expense_name; - obj['price'] = val.price; - obj['category'] = val.category; - obj['paid_to'] = val.paid_to; - obj['expense_date'] = `${month}-${day}-${year}`; - - return obj; - }); - - this.setState( - { - csvData: csvData, - graphData: [...prices], - expenses: [...result.data], - allExpenses: [...result.data], - startDate: new Date(), - }, - this.hasLoaded() - ); - } catch (e) { - console.log(e); - } - }; + // getAllExpenses = async () => { + // try { + // const token = JSON.parse(localStorage.getItem('token')); + + // //Data from API + // const result = await axios({ + // method: 'get', + // url: `${API_URL}/expenses`, + // headers: { Authorization: `Bearer ${token}` }, + // }); + + // // Save prices for graphs + // const prices = result.data.map((val) => { + // const obj = {}; + // const date = new Date(val.expense_date * 1000); + + // obj['price'] = Number(val.price); + // obj['date'] = `${date}`.slice(0, 15); + // return obj; + // }); + + // //Format data to be saved to CSV + // const csvData = result.data.map((val) => { + // let obj = {}; + // let dateObj = new Date(val.expense_date * 1000); + // let year = dateObj.getFullYear().toString(); + // let month = (dateObj.getMonth() + 1).toString(); + // let day = dateObj.getDate().toString(); + + // obj['expense_name'] = val.expense_name; + // obj['price'] = val.price; + // obj['category'] = val.category; + // obj['paid_to'] = val.paid_to; + // obj['expense_date'] = `${month}-${day}-${year}`; + + // return obj; + // }); + + // this.setState( + // { + // csvData: csvData, + // graphData: [...prices], + // expenses: [...result.data], + // allExpenses: [...result.data], + // startDate: new Date(), + // }, + // this.hasLoaded() + // ); + // } catch (e) { + // console.log(e); + // } + // }; //Get all expenses for a particular year getYearExpenses = async (year) => { @@ -334,100 +327,59 @@ export default class Dashboard extends Component { } }; - clearLoginForm = () => { - console.log('clearLoginForm'); - this.setState({ - username: '', - password: '', - }); - }; - - login = async () => { - console.log('logggin in'); - try { - const result = await axios({ - method: 'post', - url: `${API_URL}/users/login`, - data: { - username: this.state.username, - password: this.state.password, - }, - }); - - const token = result.data.token; - localStorage.setItem('token', JSON.stringify(token)); - - //Get expenses/currentUser after loggin in - this.getCurrentUser(); - this.getAllExpenses(); - this.getIncome(); - // this.clearLoginForm(); - - const loginErr = result['data']['message']; - if (loginErr) { - console.log('Theres a login err'); - this.setState({ - loginError: loginErr, - // username: '', - // password: '', - }); - } else { - // Use username/password state only for handling input - console.log('There are no login errors'); - this.setState({ - loginError: '', - username: '', - password: '', - }); - } - } catch (e) { - console.log(e); - } - }; - - logout = () => { - this.setState({ - currentUser: '', - toggleAddExpense: false, - }); - localStorage.removeItem('token'); - - //Will clear the expenses since invalid credentials - this.getAllExpenses(); - }; - - addExpense = async () => { - try { - const token = JSON.parse(localStorage.getItem('token')); - - const result = await axios({ - method: 'post', - url: `${API_URL}/expenses`, - data: { - expense_name: this.state.expense_name, - price: this.state.price, - category: this.state.category, - paid_to: this.state.paid_to, - expense_date: this.state.startDate.getTime() / 1000, - }, - headers: { Authorization: `Bearer ${token}` }, - }); - - this.getAllExpenses(); - - //Clear inputs after adding expense - this.setState({ - toggleAddExpense: false, - expense_name: '', - price: '', - category: '', - paid_to: '', - startDate: '', - }); - } catch (e) { - console.log(e); - } - }; + // login = async () => { + // console.log('logggin in'); + // try { + // const result = await axios({ + // method: 'post', + // url: `${API_URL}/users/login`, + // data: { + // username: this.state.username, + // password: this.state.password, + // }, + // }); + + // const token = result.data.token; + // localStorage.setItem('token', JSON.stringify(token)); + + // //Get expenses/currentUser after loggin in + // this.getCurrentUser(); + // this.getAllExpenses(); + // this.getIncome(); + // // this.clearLoginForm(); + + // const loginErr = result['data']['message']; + // if (loginErr) { + // console.log('Theres a login err'); + // this.setState({ + // loginError: loginErr, + // // username: '', + // // password: '', + // }); + // } else { + // // Use username/password state only for handling input + // console.log('There are no login errors'); + // this.setState({ + // loginError: '', + // username: '', + // password: '', + // }); + // } + // } catch (e) { + // console.log(e); + // } + // }; + + // logout = () => { + // this.setState({ + // // currentUser: '', + // toggleAddExpense: false, + // }); + // localStorage.removeItem('token'); + + // //Will clear the expenses since invalid credentials + // // this.getAllExpenses(); + // }; //Make API call to create an account createAccount = async () => { @@ -447,8 +399,8 @@ export default class Dashboard extends Component { localStorage.setItem('token', JSON.stringify(token)); //Get expenses/currentUser after loggin in - this.getCurrentUser(); - this.getAllExpenses(); + this.props.getCurrentUser(); + // this.getAllExpenses(); this.setState({ emai: '', @@ -457,12 +409,6 @@ export default class Dashboard extends Component { }); }; - setCalendar = (date) => { - this.setState({ - startDate: date, - }); - }; - //Move logic to state formatGraphData = () => { const data = this.state.expenses.map((val) => { @@ -474,22 +420,6 @@ export default class Dashboard extends Component { }); }; - deleteExpense = async (expenseId) => { - try { - const token = JSON.parse(localStorage.getItem('token')); - - const result = await axios({ - method: 'delete', - url: `${API_URL}/expenses/${expenseId}`, - headers: { Authorization: `Bearer ${token}` }, - }); - - this.getAllExpenses(); - } catch (e) { - console.log(e); - } - }; - toggleAddForm = () => { this.setState({ toggleAddExpense: !this.state.toggleAddExpense, @@ -502,115 +432,6 @@ export default class Dashboard extends Component { }); }; - //EDIT EXPENSE NAME - editExpenseName = async (expenseId, expenseName) => { - try { - const token = JSON.parse(localStorage.getItem('token')); - - const result = await axios({ - method: 'patch', - url: `${API_URL}/expenses/${expenseId}`, - data: { - expense_name: expenseName, - }, - headers: { Authorization: `Bearer ${token}` }, - }); - - this.getAllExpenses(); - - //Clear inputs after adding expense - } catch (e) { - console.log(e); - } - }; - - editExpensePrice = async (expenseId, price) => { - try { - const token = JSON.parse(localStorage.getItem('token')); - - const result = await axios({ - method: 'patch', - url: `${API_URL}/expenses/${expenseId}`, - data: { - price: price, - }, - headers: { Authorization: `Bearer ${token}` }, - }); - - this.getAllExpenses(); - - //Clear inputs after adding expense - } catch (e) { - console.log(e); - } - }; - - editExpenseCategory = async (expenseId, category) => { - try { - const token = JSON.parse(localStorage.getItem('token')); - - const result = await axios({ - method: 'patch', - url: `${API_URL}/expenses/${expenseId}`, - data: { - category: category, - }, - headers: { Authorization: `Bearer ${token}` }, - }); - - this.getAllExpenses(); - - //Clear inputs after adding expense - } catch (e) { - console.log(e); - } - }; - - editExpensePaidTo = async (expenseId, paid_to) => { - try { - const token = JSON.parse(localStorage.getItem('token')); - - const result = await axios({ - method: 'patch', - url: `${API_URL}/expenses/${expenseId}`, - data: { - paid_to: paid_to, - }, - headers: { Authorization: `Bearer ${token}` }, - }); - - this.getAllExpenses(); - - //Clear inputs after adding expense - } catch (e) { - console.log(e); - } - }; - - //This code is not executing - editExpenseDate = async (expenseId, time) => { - //Time is correct up here - try { - const token = JSON.parse(localStorage.getItem('token')); - - //Something is getting blocked here - const result = await axios({ - method: 'patch', - url: `${API_URL}/expenses/${expenseId}`, - data: { - expense_date: time.getTime() / 1000, - }, - headers: { Authorization: `Bearer ${token}` }, - }); - - this.getAllExpenses(); - - //Clear inputs after adding expense - } catch (e) { - console.log(e); - } - }; - toggleDropdownMenu = () => { this.setState({ showDropdownMenu: !this.state.showDropdownMenu, @@ -820,23 +641,20 @@ export default class Dashboard extends Component { return ( <> {this.state.isLoading ? ( - ) : this.state.currentUser ? ( + ) : this.props.auth.currentUser ? ( // ) : this.state.showSignupForm ? ( { + return { + auth: state.auth, + expenses: state.expenses, + }; +}; + +export default connect(mapStateToProps, { + getCurrentUser, + logout, + getAllExpenses, +})(Dashboard); diff --git a/src/ExpenseApp.js b/src/ExpenseApp.js index 9144c58..1c27816 100644 --- a/src/ExpenseApp.js +++ b/src/ExpenseApp.js @@ -1,8 +1,10 @@ import React, { Component } from 'react'; - +import { connect } from 'react-redux'; import axios from 'axios'; import jwt from 'jwt-decode'; +import { getCurrentUser, getAllExpenses, logout } from './actions'; + import Expenses from './Expenses'; import Login from './Login'; import SignUp from './SignUp'; @@ -14,22 +16,12 @@ import './styles/ExpenseApp.css'; const API_URL = process.env.REACT_APP_API_URL; -export default class ExpenseApp extends Component { +class ExpenseApp extends Component { constructor(props) { super(props); this.state = { - email: '', - username: '', - password: '', allExpenses: [], - expenses: [], - currentUser: '', - startDate: new Date(), - expense_name: '', - price: '', - category: '', - paid_to: '', graphData: [], toggleAddExpense: false, toggleEditOn: false, @@ -55,9 +47,10 @@ export default class ExpenseApp extends Component { console.log('Component Mounted'); try { const token = JSON.parse(localStorage.getItem('token')); + console.log(token); if (token) { - this.getCurrentUser(); - this.getAllExpenses(); + this.props.getCurrentUser(); + this.props.getAllExpenses(); this.getIncome(); this.hasLoaded(); } else { @@ -83,86 +76,86 @@ export default class ExpenseApp extends Component { console.log(result); }; - getCurrentUser = () => { - try { - const token = JSON.parse(localStorage.getItem('token')); - if (token) { - const decodedToken = jwt(token); - const decodedUser = decodedToken.username; - const expireDate = decodedToken.exp; - const currentTime = Date.now() / 1000; - - //If token is expired, remove token from localstorage & set - //currentUser to '' - if (expireDate < currentTime) { - this.logout(); - } else { - this.setState({ - currentUser: decodedUser, - }); - } - } - } catch (e) { - console.log(e); - } - }; + // getCurrentUser = () => { + // try { + // const token = JSON.parse(localStorage.getItem('token')); + // if (token) { + // const decodedToken = jwt(token); + // const decodedUser = decodedToken.username; + // const expireDate = decodedToken.exp; + // const currentTime = Date.now() / 1000; + + // //If token is expired, remove token from localstorage & set + // //currentUser to '' + // if (expireDate < currentTime) { + // this.logout(); + // } else { + // this.setState({ + // currentUser: decodedUser, + // }); + // } + // } + // } catch (e) { + // console.log(e); + // } + // }; handleFormChange = (event) => { this.setState({ [event.target.name]: event.target.value }); }; - getAllExpenses = async () => { - try { - const token = JSON.parse(localStorage.getItem('token')); - - //Data from API - const result = await axios({ - method: 'get', - url: `${API_URL}/expenses`, - headers: { Authorization: `Bearer ${token}` }, - }); - - // Save prices for graphs - const prices = result.data.map((val) => { - const obj = {}; - const date = new Date(val.expense_date * 1000); - - obj['price'] = Number(val.price); - obj['date'] = `${date}`.slice(0, 15); - return obj; - }); - - //Format data to be saved to CSV - const csvData = result.data.map((val) => { - let obj = {}; - let dateObj = new Date(val.expense_date * 1000); - let year = dateObj.getFullYear().toString(); - let month = (dateObj.getMonth() + 1).toString(); - let day = dateObj.getDate().toString(); - - obj['expense_name'] = val.expense_name; - obj['price'] = val.price; - obj['category'] = val.category; - obj['paid_to'] = val.paid_to; - obj['expense_date'] = `${month}-${day}-${year}`; - - return obj; - }); - - this.setState( - { - csvData: csvData, - graphData: [...prices], - expenses: [...result.data], - allExpenses: [...result.data], - startDate: new Date(), - }, - this.hasLoaded() - ); - } catch (e) { - console.log(e); - } - }; + // getAllExpenses = async () => { + // try { + // const token = JSON.parse(localStorage.getItem('token')); + + // //Data from API + // const result = await axios({ + // method: 'get', + // url: `${API_URL}/expenses`, + // headers: { Authorization: `Bearer ${token}` }, + // }); + + // // Save prices for graphs + // const prices = result.data.map((val) => { + // const obj = {}; + // const date = new Date(val.expense_date * 1000); + + // obj['price'] = Number(val.price); + // obj['date'] = `${date}`.slice(0, 15); + // return obj; + // }); + + // //Format data to be saved to CSV + // const csvData = result.data.map((val) => { + // let obj = {}; + // let dateObj = new Date(val.expense_date * 1000); + // let year = dateObj.getFullYear().toString(); + // let month = (dateObj.getMonth() + 1).toString(); + // let day = dateObj.getDate().toString(); + + // obj['expense_name'] = val.expense_name; + // obj['price'] = val.price; + // obj['category'] = val.category; + // obj['paid_to'] = val.paid_to; + // obj['expense_date'] = `${month}-${day}-${year}`; + + // return obj; + // }); + + // this.setState( + // { + // csvData: csvData, + // graphData: [...prices], + // expenses: [...result.data], + // allExpenses: [...result.data], + // startDate: new Date(), + // }, + // this.hasLoaded() + // ); + // } catch (e) { + // console.log(e); + // } + // }; //Get all expenses for a particular year getYearExpenses = async (year) => { @@ -340,48 +333,48 @@ export default class ExpenseApp extends Component { }); }; - login = async () => { - console.log('logggin in'); - try { - const result = await axios({ - method: 'post', - url: `${API_URL}/users/login`, - data: { - username: this.state.username, - password: this.state.password, - }, - }); - - const token = result.data.token; - localStorage.setItem('token', JSON.stringify(token)); - - //Get expenses/currentUser after loggin in - this.getCurrentUser(); - this.getAllExpenses(); - this.getIncome(); - // this.clearLoginForm(); - - const loginErr = result['data']['message']; - if (loginErr) { - console.log('Theres a login err'); - this.setState({ - loginError: loginErr, - // username: '', - // password: '', - }); - } else { - // Use username/password state only for handling input - console.log('There are no login errors'); - this.setState({ - loginError: '', - username: '', - password: '', - }); - } - } catch (e) { - console.log(e); - } - }; + // login = async () => { + // console.log('logggin in'); + // try { + // const result = await axios({ + // method: 'post', + // url: `${API_URL}/users/login`, + // data: { + // username: this.state.username, + // password: this.state.password, + // }, + // }); + + // const token = result.data.token; + // localStorage.setItem('token', JSON.stringify(token)); + + // //Get expenses/currentUser after loggin in + // this.getCurrentUser(); + // this.getAllExpenses(); + // this.getIncome(); + // // this.clearLoginForm(); + + // const loginErr = result['data']['message']; + // if (loginErr) { + // console.log('Theres a login err'); + // this.setState({ + // loginError: loginErr, + // // username: '', + // // password: '', + // }); + // } else { + // // Use username/password state only for handling input + // console.log('There are no login errors'); + // this.setState({ + // loginError: '', + // username: '', + // password: '', + // }); + // } + // } catch (e) { + // console.log(e); + // } + // }; logout = () => { this.setState({ @@ -394,39 +387,6 @@ export default class ExpenseApp extends Component { this.getAllExpenses(); }; - addExpense = async () => { - try { - const token = JSON.parse(localStorage.getItem('token')); - - const result = await axios({ - method: 'post', - url: `${API_URL}/expenses`, - data: { - expense_name: this.state.expense_name, - price: this.state.price, - category: this.state.category, - paid_to: this.state.paid_to, - expense_date: this.state.startDate.getTime() / 1000, - }, - headers: { Authorization: `Bearer ${token}` }, - }); - - this.getAllExpenses(); - - //Clear inputs after adding expense - this.setState({ - toggleAddExpense: false, - expense_name: '', - price: '', - category: '', - paid_to: '', - startDate: '', - }); - } catch (e) { - console.log(e); - } - }; - //Make API call to create an account createAccount = async () => { const result = await axios({ @@ -455,12 +415,6 @@ export default class ExpenseApp extends Component { }); }; - setCalendar = (date) => { - this.setState({ - startDate: date, - }); - }; - //Move logic to state formatGraphData = () => { const data = this.state.expenses.map((val) => { @@ -472,22 +426,6 @@ export default class ExpenseApp extends Component { }); }; - deleteExpense = async (expenseId) => { - try { - const token = JSON.parse(localStorage.getItem('token')); - - const result = await axios({ - method: 'delete', - url: `${API_URL}/expenses/${expenseId}`, - headers: { Authorization: `Bearer ${token}` }, - }); - - this.getAllExpenses(); - } catch (e) { - console.log(e); - } - }; - toggleAddForm = () => { this.setState({ toggleAddExpense: !this.state.toggleAddExpense, @@ -500,115 +438,6 @@ export default class ExpenseApp extends Component { }); }; - //EDIT EXPENSE NAME - editExpenseName = async (expenseId, expenseName) => { - try { - const token = JSON.parse(localStorage.getItem('token')); - - const result = await axios({ - method: 'patch', - url: `${API_URL}/expenses/${expenseId}`, - data: { - expense_name: expenseName, - }, - headers: { Authorization: `Bearer ${token}` }, - }); - - this.getAllExpenses(); - - //Clear inputs after adding expense - } catch (e) { - console.log(e); - } - }; - - editExpensePrice = async (expenseId, price) => { - try { - const token = JSON.parse(localStorage.getItem('token')); - - const result = await axios({ - method: 'patch', - url: `${API_URL}/expenses/${expenseId}`, - data: { - price: price, - }, - headers: { Authorization: `Bearer ${token}` }, - }); - - this.getAllExpenses(); - - //Clear inputs after adding expense - } catch (e) { - console.log(e); - } - }; - - editExpenseCategory = async (expenseId, category) => { - try { - const token = JSON.parse(localStorage.getItem('token')); - - const result = await axios({ - method: 'patch', - url: `${API_URL}/expenses/${expenseId}`, - data: { - category: category, - }, - headers: { Authorization: `Bearer ${token}` }, - }); - - this.getAllExpenses(); - - //Clear inputs after adding expense - } catch (e) { - console.log(e); - } - }; - - editExpensePaidTo = async (expenseId, paid_to) => { - try { - const token = JSON.parse(localStorage.getItem('token')); - - const result = await axios({ - method: 'patch', - url: `${API_URL}/expenses/${expenseId}`, - data: { - paid_to: paid_to, - }, - headers: { Authorization: `Bearer ${token}` }, - }); - - this.getAllExpenses(); - - //Clear inputs after adding expense - } catch (e) { - console.log(e); - } - }; - - //This code is not executing - editExpenseDate = async (expenseId, time) => { - //Time is correct up here - try { - const token = JSON.parse(localStorage.getItem('token')); - - //Something is getting blocked here - const result = await axios({ - method: 'patch', - url: `${API_URL}/expenses/${expenseId}`, - data: { - expense_date: time.getTime() / 1000, - }, - headers: { Authorization: `Bearer ${token}` }, - }); - - this.getAllExpenses(); - - //Clear inputs after adding expense - } catch (e) { - console.log(e); - } - }; - toggleDropdownMenu = () => { this.setState({ showDropdownMenu: !this.state.showDropdownMenu, @@ -724,7 +553,7 @@ export default class ExpenseApp extends Component { this.setState({ isLoading: false, }); - }, 500); + }, 0); }; clearLoginErr = () => { @@ -745,40 +574,32 @@ export default class ExpenseApp extends Component { }; render() { + console.log('Loading: ', this.state.isLoading); return ( <> {this.state.isLoading ? ( - ) : this.state.currentUser ? ( + ) : this.props.auth.currentUser ? ( ) : this.state.showSignupForm ? ( { + return { + auth: state.auth, + expenses: state.expenses, + }; +}; + +export default connect(mapStateToProps, { + getCurrentUser, + getAllExpenses, + logout, +})(ExpenseApp); diff --git a/src/ExpenseRow.js b/src/ExpenseRow.js index e559a42..24ac01b 100644 --- a/src/ExpenseRow.js +++ b/src/ExpenseRow.js @@ -1,9 +1,20 @@ import React, { Component } from 'react'; +import { connect } from 'react-redux'; + +import { + deleteExpense, + editExpenseName, + editExpensePrice, + editExpenseCategory, + editExpensePaidTo, + editExpenseDate, +} from './actions'; + import './styles/ExpenseRow.css'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -export default class Expense extends Component { +class ExpenseRow extends Component { constructor(props) { super(props); @@ -17,7 +28,7 @@ export default class Expense extends Component { } //Delete an expens - handleClick = (e, expenseId) => { + handleDelete = (e, expenseId) => { e.preventDefault(); this.props.deleteExpense(expenseId); }; @@ -26,7 +37,6 @@ export default class Expense extends Component { handleChange = (e) => { e.preventDefault(); this.setState({ [e.target.name]: e.target.value }); - // console.log(this.state.nameInput); }; //edit name @@ -125,7 +135,7 @@ export default class Expense extends Component { @@ -281,11 +247,6 @@ export default class Expenses extends Component { setCalendar={setCalendar} deleteExpense={deleteExpense} handleFormChange={handleFormChange} - editExpenseName={editExpenseName} - editExpensePrice={editExpensePrice} - editExpenseCategory={editExpenseCategory} - editExpensePaidTo={editExpensePaidTo} - editExpenseDate={editExpenseDate} />
diff --git a/src/Login.js b/src/Login.js index ae5a91f..f78c0ff 100644 --- a/src/Login.js +++ b/src/Login.js @@ -1,22 +1,48 @@ import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { login, getCurrentUser, getAllExpenses } from './actions'; + import './styles/Login.css'; -export default class Login extends Component { +class Login extends Component { + constructor(props) { + super(props); + this.state = { + username: '', + password: '', + }; + } + handleChange = (e) => { e.preventDefault(); - this.props.handleLogin(e); + this.setState({ + [e.target.name]: e.target.value, + }); + }; + + clearForm = () => { + this.setState({ + username: '', + password: '', + }); }; handleSubmit = (e) => { e.preventDefault(); this.props.clearLoginErr(); - this.props.login().then(() => { - if (!this.props.loginError) { - this.props.loading(); - } + this.props.login(this.state.username, this.state.password).then(() => { + // if (!this.props.loginError) { + // this.props.loading(); + // } else { + // this.props.hasLoaded(); + // } + this.props.getCurrentUser(); + this.props.getAllExpenses(); + this.props.hasLoaded(); }); + this.props.loading(); - this.props.clearLoginForm(); + // this.clearForm(); }; componentDidMount() { @@ -45,7 +71,7 @@ export default class Login extends Component { type="text" placeholder="Username" name="username" - value={this.props.username} + value={this.state.username} > @@ -61,3 +87,5 @@ export default class Login extends Component { ); } } + +export default connect(null, { login, getCurrentUser, getAllExpenses })(Login); diff --git a/src/Table.js b/src/Table.js index 354ad1c..ab5e3de 100644 --- a/src/Table.js +++ b/src/Table.js @@ -51,7 +51,6 @@ export default class Table extends Component { paid_to={val.paid_to} startDate={startDate} setCalendar={setCalendar} - deleteExpense={deleteExpense} handleFormChange={handleFormChange} editExpenseName={editExpenseName} editExpensePrice={editExpensePrice} diff --git a/src/actions/index.js b/src/actions/index.js new file mode 100644 index 0000000..f818004 --- /dev/null +++ b/src/actions/index.js @@ -0,0 +1,196 @@ +import axios from 'axios'; +import { + LOGIN, + LOGOUT, + GET_CURRENT_USER, + GET_ALL_EXPENSES, + ADD_EXPENSE, + EDIT_EXPENSE_NAME, + EDIT_EXPENSE_PRICE, + EDIT_EXPENSE_CATEGORY, + EDIT_EXPENSE_PAIDTO, + EDIT_EXPENSE_DATE, + DELETE_EXPENSE, +} from './types'; +import jwt from 'jwt-decode'; + +const API_URL = process.env.REACT_APP_API_URL; + +// AUTH ACTION CREATORS +export const login = (username, password) => async (dispatch) => { + const res = await axios({ + method: 'POST', + url: `${API_URL}/users/login`, + data: { + username: username, + password: password, + }, + }); + + const token = res.data.token; + localStorage.setItem('token', JSON.stringify(token)); + const decodedToken = jwt(res.data.token); + const decodedUser = decodedToken.username; + + dispatch({ type: LOGIN, payload: decodedUser }); +}; + +export const getCurrentUser = () => (dispatch) => { + let currentUser; + const token = JSON.parse(localStorage.getItem('token')); + // Check if token is valid + if (token) { + const decodedToken = jwt(token); + const decodedUser = decodedToken.username; + const expireDate = decodedToken.exp; + const currentTime = Date.now() / 1000; + + //If token is expired, remove token from localstorage & set currentUser to '' + if (expireDate < currentTime) { + //logout here + currentUser = ''; + } else { + currentUser = decodedUser; + } + } + console.log('current user: ', currentUser); + dispatch({ type: GET_CURRENT_USER, payload: currentUser }); +}; + +export const logout = (user) => async (dispatch) => { + localStorage.removeItem('token'); + dispatch({ type: LOGOUT, payload: '' }); +}; + +//EXPENSE ACTION CREATORS +export const getAllExpenses = () => async (dispatch) => { + const token = JSON.parse(localStorage.getItem('token')); + + //Data from API + const res = await axios({ + method: 'GET', + url: `${API_URL}/expenses`, + headers: { Authorization: `Bearer ${token}` }, + }); + console.log('action creator: ', res.data); + dispatch({ type: GET_ALL_EXPENSES, payload: res.data }); +}; + +export const addExpense = ( + expense_name, + price, + category, + paid_to, + startDate +) => async (dispatch) => { + const token = JSON.parse(localStorage.getItem('token')); + + const res = await axios({ + method: 'post', + url: `${API_URL}/expenses`, + data: { + expense_name: expense_name, + price: price, + category: category, + paid_to: paid_to, + expense_date: startDate.getTime() / 1000, + }, + headers: { Authorization: `Bearer ${token}` }, + }); + + console.log('ADD_EXPENSE Action Creator: ', res.data); + + dispatch({ type: ADD_EXPENSE, payload: res.data }); +}; + +export const deleteExpense = (expenseId) => async (dispatch) => { + const token = JSON.parse(localStorage.getItem('token')); + + const res = await axios({ + method: 'delete', + url: `${API_URL}/expenses/${expenseId}`, + headers: { Authorization: `Bearer ${token}` }, + }); + + dispatch({ type: DELETE_EXPENSE, payload: res.data }); +}; + +// EDIT EXPENSE ROUTES + +//EDIT - EXPENSE NAME +export const editExpenseName = (expenseId, expenseName) => async (dispatch) => { + const token = JSON.parse(localStorage.getItem('token')); + + const res = await axios({ + method: 'patch', + url: `${API_URL}/expenses/${expenseId}`, + data: { + expense_name: expenseName, + }, + headers: { Authorization: `Bearer ${token}` }, + }); + + dispatch({ type: EDIT_EXPENSE_NAME, payload: res.data }); +}; + +export const editExpensePrice = (expenseId, price) => async (dispatch) => { + const token = JSON.parse(localStorage.getItem('token')); + + const res = await axios({ + method: 'patch', + url: `${API_URL}/expenses/${expenseId}`, + data: { + price: price, + }, + headers: { Authorization: `Bearer ${token}` }, + }); + + dispatch({ type: EDIT_EXPENSE_PRICE, payload: res.data }); +}; + +export const editExpenseCategory = (expenseId, category) => async ( + dispatch +) => { + const token = JSON.parse(localStorage.getItem('token')); + + const res = await axios({ + method: 'patch', + url: `${API_URL}/expenses/${expenseId}`, + data: { + category: category, + }, + headers: { Authorization: `Bearer ${token}` }, + }); + + dispatch({ type: EDIT_EXPENSE_CATEGORY, payload: res.data }); +}; + +export const editExpensePaidTo = (expenseId, paid_to) => async (dispatch) => { + const token = JSON.parse(localStorage.getItem('token')); + + const res = await axios({ + method: 'patch', + url: `${API_URL}/expenses/${expenseId}`, + data: { + paid_to: paid_to, + }, + headers: { Authorization: `Bearer ${token}` }, + }); + + dispatch({ type: EDIT_EXPENSE_PAIDTO, payload: res.data }); +}; + +export const editExpenseDate = (expenseId, time) => async (dispatch) => { + const token = JSON.parse(localStorage.getItem('token')); + + const res = await axios({ + method: 'patch', + url: `${API_URL}/expenses/${expenseId}`, + data: { + expense_date: time.getTime() / 1000, + }, + headers: { Authorization: `Bearer ${token}` }, + }); + + dispatch({ type: EDIT_EXPENSE_DATE, payload: res.data }); +}; diff --git a/src/actions/types.js b/src/actions/types.js new file mode 100644 index 0000000..d28020c --- /dev/null +++ b/src/actions/types.js @@ -0,0 +1,16 @@ +export const LOGIN = 'LOGIN'; +export const LOGOUT = 'LOGOUT'; + +export const GET_CURRENT_USER = 'GET_CURRENT_USER'; +export const GET_ALL_EXPENSES = 'GET_ALL_EXPENSES'; + +export const ADD_EXPENSE = 'ADD_EXPENSE'; +export const DELETE_EXPENSE = 'DELETE_EXPENSE'; + +export const EDIT_EXPENSE_NAME = 'EDIT_EXPENSE_NAME'; +export const EDIT_EXPENSE_PRICE = 'EDIT_EXPENSE_PRICE'; +export const EDIT_EXPENSE_CATEGORY = 'EDIT_EXPENSE_CATEGORY'; +export const EDIT_EXPENSE_PAIDTO = 'EDIT_EXPENSE_PAIDTO'; +export const EDIT_EXPENSE_DATE = 'EDIT_EXPENSE_DATE'; + +// export const SET_CALENDAR = 'SET_CALENDAR'; diff --git a/src/colors.js b/src/colors.js new file mode 100644 index 0000000..d45d285 --- /dev/null +++ b/src/colors.js @@ -0,0 +1,301 @@ +export const COLORS = [ + '#f714ce', + '#f71942', + 'grey', + '#10e348', + 'teal', + 'blue', + 'brown', + '#14bef7', + 'orange', + 'purple', + 'black', + '#19f78c', + 'pink', + '#bab400', + '#d705f7', + '#f76205', + '#05f7cf', + '#0576f7', + // + '#63b598', + '#ce7d78', + '#ea9e70', + '#a48a9e', + '#c6e1e8', + '#648177', + '#0d5ac1', + '#f205e6', + '#1c0365', + '#14a9ad', + '#4ca2f9', + '#a4e43f', + '#d298e2', + '#6119d0', + '#d2737d', + '#c0a43c', + '#f2510e', + '#651be6', + '#79806e', + '#61da5e', + '#cd2f00', + '#9348af', + '#01ac53', + '#c5a4fb', + '#996635', + '#b11573', + '#4bb473', + '#75d89e', + '#2f3f94', + '#2f7b99', + '#da967d', + '#34891f', + '#b0d87b', + '#ca4751', + '#7e50a8', + '#c4d647', + '#e0eeb8', + '#11dec1', + '#289812', + '#566ca0', + '#ffdbe1', + '#2f1179', + '#935b6d', + '#916988', + '#513d98', + '#aead3a', + '#9e6d71', + '#4b5bdc', + '#0cd36d', + '#250662', + '#cb5bea', + '#228916', + '#ac3e1b', + '#df514a', + '#539397', + '#880977', + '#f697c1', + '#ba96ce', + '#679c9d', + '#c6c42c', + '#5d2c52', + '#48b41b', + '#e1cf3b', + '#5be4f0', + '#57c4d8', + '#a4d17a', + '#225b8', + '#be608b', + '#96b00c', + '#088baf', + '#f158bf', + '#e145ba', + '#ee91e3', + '#05d371', + '#5426e0', + '#4834d0', + '#802234', + '#6749e8', + '#0971f0', + '#8fb413', + '#b2b4f0', + '#c3c89d', + '#c9a941', + '#41d158', + '#fb21a3', + '#51aed9', + '#5bb32d', + '#807fb', + '#21538e', + '#89d534', + '#d36647', + '#7fb411', + '#0023b8', + '#3b8c2a', + '#986b53', + '#f50422', + '#983f7a', + '#ea24a3', + '#79352c', + '#521250', + '#c79ed2', + '#d6dd92', + '#e33e52', + '#b2be57', + '#fa06ec', + '#1bb699', + '#6b2e5f', + '#64820f', + '#1c271', + '#21538e', + '#89d534', + '#d36647', + '#7fb411', + '#0023b8', + '#3b8c2a', + '#986b53', + '#f50422', + '#983f7a', + '#ea24a3', + '#79352c', + '#521250', + '#c79ed2', + '#d6dd92', + '#e33e52', + '#b2be57', + '#fa06ec', + '#1bb699', + '#6b2e5f', + '#64820f', + '#1c271', + '#9cb64a', + '#996c48', + '#9ab9b7', + '#06e052', + '#e3a481', + '#0eb621', + '#fc458e', + '#b2db15', + '#aa226d', + '#792ed8', + '#73872a', + '#520d3a', + '#cefcb8', + '#a5b3d9', + '#7d1d85', + '#c4fd57', + '#f1ae16', + '#8fe22a', + '#ef6e3c', + '#243eeb', + '#1dc18', + '#dd93fd', + '#3f8473', + '#e7dbce', + '#421f79', + '#7a3d93', + '#635f6d', + '#93f2d7', + '#9b5c2a', + '#15b9ee', + '#0f5997', + '#409188', + '#911e20', + '#1350ce', + '#10e5b1', + '#fff4d7', + '#cb2582', + '#ce00be', + '#32d5d6', + '#17232', + '#608572', + '#c79bc2', + '#00f87c', + '#77772a', + '#6995ba', + '#fc6b57', + '#f07815', + '#8fd883', + '#060e27', + '#96e591', + '#21d52e', + '#d00043', + '#b47162', + '#1ec227', + '#4f0f6f', + '#1d1d58', + '#947002', + '#bde052', + '#e08c56', + '#28fcfd', + '#bb09b', + '#36486a', + '#d02e29', + '#1ae6db', + '#3e464c', + '#a84a8f', + '#911e7e', + '#3f16d9', + '#0f525f', + '#ac7c0a', + '#b4c086', + '#c9d730', + '#30cc49', + '#3d6751', + '#fb4c03', + '#640fc1', + '#62c03e', + '#d3493a', + '#88aa0b', + '#406df9', + '#615af0', + '#4be47', + '#2a3434', + '#4a543f', + '#79bca0', + '#a8b8d4', + '#00efd4', + '#7ad236', + '#7260d8', + '#1deaa7', + '#06f43a', + '#823c59', + '#e3d94c', + '#dc1c06', + '#f53b2a', + '#b46238', + '#2dfff6', + '#a82b89', + '#1a8011', + '#436a9f', + '#1a806a', + '#4cf09d', + '#c188a2', + '#67eb4b', + '#b308d3', + '#fc7e41', + '#af3101', + '#ff065', + '#71b1f4', + '#a2f8a5', + '#e23dd0', + '#d3486d', + '#00f7f9', + '#474893', + '#3cec35', + '#1c65cb', + '#5d1d0c', + '#2d7d2a', + '#ff3420', + '#5cdd87', + '#a259a4', + '#e4ac44', + '#1bede6', + '#8798a4', + '#d7790f', + '#b2c24f', + '#de73c2', + '#d70a9c', + '#25b67', + '#88e9b8', + '#c2b0e2', + '#86e98f', + '#ae90e2', + '#1a806b', + '#436a9e', + '#0ec0ff', + '#f812b3', + '#b17fc9', + '#8d6c2f', + '#d3277a', + '#2ca1ae', + '#9685eb', + '#8a96c6', + '#dba2e6', + '#76fc1b', + '#608fa4', + '#20f6ba', + '#07d7f6', + '#dce77a', + '#77ecca', +]; diff --git a/src/index.js b/src/index.js index 05086e7..ebaab95 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,27 @@ import React, { Component } from 'react'; import ReactDOM from 'react-dom'; +import { Provider } from 'react-redux'; +import { createStore, applyMiddleware, compose } from 'redux'; +import reduxThunk from 'redux-thunk'; + +import reducers from './reducers'; import App from './App'; import './styles/index.css'; // import * as serviceWorker from './serviceWorker'; +const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; +const store = createStore( + reducers, + {}, + composeEnhancers(applyMiddleware(reduxThunk)) +); - -ReactDOM.render(, document.getElementById('root')); +ReactDOM.render( + + + , + document.getElementById('root') +); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. diff --git a/src/reducers/authReducer.js b/src/reducers/authReducer.js new file mode 100644 index 0000000..f35a7b4 --- /dev/null +++ b/src/reducers/authReducer.js @@ -0,0 +1,21 @@ +import { LOGIN, GET_CURRENT_USER, LOGOUT } from '../actions/types'; + +const initialState = {}; + +export default function (state = initialState, action) { + switch (action.type) { + case LOGIN: + console.log('LOGIN REDUCER: ', action.payload); + console.log('LOGIN REDUCER: ', action.payload); + return { ...state, currentUser: action.payload }; + case GET_CURRENT_USER: + console.log('state: ', state); + console.log('GET_CURRENT_USER: ', action.payload); + return { ...state, currentUser: action.payload }; + case LOGOUT: + console.log('LOGOUT: ', action.payload); + return { ...state, currentUser: action.payload }; + default: + return state; + } +} diff --git a/src/reducers/dateReducer.js b/src/reducers/dateReducer.js new file mode 100644 index 0000000..a5a232a --- /dev/null +++ b/src/reducers/dateReducer.js @@ -0,0 +1,16 @@ +import { SET_CALENDAR } from '../actions/types'; + +const initialState = { startDate: new Date() }; + +// Probably better to leave this in local state for the calendar component +// Time will be refreshed each time calendar component reloads/remounts +// export default function (state = initialState, action) { +// switch (action.type) { +// case SET_CALENDAR: +// console.log('set calendar:', action.payload); +// return { startDate: action.payload }; + +// default: +// return state; +// } +// } diff --git a/src/reducers/expenseReducer.js b/src/reducers/expenseReducer.js new file mode 100644 index 0000000..bcd4aab --- /dev/null +++ b/src/reducers/expenseReducer.js @@ -0,0 +1,72 @@ +import { + GET_ALL_EXPENSES, + ADD_EXPENSE, + EDIT_EXPENSE_NAME, + EDIT_EXPENSE_PRICE, + EDIT_EXPENSE_CATEGORY, + EDIT_EXPENSE_PAIDTO, + EDIT_EXPENSE_DATE, + DELETE_EXPENSE, +} from '../actions/types'; + +const initialState = []; + +export default function (state = initialState, action) { + switch (action.type) { + case GET_ALL_EXPENSES: + // console.log('GET_ALL_EXPENSES Reducer: ', action.payload); + return [...action.payload]; + + case ADD_EXPENSE: + return [...state, action.payload]; + + case EDIT_EXPENSE_NAME: + // console.log('EDIT EXPENSE NAME REDUCER: ', action.payload); + return state.map((expense) => { + if (expense.id === action.payload.id) { + expense.expense_name = action.payload.expense_name; + } + return expense; + }); + + case EDIT_EXPENSE_PRICE: + return state.map((expense) => { + if (expense.id === action.payload.id) { + expense.price = action.payload.price; + } + return expense; + }); + + case EDIT_EXPENSE_CATEGORY: + return state.map((expense) => { + if (expense.id === action.payload.id) { + expense.category = action.payload.category; + } + return expense; + }); + + case EDIT_EXPENSE_PAIDTO: + return state.map((expense) => { + if (expense.id === action.payload.id) { + expense.paid_to = action.payload.paid_to; + } + return expense; + }); + + case EDIT_EXPENSE_DATE: + return state.map((expense) => { + if (expense.id === action.payload.id) { + expense.expense_date = action.payload.expense_date; + } + return expense; + }); + + case DELETE_EXPENSE: + let newState = state.filter((x) => x.id !== action.payload.id); + console.log('DELETE REDUCER: ', action.payload); + return newState; + + default: + return state; + } +} diff --git a/src/reducers/index.js b/src/reducers/index.js new file mode 100644 index 0000000..717fa6a --- /dev/null +++ b/src/reducers/index.js @@ -0,0 +1,8 @@ +import { combineReducers } from 'redux'; +import authReducer from './authReducer'; +import expenseReducer from './expenseReducer'; + +export default combineReducers({ + auth: authReducer, + expenses: expenseReducer, +});