├── 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 |
19 |
20 | } />
21 | } />
22 | } />
23 |
24 |
25 |
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 | 
7 | 
8 | 
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 |
--------------------------------------------------------------------------------