├── 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 |

2 | 3 | Interest Calculator 4 | 5 |

6 | 7 |

8 | A fast, user-friendly, offline-first, interest calculator progressive web app (PWA). 9 |

10 | 11 | ![Interest Calculator in Action](https://user-images.githubusercontent.com/48409548/95246294-b3525500-082d-11eb-8507-650d617a226f.png) 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 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 26 | 28 | 30 | 31 | 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 |
173 | 174 | 175 | 186 | 187 | 188 | 199 | 200 | 201 | 212 | 213 | 214 | 224 | Days 225 | Months 226 | Years 227 | 228 | 229 | 230 | 240 | {isCalculating && ( 241 | 246 | )} 247 | 256 | {isResetting && ( 257 | 262 | )} 263 | 264 | 265 |
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 |
197 | 198 | 199 | 210 | 211 | 212 | 223 | 224 | 225 | 236 | 237 | 238 | 248 | Days 249 | Months 250 | Years 251 | 252 | 253 | 254 | 265 | Daily 266 | Monthly 267 | Quarterly 268 | Half Yearly 269 | Yearly 270 | 271 | 272 | 273 | 284 | 285 | 286 | 296 | {isCalculating && ( 297 | 302 | )} 303 | 312 | {isResetting && ( 313 | 318 | )} 319 | 320 | 321 |
322 | 323 |
324 | ); 325 | } 326 | } 327 | 328 | export default withStyles(styles)(CompoundInterest); 329 | --------------------------------------------------------------------------------