├── src
├── setupTests.js
├── index.css
├── utils
│ ├── index.js
│ ├── calculateSimpleInterest.js
│ └── calculateCompoundInterest.js
├── components
│ ├── App
│ │ ├── index.test.jsx
│ │ └── index.jsx
│ ├── Header
│ │ └── index.jsx
│ ├── TabsPanel
│ │ └── index.jsx
│ ├── Result
│ │ └── index.jsx
│ ├── Main
│ │ └── index.jsx
│ ├── SimpleInterest
│ │ └── index.jsx
│ └── CompoundInterest
│ │ └── index.jsx
├── index.js
└── serviceWorker.js
├── public
├── robots.txt
├── favicon.ico
├── mstile-150x150.png
├── apple-touch-icon.png
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── browserconfig.xml
├── manifest.json
├── safari-pinned-tab.svg
└── index.html
├── .gitignore
├── LICENSE
├── package.json
└── README.md
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom/extend-expect';
2 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | background-color: #e5e5e5;
4 | }
5 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SafdarJamal/interest-calculator/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SafdarJamal/interest-calculator/HEAD/public/mstile-150x150.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SafdarJamal/interest-calculator/HEAD/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SafdarJamal/interest-calculator/HEAD/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SafdarJamal/interest-calculator/HEAD/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | import calculateSimpleInterest from './calculateSimpleInterest';
2 | import calculateCompoundInterest from './calculateCompoundInterest';
3 |
4 | export { calculateSimpleInterest, calculateCompoundInterest };
5 |
--------------------------------------------------------------------------------
/src/components/App/index.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import App from '.';
4 |
5 | test('renders without crashing', () => {
6 | const { unmount } = render();
7 | unmount();
8 | });
9 |
--------------------------------------------------------------------------------
/src/utils/calculateSimpleInterest.js:
--------------------------------------------------------------------------------
1 | const calculateSimpleInterest = (P, r, t) => {
2 | P = Number(P.toFixed(2));
3 | r = r / 100;
4 |
5 | const I = Number((P * (1 + r * t) - P).toFixed(2));
6 | const A = P + I;
7 |
8 | return { P, I, A };
9 | };
10 |
11 | export default calculateSimpleInterest;
12 |
--------------------------------------------------------------------------------
/src/components/App/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 |
3 | import Header from '../Header';
4 | import Main from '../Main';
5 |
6 | const App = () => {
7 | return (
8 |
9 |
10 |
11 |
12 | );
13 | };
14 |
15 | export default App;
16 |
--------------------------------------------------------------------------------
/public/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #2b5797
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './components/App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | serviceWorker.register();
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/src/utils/calculateCompoundInterest.js:
--------------------------------------------------------------------------------
1 | const calculateCompoundInterest = (P, r, t, n, PMT) => {
2 | P = Number(P.toFixed(2));
3 | r = r / 100;
4 | PMT = Number(Number(PMT).toFixed(2));
5 |
6 | const A = Number(
7 | (
8 | P * Math.pow(1 + r / n, n * t) +
9 | (PMT * (Math.pow(1 + r / n, n * t) - 1)) / (r / n)
10 | ).toFixed(2)
11 | );
12 |
13 | PMT = Number((PMT * 12 * t).toFixed(2));
14 | const I = Number((A - P - PMT).toFixed(2));
15 |
16 | return { P, PMT, I, A };
17 | };
18 |
19 | export default calculateCompoundInterest;
20 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Interest Calculator",
3 | "short_name": "Interest Calculator",
4 | "description": "A fast, user-friendly, offline-first, interest calculator progressive web app (PWA).",
5 | "icons": [
6 | {
7 | "src": "android-chrome-192x192.png",
8 | "type": "image/png",
9 | "sizes": "192x192"
10 | },
11 | {
12 | "src": "android-chrome-512x512.png",
13 | "type": "image/png",
14 | "sizes": "512x512"
15 | }
16 | ],
17 | "start_url": ".",
18 | "theme_color": "#3f51b5",
19 | "background_color": "#e5e5e5",
20 | "orientation": "any",
21 | "display": "standalone"
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/Header/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { makeStyles } from '@material-ui/core/styles';
3 | import AppBar from '@material-ui/core/AppBar';
4 | import Toolbar from '@material-ui/core/Toolbar';
5 | import Typography from '@material-ui/core/Typography';
6 |
7 | const useStyles = makeStyles(theme => ({
8 | root: {
9 | flexGrow: 1
10 | },
11 | title: {
12 | flexGrow: 1
13 | }
14 | }));
15 |
16 | const Header = () => {
17 | const classes = useStyles();
18 |
19 | return (
20 |
21 |
22 |
23 | Interest Calculator
24 |
25 |
26 |
27 | );
28 | };
29 |
30 | export default Header;
31 |
--------------------------------------------------------------------------------
/src/components/TabsPanel/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Paper from '@material-ui/core/Paper';
3 | import Tabs from '@material-ui/core/Tabs';
4 | import Tab from '@material-ui/core/Tab';
5 |
6 | const TabsPanel = ({ tabStatus, setTabStatus }) => {
7 | const handleChange = (event, newValue) => {
8 | setTabStatus(newValue);
9 | };
10 |
11 | return (
12 |
27 | );
28 | };
29 |
30 | export default TabsPanel;
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2020 Safdar Jamal
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "interest-calculator",
3 | "version": "1.3.0",
4 | "private": true,
5 | "author": {
6 | "name": "Safdar Jamal",
7 | "url": "https://safdarjamal.github.io"
8 | },
9 | "homepage": "https://safdarjamal.github.io/interest-calculator/",
10 | "dependencies": {
11 | "@material-ui/core": "^4.11.0",
12 | "@testing-library/jest-dom": "^4.2.4",
13 | "@testing-library/react": "^9.5.0",
14 | "@testing-library/user-event": "^7.2.1",
15 | "react": "^16.13.1",
16 | "react-dom": "^16.13.1",
17 | "react-scripts": "3.4.3"
18 | },
19 | "scripts": {
20 | "start": "react-scripts start",
21 | "build": "react-scripts build",
22 | "test": "react-scripts test",
23 | "eject": "react-scripts eject",
24 | "predeploy": "npm run build",
25 | "deploy": "gh-pages -d build"
26 | },
27 | "eslintConfig": {
28 | "extends": "react-app"
29 | },
30 | "browserslist": {
31 | "production": [
32 | ">0.2%",
33 | "not dead",
34 | "not op_mini all"
35 | ],
36 | "development": [
37 | "last 1 chrome version",
38 | "last 1 firefox version",
39 | "last 1 safari version"
40 | ]
41 | },
42 | "devDependencies": {
43 | "gh-pages": "^3.1.0"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/components/Result/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { makeStyles } from '@material-ui/core/styles';
3 | import Table from '@material-ui/core/Table';
4 | import TableHead from '@material-ui/core/TableHead';
5 | import TableBody from '@material-ui/core/TableBody';
6 | import TableRow from '@material-ui/core/TableRow';
7 | import TableCell from '@material-ui/core/TableCell';
8 |
9 | const useStyles = makeStyles({
10 | tableCell: {
11 | fontSize: 16
12 | }
13 | });
14 |
15 | const Result = ({ resultData }) => {
16 | const classes = useStyles();
17 |
18 | const numberFormatter = new Intl.NumberFormat('en-US');
19 |
20 | return (
21 |
22 |
23 |
24 |
25 | RESULT
26 |
27 |
28 |
29 |
30 | {resultData.map(i => (
31 |
32 | {i.name}
33 |
34 | {numberFormatter.format(i.value)}
35 |
36 |
37 | ))}
38 |
39 |
40 | );
41 | };
42 |
43 | export default Result;
44 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 | A fast, user-friendly, offline-first, interest calculator progressive web app (PWA).
9 |
10 |
11 | 
12 |
13 | ## Development
14 |
15 | To get a local copy of the code, clone it using git:
16 |
17 | ```
18 | git clone https://github.com/SafdarJamal/interest-calculator.git
19 | cd interest-calculator
20 | ```
21 |
22 | Install dependencies:
23 |
24 | ```
25 | npm install
26 | ```
27 |
28 | Now, you can start a local web server by running:
29 |
30 | ```
31 | npm start
32 | ```
33 |
34 | and then you can open http://localhost:3000 to view it in the browser.
35 |
36 | #### Available Scripts
37 |
38 | | Script | Description |
39 | | ------------- | ----------------------------------------------------------------------- |
40 | | npm start | Runs the app in the development mode. |
41 | | npm test | Launches the test runner in the interactive watch mode. |
42 | | npm run build | Builds the app for production to the `build` folder. |
43 | | npm run eject | This command will remove the single build dependency from your project. |
44 |
45 | ## License
46 |
47 | Code released under the [MIT License](https://github.com/SafdarJamal/interest-calculator/blob/master/LICENSE).
48 |
--------------------------------------------------------------------------------
/src/components/Main/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from 'react';
2 | import { makeStyles } from '@material-ui/core/styles';
3 | import Container from '@material-ui/core/Container';
4 | import Paper from '@material-ui/core/Paper';
5 |
6 | import TabsPanel from '../TabsPanel';
7 | import SimpleInterest from '../SimpleInterest';
8 | import CompoundInterest from '../CompoundInterest';
9 |
10 | const useStyles = makeStyles(theme => ({
11 | root: {
12 | flexGrow: 1
13 | },
14 | paper: {
15 | marginTop: 80,
16 | marginBottom: 20,
17 | margin: 'auto',
18 | [theme.breakpoints.up('md')]: {
19 | width: '50%'
20 | }
21 | }
22 | }));
23 |
24 | const Main = () => {
25 | const classes = useStyles();
26 | const [tabStatus, setTabStatus] = useState(0);
27 | const top = useRef();
28 | const bottom = useRef();
29 |
30 | const scrollTop = () => {
31 | top.current.scrollIntoView({ behavior: 'smooth' });
32 | };
33 |
34 | const scrollBottom = () => {
35 | bottom.current.scrollIntoView({ behavior: 'smooth' });
36 | };
37 |
38 | return (
39 |
40 |
41 |
42 |
43 | {tabStatus === 0 && (
44 |
45 | )}
46 | {tabStatus === 1 && (
47 |
48 | )}
49 |
50 |
51 |
52 | );
53 | };
54 |
55 | export default Main;
56 |
--------------------------------------------------------------------------------
/public/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
32 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Interest Calculator
7 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | const isLocalhost = Boolean(
2 | window.location.hostname === 'localhost' ||
3 | // [::1] is the IPv6 localhost address.
4 | window.location.hostname === '[::1]' ||
5 | // 127.0.0.0/8 are considered localhost for IPv4.
6 | window.location.hostname.match(
7 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
8 | )
9 | );
10 |
11 | export function register(config) {
12 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
13 | // The URL constructor is available in all browsers that support SW.
14 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
15 | if (publicUrl.origin !== window.location.origin) {
16 | // Our service worker won't work if PUBLIC_URL is on a different origin
17 | // from what our page is served on. This might happen if a CDN is used to
18 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
19 | return;
20 | }
21 |
22 | window.addEventListener('load', () => {
23 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
24 |
25 | if (isLocalhost) {
26 | // This is running on localhost. Let's check if a service worker still exists or not.
27 | checkValidServiceWorker(swUrl, config);
28 |
29 | // Add some additional logging to localhost, pointing developers to the
30 | // service worker/PWA documentation.
31 | navigator.serviceWorker.ready.then(() => {
32 | console.log(
33 | 'This web app is being served cache-first by a service ' +
34 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
35 | );
36 | });
37 | } else {
38 | // Is not localhost. Just register service worker
39 | registerValidSW(swUrl, config);
40 | }
41 | });
42 | }
43 | }
44 |
45 | function registerValidSW(swUrl, config) {
46 | navigator.serviceWorker
47 | .register(swUrl)
48 | .then(registration => {
49 | registration.onupdatefound = () => {
50 | const installingWorker = registration.installing;
51 | if (installingWorker == null) {
52 | return;
53 | }
54 | installingWorker.onstatechange = () => {
55 | if (installingWorker.state === 'installed') {
56 | if (navigator.serviceWorker.controller) {
57 | // At this point, the updated precached content has been fetched,
58 | // but the previous service worker will still serve the older
59 | // content until all client tabs are closed.
60 | console.log(
61 | 'New content is available and will be used when all ' +
62 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
63 | );
64 |
65 | // Execute callback
66 | if (config && config.onUpdate) {
67 | config.onUpdate(registration);
68 | }
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 |
75 | // Execute callback
76 | if (config && config.onSuccess) {
77 | config.onSuccess(registration);
78 | }
79 | }
80 | }
81 | };
82 | };
83 | })
84 | .catch(error => {
85 | console.error('Error during service worker registration:', error);
86 | });
87 | }
88 |
89 | function checkValidServiceWorker(swUrl, config) {
90 | // Check if the service worker can be found. If it can't reload the page.
91 | fetch(swUrl, {
92 | headers: { 'Service-Worker': 'script' }
93 | })
94 | .then(response => {
95 | // Ensure service worker exists, and that we really are getting a JS file.
96 | const contentType = response.headers.get('content-type');
97 | if (
98 | response.status === 404 ||
99 | (contentType != null && contentType.indexOf('javascript') === -1)
100 | ) {
101 | // No service worker found. Probably a different app. Reload the page.
102 | navigator.serviceWorker.ready.then(registration => {
103 | registration.unregister().then(() => {
104 | window.location.reload();
105 | });
106 | });
107 | } else {
108 | // Service worker found. Proceed as normal.
109 | registerValidSW(swUrl, config);
110 | }
111 | })
112 | .catch(() => {
113 | console.log(
114 | 'No internet connection found. App is running in offline mode.'
115 | );
116 | });
117 | }
118 |
119 | export function unregister() {
120 | if ('serviceWorker' in navigator) {
121 | navigator.serviceWorker.ready
122 | .then(registration => {
123 | registration.unregister();
124 | })
125 | .catch(error => {
126 | console.error(error.message);
127 | });
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/components/SimpleInterest/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { withStyles } from '@material-ui/core/styles';
3 | import Grid from '@material-ui/core/Grid';
4 | import TextField from '@material-ui/core/TextField';
5 | import MenuItem from '@material-ui/core/MenuItem';
6 | import Button from '@material-ui/core/Button';
7 | import CircularProgress from '@material-ui/core/CircularProgress';
8 |
9 | import { calculateSimpleInterest } from '../../utils';
10 | import Result from '../Result';
11 |
12 | const styles = theme => ({
13 | root: {
14 | padding: 16
15 | },
16 | input: {
17 | width: '100%'
18 | },
19 | calcButton: {
20 | width: '100%',
21 | marginBottom: 16,
22 | [theme.breakpoints.up('sm')]: {
23 | width: '48%',
24 | marginRight: 11,
25 | marginBottom: 0
26 | }
27 | },
28 | resetButton: {
29 | width: '100%',
30 | [theme.breakpoints.up('sm')]: {
31 | width: '48%',
32 | marginLeft: 11
33 | }
34 | },
35 | buttonProgress: {
36 | position: 'absolute',
37 | marginTop: 8,
38 | marginLeft: -150
39 | }
40 | });
41 |
42 | class SimpleInterest extends Component {
43 | constructor(props) {
44 | super(props);
45 |
46 | const data = JSON.parse(localStorage.getItem('simpleInterest'));
47 |
48 | const {
49 | initialInvestment,
50 | interestRate,
51 | calculationPeriod,
52 | calculationPeriodType,
53 | resultData
54 | } = data || {
55 | initialInvestment: '',
56 | interestRate: '',
57 | calculationPeriod: '',
58 | calculationPeriodType: 1,
59 | resultData: [
60 | { name: 'Initial Investment', value: 0 },
61 | { name: 'Interest Earned', value: 0 },
62 | { name: 'Total', value: 0 }
63 | ]
64 | };
65 |
66 | this.state = {
67 | initialInvestment,
68 | interestRate,
69 | calculationPeriod,
70 | calculationPeriodType,
71 | isCalculating: false,
72 | isResetting: false,
73 | resultData
74 | };
75 | }
76 |
77 | handleChange = e => {
78 | this.setState({ [e.target.name]: Number(e.target.value) });
79 | };
80 |
81 | handleSubmit = e => {
82 | e.preventDefault();
83 |
84 | this.setState({ isCalculating: true });
85 |
86 | setTimeout(() => {
87 | const {
88 | initialInvestment,
89 | interestRate,
90 | calculationPeriodType,
91 | resultData
92 | } = this.state;
93 |
94 | let { calculationPeriod } = this.state;
95 | calculationPeriod = calculationPeriod / calculationPeriodType / 1;
96 |
97 | const { P, I, A } = calculateSimpleInterest(
98 | initialInvestment,
99 | interestRate,
100 | calculationPeriod
101 | );
102 |
103 | resultData[0].value = P;
104 | resultData[1].value = I;
105 | resultData[2].value = A;
106 |
107 | this.setState({ isCalculating: false, resultData });
108 |
109 | this.props.scrollBottom();
110 | }, 2000);
111 | };
112 |
113 | handleReset = e => {
114 | this.setState({ isResetting: true });
115 |
116 | setTimeout(() => {
117 | this.setState({
118 | initialInvestment: '',
119 | interestRate: '',
120 | calculationPeriod: '',
121 | calculationPeriodType: 1,
122 | isCalculating: false,
123 | isResetting: false,
124 | resultData: [
125 | { name: 'Initial Investment', value: 0 },
126 | { name: 'Interest Earned', value: 0 },
127 | { name: 'Total', value: 0 }
128 | ]
129 | });
130 |
131 | this.props.scrollTop();
132 | }, 2000);
133 | };
134 |
135 | render() {
136 | const {
137 | initialInvestment,
138 | interestRate,
139 | calculationPeriod,
140 | calculationPeriodType,
141 | isCalculating,
142 | isResetting,
143 | resultData
144 | } = this.state;
145 |
146 | const { classes } = this.props;
147 | let isFormFilled = false;
148 |
149 | if (initialInvestment && interestRate && calculationPeriod) {
150 | isFormFilled = true;
151 | }
152 |
153 | localStorage.setItem(
154 | 'simpleInterest',
155 | JSON.stringify({
156 | initialInvestment,
157 | interestRate,
158 | calculationPeriod,
159 | calculationPeriodType,
160 | resultData
161 | })
162 | );
163 |
164 | return (
165 |
166 |
266 |
267 |
268 | );
269 | }
270 | }
271 |
272 | export default withStyles(styles)(SimpleInterest);
273 |
--------------------------------------------------------------------------------
/src/components/CompoundInterest/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { withStyles } from '@material-ui/core/styles';
3 | import Grid from '@material-ui/core/Grid';
4 | import TextField from '@material-ui/core/TextField';
5 | import MenuItem from '@material-ui/core/MenuItem';
6 | import Button from '@material-ui/core/Button';
7 | import CircularProgress from '@material-ui/core/CircularProgress';
8 |
9 | import { calculateCompoundInterest } from '../../utils';
10 | import Result from '../Result';
11 |
12 | const styles = theme => ({
13 | root: {
14 | padding: 16
15 | },
16 | input: {
17 | width: '100%'
18 | },
19 | calcButton: {
20 | width: '100%',
21 | marginBottom: 16,
22 | [theme.breakpoints.up('sm')]: {
23 | width: '48%',
24 | marginRight: 11,
25 | marginBottom: 0
26 | }
27 | },
28 | resetButton: {
29 | width: '100%',
30 | [theme.breakpoints.up('sm')]: {
31 | width: '48%',
32 | marginLeft: 11
33 | }
34 | },
35 | buttonProgress: {
36 | position: 'absolute',
37 | marginTop: 8,
38 | marginLeft: -150
39 | }
40 | });
41 |
42 | class CompoundInterest extends Component {
43 | constructor(props) {
44 | super(props);
45 |
46 | const data = JSON.parse(localStorage.getItem('compoundInterest'));
47 |
48 | const {
49 | initialInvestment,
50 | interestRate,
51 | calculationPeriod,
52 | calculationPeriodType,
53 | compoundInterval,
54 | regularInvestment,
55 | resultData
56 | } = data || {
57 | initialInvestment: '',
58 | interestRate: '',
59 | calculationPeriod: '',
60 | calculationPeriodType: 1,
61 | compoundInterval: 12,
62 | regularInvestment: '',
63 | resultData: [
64 | { name: 'Initial Investment', value: 0 },
65 | { name: 'Regular Investment', value: 0 },
66 | { name: 'Interest Earned', value: 0 },
67 | { name: 'Total', value: 0 }
68 | ]
69 | };
70 |
71 | this.state = {
72 | initialInvestment,
73 | interestRate,
74 | calculationPeriod,
75 | calculationPeriodType,
76 | compoundInterval,
77 | regularInvestment,
78 | isCalculating: false,
79 | isResetting: false,
80 | resultData
81 | };
82 | }
83 |
84 | handleChange = e => {
85 | this.setState({ [e.target.name]: Number(e.target.value) });
86 | };
87 |
88 | handleSubmit = e => {
89 | e.preventDefault();
90 |
91 | this.setState({ isCalculating: true });
92 |
93 | setTimeout(() => {
94 | const {
95 | initialInvestment,
96 | interestRate,
97 | calculationPeriodType,
98 | compoundInterval,
99 | regularInvestment,
100 | resultData
101 | } = this.state;
102 |
103 | let { calculationPeriod } = this.state;
104 | calculationPeriod = calculationPeriod / calculationPeriodType / 1;
105 |
106 | const { P, PMT, I, A } = calculateCompoundInterest(
107 | initialInvestment,
108 | interestRate,
109 | calculationPeriod,
110 | compoundInterval,
111 | regularInvestment
112 | );
113 |
114 | resultData[0].value = P;
115 | resultData[1].value = PMT;
116 | resultData[2].value = I;
117 | resultData[3].value = A;
118 |
119 | this.setState({ isCalculating: false, resultData });
120 |
121 | this.props.scrollBottom();
122 | }, 2000);
123 | };
124 |
125 | handleReset = e => {
126 | this.setState({ isResetting: true });
127 |
128 | setTimeout(() => {
129 | this.setState({
130 | initialInvestment: '',
131 | interestRate: '',
132 | calculationPeriod: '',
133 | calculationPeriodType: 1,
134 | compoundInterval: 12,
135 | regularInvestment: '',
136 | isCalculating: false,
137 | isResetting: false,
138 | resultData: [
139 | { name: 'Initial Investment', value: 0 },
140 | { name: 'Regular Investment', value: 0 },
141 | { name: 'Interest Earned', value: 0 },
142 | { name: 'Total', value: 0 }
143 | ]
144 | });
145 |
146 | this.props.scrollTop();
147 | }, 2000);
148 | };
149 |
150 | render() {
151 | const {
152 | initialInvestment,
153 | interestRate,
154 | calculationPeriod,
155 | calculationPeriodType,
156 | compoundInterval,
157 | regularInvestment,
158 | isCalculating,
159 | isResetting,
160 | resultData
161 | } = this.state;
162 |
163 | const { classes } = this.props;
164 | let isFormFilled = false;
165 |
166 | if (
167 | initialInvestment &&
168 | interestRate &&
169 | calculationPeriod &&
170 | compoundInterval
171 | ) {
172 | isFormFilled = true;
173 | }
174 |
175 | localStorage.setItem(
176 | 'compoundInterest',
177 | JSON.stringify({
178 | initialInvestment,
179 | interestRate,
180 | calculationPeriod,
181 | calculationPeriodType,
182 | compoundInterval,
183 | regularInvestment,
184 | resultData
185 | })
186 | );
187 |
188 | return (
189 |
190 |
322 |
323 |
324 | );
325 | }
326 | }
327 |
328 | export default withStyles(styles)(CompoundInterest);
329 |
--------------------------------------------------------------------------------