├── src ├── cal5.png ├── components │ ├── Quote.js │ ├── Nav.js │ ├── Home.js │ └── Calculator.js ├── index.js ├── reportWebVitals.js ├── logic │ ├── operate.js │ └── calculate.js ├── App.js └── index.css ├── public ├── robots.txt ├── favicon.ico ├── manifest.json └── index.html ├── .babelrc ├── .gitignore ├── .stylelintrc.json ├── .eslintrc.json ├── README.md ├── package.json └── .github └── workflows └── linters.yml /src/cal5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/divinecharlotte/math-magician/HEAD/src/cal5.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/divinecharlotte/math-magician/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-react" 4 | ], 5 | "plugins": ["@babel/plugin-syntax-jsx"] 6 | } -------------------------------------------------------------------------------- /src/components/Quote.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function Quote() { 4 | return ( 5 |
6 |

7 | Mathematics is not about numbers,equations,computation or algorithms: 8 | its about understanding. William Paul Thurston 9 |

10 |
11 | ); 12 | } 13 | 14 | export default Quote; 15 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | const root = ReactDOM.createRoot(document.getElementById('root')); 8 | root.render( 9 | 10 | 11 | , 12 | ); 13 | 14 | reportWebVitals(); 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/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = (onPerfEntry) => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ 4 | getCLS, getFID, getFCP, getLCP, getTTFB, 5 | }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard"], 3 | "plugins": ["stylelint-scss", "stylelint-csstree-validator"], 4 | "rules": { 5 | "at-rule-no-unknown": [ 6 | true, 7 | { 8 | "ignoreAtRules": ["tailwind", "apply", "variants", "responsive", "screen"] 9 | } 10 | ], 11 | "scss/at-rule-no-unknown": [ 12 | true, 13 | { 14 | "ignoreAtRules": ["tailwind", "apply", "variants", "responsive", "screen"] 15 | } 16 | ], 17 | "csstree/validator": true 18 | }, 19 | "ignoreFiles": ["build/**", "dist/**", "**/reset*.css", "**/bootstrap*.css", "**/*.js", "**/*.jsx"] 20 | } -------------------------------------------------------------------------------- /src/components/Nav.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-line 2 | import React from 'react'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | function Nav() { 6 | const navStyle = { 7 | color: '#ddd2e0', 8 | }; 9 | return ( 10 | 24 | ); 25 | } 26 | 27 | export default Nav; 28 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "jest": true 6 | }, 7 | "parser": "@babel/eslint-parser", 8 | "parserOptions": { 9 | "ecmaFeatures": { 10 | "jsx": true 11 | }, 12 | "ecmaVersion": 2018, 13 | "sourceType": "module" 14 | }, 15 | "extends": ["airbnb", "plugin:react/recommended"], 16 | "plugins": ["react"], 17 | "rules": { 18 | "react/jsx-filename-extension": ["warn", { "extensions": [".js", ".jsx"] }], 19 | "react/react-in-jsx-scope": "off", 20 | "import/no-unresolved": "off", 21 | "no-shadow": "off" 22 | }, 23 | "ignorePatterns": [ 24 | "dist/", 25 | "build/" 26 | ] 27 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | MATH MAGICIAN 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/logic/operate.js: -------------------------------------------------------------------------------- 1 | import Big from 'big.js'; 2 | 3 | export default function operate(numberOne, numberTwo, operation) { 4 | const one = Big(numberOne); 5 | const two = Big(numberTwo); 6 | if (operation === '+') { 7 | return one.plus(two).toString(); 8 | } 9 | if (operation === '-') { 10 | return one.minus(two).toString(); 11 | } 12 | if (operation === 'x') { 13 | return one.times(two).toString(); 14 | } 15 | if (operation === '÷') { 16 | try { 17 | return one.div(two).toString(); 18 | } catch (err) { 19 | return "Can't divide by 0."; 20 | } 21 | } 22 | if (operation === '%') { 23 | try { 24 | return one.mod(two).toString(); 25 | } catch (err) { 26 | return "Can't find modulo as can't divide by 0."; 27 | } 28 | } 29 | throw Error(`Unknown operation '${operation}'`); 30 | } 31 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import react from 'react'; 2 | import './index.css'; 3 | import { 4 | BrowserRouter, 5 | Routes, 6 | Route, 7 | } from 'react-router-dom'; 8 | import Nav from './components/Nav'; 9 | import Home from './components/Home'; 10 | import Calculator from './components/Calculator'; 11 | import Quote from './components/Quote'; 12 | 13 | class App extends react.Component { 14 | render() { 15 | return ( 16 |
17 | 18 |
26 | ); 27 | } 28 | } 29 | 30 | export default App; 31 | -------------------------------------------------------------------------------- /src/components/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function Home() { 4 | return ( 5 |
6 |

7 | WELCOME TO MATH MAGICIAN 8 |

9 |

10 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. 11 | Accusantium maximenobis similique nisi voluptatum, numquam 12 | perferendis deserunt fuga obcaecati iure,eius omnis aperiam 13 | quis. Officia voluptatum maiores soluta eveniet rem. Lorem, 14 | ipsum dolor 15 | 16 |

17 |

18 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. 19 | Accusantium maximenobis similique nisi voluptatum, numquam 20 | perferendis deserunt fuga obcaecati iure,eius omnis aperiam 21 | quis. Officia voluptatum maiores soluta eveniet rem. Lorem, 22 | ipsum dolor 23 | 24 |

25 |
26 | ); 27 | } 28 | 29 | export default Home; 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # math-magician 2 | 3 | "Math magicians" is a website for all fans of mathematics. It is a Single Page App (SPA) 4 | 5 | ### project spashoots 6 | ![math1](https://user-images.githubusercontent.com/60146030/177217028-758cf52e-39c3-4b01-aa9f-afebc8916816.JPG) 7 | ![math2](https://user-images.githubusercontent.com/60146030/177217050-c427d549-ddba-4e7a-91fd-2f807a3ffb2b.JPG) 8 | ![math3](https://user-images.githubusercontent.com/60146030/177217076-7cfa1480-ea2b-48a9-83a8-ba6f4742a6de.JPG) 9 | 10 | ### live demo 11 | - [Netlify live demo](https://thunderous-sawine-3c7e05.netlify.app/) 12 | - [Heroku live demo](https://math-magician-ap.herokuapp.com/) 13 | 14 | 15 | ##### For validation detection using Webhint Run 16 | 17 | npx hint . 18 | 19 | ## Available Scripts 20 | 21 | In the project directory, you can run: 22 | 23 | ### `npm start` 24 | once you have cloned the repo 25 | 26 | 27 | 28 | ## Author 29 | 30 | 👤 **Divine Charlotte** 31 | 32 | - GitHub: [@Divine](https://github.com/divinecharlotte) 33 | - LinkedIn: [Divine Charlotte](https://www.linkedin.com/in/charlotte-divine-dusenge-31b19017a/) 34 | 35 | ## 🤝 Contributing 36 | 37 | Contributions, issues, and feature requests are welcome! 38 | 39 | Feel free to check the [issues page](https://github.com/divinecharlotte/math-magician/issues). 40 | 41 | ## Show your support 42 | 43 | Give a ⭐️ if you like this project! 44 | 45 | ## Acknowledgments 46 | 47 | -This project was inspired by [Microverse](https://www.microverse.org) 48 | 49 | 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.4", 7 | "@testing-library/react": "^13.3.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "big-js": "^3.1.3", 10 | "big.js": "^6.2.0", 11 | "react": "^18.2.0", 12 | "react-dom": "^18.2.0", 13 | "react-router-dom": "^6.3.0", 14 | "react-scripts": "5.0.1", 15 | "web-vitals": "^2.1.4" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test", 21 | "eject": "react-scripts eject" 22 | }, 23 | "eslintConfig": { 24 | "extends": [ 25 | "react-app", 26 | "react-app/jest" 27 | ] 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.2%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | }, 41 | "devDependencies": { 42 | "@babel/core": "^7.18.5", 43 | "@babel/eslint-parser": "^7.18.2", 44 | "@babel/plugin-syntax-jsx": "^7.17.12", 45 | "@babel/preset-react": "^7.17.12", 46 | "eslint": "^7.32.0", 47 | "eslint-config-airbnb": "^18.2.1", 48 | "eslint-plugin-import": "^2.26.0", 49 | "eslint-plugin-jsx-a11y": "^6.6.0", 50 | "eslint-plugin-react": "^7.30.1", 51 | "eslint-plugin-react-hooks": "^4.6.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /.github/workflows/linters.yml: -------------------------------------------------------------------------------- 1 | name: Linters 2 | 3 | on: pull_request 4 | 5 | env: 6 | FORCE_COLOR: 1 7 | 8 | jobs: 9 | eslint: 10 | name: ESLint 11 | runs-on: ubuntu-18.04 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v1 15 | with: 16 | node-version: "12.x" 17 | - name: Setup ESLint 18 | run: | 19 | npm install --save-dev eslint@7.x eslint-config-airbnb@18.x eslint-plugin-import@2.x eslint-plugin-jsx-a11y@6.x eslint-plugin-react@7.x eslint-plugin-react-hooks@4.x @babel/eslint-parser@7.x @babel/core@7.x @babel/plugin-syntax-jsx@7.x @babel/preset-env@7.x @babel/preset-react@7.x 20 | [ -f .eslintrc.json ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/react-redux/.eslintrc.json 21 | [ -f .babelrc ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/react-redux/.babelrc 22 | - name: ESLint Report 23 | run: npx eslint . 24 | stylelint: 25 | name: Stylelint 26 | runs-on: ubuntu-18.04 27 | steps: 28 | - uses: actions/checkout@v2 29 | - uses: actions/setup-node@v1 30 | with: 31 | node-version: "12.x" 32 | - name: Setup Stylelint 33 | run: | 34 | npm install --save-dev stylelint@13.x stylelint-scss@3.x stylelint-config-standard@21.x stylelint-csstree-validator@1.x 35 | [ -f .stylelintrc.json ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/react-redux/.stylelintrc.json 36 | - name: Stylelint Report 37 | run: npx stylelint "**/*.{css,scss}" 38 | nodechecker: 39 | name: node_modules checker 40 | runs-on: ubuntu-18.04 41 | steps: 42 | - uses: actions/checkout@v2 43 | - name: Check node_modules existence 44 | run: | 45 | if [ -d "node_modules/" ]; then echo -e "\e[1;31mThe node_modules/ folder was pushed to the repo. Please remove it from the GitHub repository and try again."; echo -e "\e[1;32mYou can set up a .gitignore file with this folder included on it to prevent this from happening in the future." && exit 1; fi 46 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | *, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | font-family: sans-serif; 6 | } 7 | 8 | body { 9 | background-color: #320536; 10 | background-repeat: no-repeat; 11 | } 12 | 13 | button { 14 | font-size: 20px; 15 | font-weight: 600; 16 | padding: 20px 0; 17 | margin: 15px; 18 | outline: none; 19 | background-color: transparent; 20 | border: none; 21 | cursor: pointer; 22 | color: #ddd2e0; 23 | border-radius: 30%; 24 | } 25 | 26 | button:hover, 27 | button:focus { 28 | background-color: rgba(107, 13, 26, 0.03); 29 | } 30 | 31 | .calculator-main-cont { 32 | float: right; 33 | width: 400px; 34 | height: auto; 35 | background-color: #320536; 36 | border: white solid 2px; 37 | border-radius: 7px; 38 | } 39 | 40 | .main-container { 41 | display: flex; 42 | margin-top: 5%; 43 | justify-content: space-around; 44 | } 45 | 46 | .calculator-p { 47 | font-size: 40px; 48 | color: #ddd2e0; 49 | } 50 | 51 | .calculator-output { 52 | background-color: #c4c4c4; 53 | text-align: end; 54 | padding: 20px 10px; 55 | color: #320536; 56 | font-size: 30px; 57 | font-weight: 500; 58 | } 59 | 60 | .calculator-wrapper { 61 | display: grid; 62 | grid-template-columns: 9fr 3fr; 63 | } 64 | 65 | .calculator-numbers { 66 | display: grid; 67 | grid-template-columns: 1fr 1fr 1fr; 68 | text-align: center; 69 | } 70 | 71 | .calculator-numbers:last-child { 72 | display: grid; 73 | grid-template-columns: 2fr 1fr; 74 | } 75 | 76 | .calculator-numbers button, 77 | .calculator-symbol-cont button { 78 | border: 1px solid #e5e5e5; 79 | } 80 | 81 | .calculator-symbol-cont { 82 | background-color: #ddd2e0; 83 | text-align: center; 84 | display: grid; 85 | grid-template-columns: auto; 86 | border: 1px solid #ddd2e0; 87 | } 88 | 89 | .symbol-button { 90 | color: #320536; 91 | border: #320536 solid 1px; 92 | } 93 | 94 | .navigation { 95 | display: flex; 96 | justify-content: space-around; 97 | align-items: center; 98 | min-height: 10vh; 99 | color: #ddd2e0; 100 | background-image: linear-gradient(to bottom right, #320536, #f162ff); 101 | } 102 | 103 | .nav-links { 104 | display: flex; 105 | width: 50%; 106 | justify-content: space-around; 107 | align-items: center; 108 | list-style: none; 109 | } 110 | 111 | .nav-links a { 112 | color: #ddd2e0; 113 | text-decoration: none; 114 | font-size: 20px; 115 | } 116 | 117 | .nav-links a :hover { 118 | text-decoration: underline; 119 | color: #830808; 120 | transition: 2s; 121 | } 122 | 123 | .home-content { 124 | width: 80%; 125 | margin: auto; 126 | height: 90vh; 127 | display: flex; 128 | flex-direction: column; 129 | justify-content: center; 130 | gap: 30px; 131 | align-items: center; 132 | color: #ddd2e0; 133 | font-size: 20px; 134 | } 135 | -------------------------------------------------------------------------------- /src/logic/calculate.js: -------------------------------------------------------------------------------- 1 | import operate from './operate'; 2 | 3 | function isNumber(item) { 4 | return !!item.match(/[0-9]+/); 5 | } 6 | 7 | /** 8 | * Given a button name and a calculator data object, return an updated 9 | * calculator data object. 10 | * 11 | * Calculator data object contains: 12 | * total:s the running total 13 | * next:String the next number to be operated on with the total 14 | * operation:String +, -, etc. 15 | */ 16 | export default function calculate(obj, buttonName) { 17 | if (buttonName === 'AC') { 18 | return { 19 | total: null, 20 | next: null, 21 | operation: null, 22 | }; 23 | } 24 | 25 | if (isNumber(buttonName)) { 26 | if (buttonName === '0' && obj.next === '0') { 27 | return {}; 28 | } 29 | // If there is an operation, update next 30 | if (obj.operation) { 31 | if (obj.next && obj.next !== '0') { 32 | return { ...obj, next: obj.next + buttonName }; 33 | } 34 | return { ...obj, next: buttonName }; 35 | } 36 | // If there is no operation, update next and clear the value 37 | if (obj.next && obj.next !== '0') { 38 | return { 39 | next: obj.next + buttonName, 40 | total: null, 41 | }; 42 | } 43 | return { 44 | next: buttonName, 45 | total: null, 46 | }; 47 | } 48 | 49 | if (buttonName === '.') { 50 | if (obj.next) { 51 | if (obj.next.includes('.')) { 52 | return { ...obj }; 53 | } 54 | return { ...obj, next: `${obj.next}.` }; 55 | } 56 | if (obj.operation) { 57 | return { ...obj, next: '0.' }; 58 | } 59 | if (obj.total) { 60 | if (obj.total.includes('.')) { 61 | return {}; 62 | } 63 | return { ...obj, next: `${obj.total}.` }; 64 | } 65 | return { ...obj, next: '0.' }; 66 | } 67 | 68 | if (buttonName === '=') { 69 | if (obj.next && obj.operation) { 70 | return { 71 | total: operate(obj.total, obj.next, obj.operation), 72 | next: null, 73 | operation: null, 74 | }; 75 | } 76 | // '=' with no operation, nothing to do 77 | return {}; 78 | } 79 | 80 | if (buttonName === '+/-') { 81 | if (obj.next) { 82 | return { ...obj, next: (-1 * parseFloat(obj.next)).toString() }; 83 | } 84 | if (obj.total) { 85 | return { ...obj, total: (-1 * parseFloat(obj.total)).toString() }; 86 | } 87 | return {}; 88 | } 89 | 90 | // Button must be an operation 91 | 92 | // When the user presses an operation button without having entered 93 | // a number first, do nothing. 94 | // if (!obj.next && !obj.total) { 95 | // return {}; 96 | // } 97 | 98 | // User pressed an operation after pressing '=' 99 | if (!obj.next && obj.total && !obj.operation) { 100 | return { ...obj, operation: buttonName }; 101 | } 102 | 103 | // User pressed an operation button and there is an existing operation 104 | if (obj.operation) { 105 | if (obj.total && !obj.next) { 106 | return { ...obj, operation: buttonName }; 107 | } 108 | 109 | if (!obj.total) { 110 | return { total: 0, operation: buttonName }; 111 | } 112 | 113 | return { 114 | total: operate(obj.total, obj.next, obj.operation), 115 | next: null, 116 | operation: buttonName, 117 | }; 118 | } 119 | 120 | // no operation yet, but the user typed one 121 | 122 | // The user hasn't typed a number yet, just save the operation 123 | if (!obj.next) { 124 | return { operation: buttonName }; 125 | } 126 | 127 | // save the operation and shift 'next' into 'total' 128 | return { 129 | total: obj.next, 130 | next: null, 131 | operation: buttonName, 132 | }; 133 | } 134 | -------------------------------------------------------------------------------- /src/components/Calculator.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import calculate from '../logic/calculate'; 3 | 4 | function Calculator() { 5 | const [state, setState] = useState({ 6 | obj: { 7 | total: null, 8 | next: null, 9 | operation: null, 10 | }, 11 | }); 12 | 13 | const handleClick = (buttonName) => setState(({ obj }) => ({ obj: calculate(obj, buttonName) })); 14 | 15 | return ( 16 |
17 |
18 |

Let's do some math

19 |
20 |
21 |
22 | {state.obj.next || state.obj.total || 0} 23 |
24 |
25 |
26 |
27 | 30 | 33 | 36 |
37 |
38 | 41 | 44 | 47 |
48 |
49 | 52 | 55 | 58 |
59 |
60 | 63 | 66 | 69 |
70 |
71 | 74 | 77 |
78 |
79 |
80 | 87 | 94 | 101 | 108 | 115 |
116 |
117 |
118 |
119 | ); 120 | } 121 | 122 | export default Calculator; 123 | --------------------------------------------------------------------------------