├── .babelrc ├── .eslintrc.json ├── .github └── workflows │ └── linters.yml ├── .gitignore ├── .stylelintrc.json ├── .vscode └── settings.json ├── LICENSE.md ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── App.css ├── App.js ├── __tests__ ├── __snapshots__ │ ├── header.test.js.snap │ ├── missions.test.js.snap │ ├── myprofile.test.js.snap │ ├── rocket.test.js.snap │ └── rocketInfo.test.js.snap ├── header.test.js ├── missions.test.js ├── myprofile.test.js ├── rocket.test.js └── rocketInfo.test.js ├── components ├── Header.css ├── Header.js ├── Rockets.css ├── Rockets.js ├── missions.css ├── missions.js ├── planet1.png ├── profile.css ├── profile.js └── rocketInfo.js ├── index.css ├── index.js └── redux ├── missions └── missionsSlice.js ├── rockets └── rocketsSlice.js └── store.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-react" 4 | ], 5 | "plugins": ["@babel/plugin-syntax-jsx"] 6 | } -------------------------------------------------------------------------------- /.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", "plugin:react-hooks/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 | "overrides": [ 24 | { 25 | // feel free to replace with your preferred file pattern - eg. 'src/**/*Slice.js' or 'redux/**/*Slice.js' 26 | "files": ["src/**/*Slice.js"], 27 | // avoid state param assignment 28 | "rules": { "no-param-reassign": ["error", { "props": false }] } 29 | } 30 | ], 31 | "ignorePatterns": [ 32 | "dist/", 33 | "build/" 34 | ] 35 | } -------------------------------------------------------------------------------- /.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-22.04 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version: "18.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 "**/*.{js,jsx}" 24 | stylelint: 25 | name: Stylelint 26 | runs-on: ubuntu-22.04 27 | steps: 28 | - uses: actions/checkout@v3 29 | - uses: actions/setup-node@v3 30 | with: 31 | node-version: "18.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-22.04 41 | steps: 42 | - uses: actions/checkout@v3 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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Myprofile" 4 | ] 5 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 NOEL NOMGNE FOKA And Suleiman Gacheru 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 | 7 |

Space Traveler's Hub

8 | 9 |
10 | 11 | # 📗 Table of Contents 12 | 13 | - [📗 Table of Contents](#-table-of-contents) 14 | - [📖 Math\_Magicians ](#-math_magicians-) 15 | - [🛠 Built With ](#-built-with-) 16 | - [Tech Stack ](#tech-stack-) 17 | - [Key Features ](#key-features-) 18 | - [💻 Getting Started ](#-getting-started-) 19 | - [Prerequisites](#prerequisites) 20 | - [Setup](#setup) 21 | - [Install](#install) 22 | - [Usage](#usage) 23 | - [Run tests](#run-tests) 24 | - [Deployment](#deployment) 25 | - [👥 Authors ](#-authors-) 26 | - [🔭 Future Features ](#-future-features-) 27 | - [🤝 Contributing ](#-contributing-) 28 | - [⭐️ Show your support ](#️-show-your-support-) 29 | - [🙏 Acknowledgments ](#-acknowledgments-) 30 | - [📝 License ](#-license-) 31 | 32 | # 📖 Math_Magicians 33 | 34 | This Project core Goal is for clients to Book a Space Shuttle or Cancel the Book Reservation. 35 | ## 🛠 Built With 36 | - React 37 | - Redux 38 | - JAVASCRIPT 39 | -Css 40 | 41 | ### Tech Stack 42 | 43 |
44 | Client 45 | 50 |
51 | 52 | ### Key Features 53 | 54 | - **Responsive Design** 55 | - **dynamic** 56 | - **Code Quality** 57 | 58 |

(back to top)

59 | 60 | ## 💻 Getting Started 61 | 62 | 63 | 64 | ### Prerequisites 65 | 66 | In order to run this project you need: 67 | 68 | - You need to have NodeJS installed 69 | - A Web Browser (Google Chrome, Firefox, etc) 70 | - A Code Editor (Notepad++, VSCode, etc) 71 | 72 | ### Setup 73 | 74 | Clone this repository to your desired folder: 75 | 76 | ``` 77 | git clone your link repo https://github.com/noelfoka/react-group-project.git 78 | 79 | cd your folder name 80 | ``` 81 | 82 | ### Install 83 | 84 | Install this project with: 85 | 86 | ``` 87 | npm install 88 | ``` 89 | 90 | ### Usage 91 | 92 | To run the project, execute the following command: 93 | 94 | ```sh 95 | npm start 96 | ``` 97 | 98 | ### Run tests 99 | 100 | To run tests, run the following command: 101 | 102 | npx hint . 103 | 104 | ### Deployment 105 | 106 | You can deploy this project using: 107 | 108 | Deploy this project on any web server 109 | 110 |

(back to top)

111 | 112 | ## 👥 Authors 113 | 114 | 👤 **suleiman gacheru ** 115 | 116 | - GitHub: [@githubhandle](https://github.com/suleiman) 117 | - Twitter: [@twitterhandle](https://twitter.com/Asuleiman) 118 | - LinkedIn: [@LinkedIn](https://www.linkedin.com/suleimangacheru/) 119 | 120 | 121 | 👤 **Noel Foka ** 122 | 123 | - GitHub: [@noelfoka](https://github.com/noelfoka) 124 | - Twitter: [@twitterhandle](https://twitter.com/noelnomgne) 125 | - LinkedIn: [@LinkedIn](https://www.linkedin.com/noelfoka/) 126 | 127 |

(back to top)

128 | 129 | ## 🔭 Future Features 130 | 131 | - [ ] **Adding more pages** 132 | - [ ] **FrontEnd Framework implementation** 133 | 134 |

(back to top)

135 | 136 | ## 🤝 Contributing 137 | 138 | Contributions, issues, and feature requests are welcome! 139 | 140 | Feel free to check the [issues page](../../issues/). 141 | 142 |

(back to top)

143 | 144 | ## ⭐️ Show your support 145 | 146 | If you like this project give me a star. 147 | 148 |

(back to top)

149 | 150 | ## 🙏 Acknowledgments 151 | 152 | We would like to thank Microverse. 153 | 154 |

(back to top)

155 | 156 | ## 📝 License 157 | 158 | 159 | This project is [MIT](./LICENSE.md) licensed. 160 | 161 | 162 |

(back to top)

-------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-group-project", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reduxjs/toolkit": "^1.9.5", 7 | "@testing-library/jest-dom": "^5.17.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "axios": "^1.5.0", 10 | "bootstrap": "^5.3.1", 11 | "prop-types": "^15.8.1", 12 | "react": "^18.2.0", 13 | "react-bootstrap": "^2.8.0", 14 | "react-dom": "^18.2.0", 15 | "react-icons": "^4.10.1", 16 | "react-redux": "^8.1.2", 17 | "react-router": "^6.15.0", 18 | "react-router-dom": "^6.15.0", 19 | "react-scripts": "^5.0.1", 20 | "redux": "^4.2.1", 21 | "redux-logger": "^3.0.6", 22 | "redux-thunk": "^2.4.2", 23 | "web-vitals": "^2.1.4" 24 | }, 25 | "scripts": { 26 | "start": "react-scripts start", 27 | "build": "react-scripts build", 28 | "test": "react-scripts test", 29 | "eject": "react-scripts eject" 30 | }, 31 | "eslintConfig": { 32 | "extends": [ 33 | "react-app", 34 | "react-app/jest" 35 | ] 36 | }, 37 | "jest": { 38 | "moduleNameMapper": { 39 | "axios": "axios/dist/node/axios.cjs" 40 | } 41 | }, 42 | "browserslist": { 43 | "production": [ 44 | ">0.2%", 45 | "not dead", 46 | "not op_mini all" 47 | ], 48 | "development": [ 49 | "last 1 chrome version", 50 | "last 1 firefox version", 51 | "last 1 safari version" 52 | ] 53 | }, 54 | "devDependencies": { 55 | "@babel/core": "^7.22.11", 56 | "@babel/eslint-parser": "^7.22.11", 57 | "@babel/plugin-proposal-private-property-in-object": "^7.21.11", 58 | "@babel/plugin-syntax-jsx": "^7.22.5", 59 | "@babel/preset-react": "^7.22.5", 60 | "@testing-library/react": "^14.0.0", 61 | "eslint": "^7.32.0", 62 | "eslint-config-airbnb": "^18.2.1", 63 | "eslint-plugin-import": "^2.28.1", 64 | "eslint-plugin-jsx-a11y": "^6.7.1", 65 | "eslint-plugin-react": "^7.33.2", 66 | "eslint-plugin-react-hooks": "^4.6.0", 67 | "jest": "^27.5.1", 68 | "react-test-renderer": "^18.2.0", 69 | "redux-mock-store": "^1.5.4", 70 | "stylelint": "^13.13.1", 71 | "stylelint-config-standard": "^21.0.0", 72 | "stylelint-csstree-validator": "^1.9.0", 73 | "stylelint-scss": "^3.21.0" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noelfoka/react-group-project/52fc523b517d7a0ba664fa50a8ad470dae4f6be8/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noelfoka/react-group-project/52fc523b517d7a0ba664fa50a8ad470dae4f6be8/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noelfoka/react-group-project/52fc523b517d7a0ba664fa50a8ad470dae4f6be8/public/logo512.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | 36 | to { 37 | transform: rotate(360deg); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { Outlet, Routes, Route } from 'react-router'; 2 | import { useDispatch } from 'react-redux'; 3 | import { useEffect } from 'react'; 4 | import Header from './components/Header'; 5 | import './App.css'; 6 | import MyProfile from './components/profile'; 7 | import Missions from './components/missions'; 8 | import Rockets from './components/Rockets'; 9 | import { fetchRockets } from './redux/rockets/rocketsSlice'; 10 | import { fetchMissionData } from './redux/missions/missionsSlice'; 11 | 12 | const Home = () => ( 13 | <> 14 |
15 | 16 | 17 | ); 18 | 19 | function App() { 20 | const dispatch = useDispatch(); 21 | 22 | useEffect(() => { 23 | dispatch(fetchRockets()); 24 | dispatch(fetchMissionData()); 25 | }); 26 | 27 | return ( 28 | 29 | }> 30 | } /> 31 | } /> 32 | } /> 33 | 34 | 35 | ); 36 | } 37 | 38 | export default App; 39 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/header.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Header component should render correctly 1`] = ` 4 |
5 |
6 |

7 | Space Travelers' Hub 8 |

9 | 40 |
41 |
42 | `; 43 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/missions.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Missions component renders correctly 1`] = ` 4 |
5 |
8 |

9 | Missions 10 |

11 |
14 |
    17 |
  • 20 | Mission Name 21 |
  • 22 |
  • 25 | Description 26 |
  • 27 |
  • 30 | Status 31 |
  • 32 |
  • 35 | Action 36 |
  • 37 |
38 |
    41 |
  • 44 | Thaicom 45 |
  • 46 |
  • 49 | Thaicom is the name of a series of communications ... Thailand and modern communications technology. 50 |
  • 51 |
  • 54 | 61 |
  • 62 |
  • 65 | 72 |
  • 73 |
74 |
    77 |
  • 80 | Telstar 81 |
  • 82 |
  • 85 | Telstar 19V (Telstar 19 Vantage) is a communicatio ... launched by Ariane 5ECA on 1 July 2009. 86 |
  • 87 |
  • 90 | 97 |
  • 98 |
  • 101 | 108 |
  • 109 |
110 |
111 |
112 |
113 | `; 114 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/myprofile.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Profile page it renders 1`] = ` 4 |
7 |
10 |

13 | My Missions 14 |

15 |
    18 |
19 |
22 |

25 | My Rockets 26 |

27 |
    30 |
31 |
32 | `; 33 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/rocket.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Rockets component renders correctly 1`] = ` 4 |
5 |
6 |
9 |
10 | rockets-pictures 15 |
16 |
19 |

20 | Falcon 9 21 |

22 |

23 | A powerful rocket developed by SpaceX. 24 |

25 | 31 |
32 |
33 |
34 |
35 | `; 36 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/rocketInfo.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`RocketInfo component should render correctly 1`] = ` 4 |
5 |
8 |
9 | rockets-pictures 14 |
15 |
18 |

19 | Falcon 9 20 |

21 |

22 | A powerful rocket developed by SpaceX. 23 |

24 | 30 |
31 |
32 |
33 | `; 34 | -------------------------------------------------------------------------------- /src/__tests__/header.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import { BrowserRouter as Router } from 'react-router-dom'; 4 | import Header from '../components/Header'; 5 | import '@testing-library/jest-dom/extend-expect'; 6 | 7 | describe('Header component', () => { 8 | it('should render correctly', () => { 9 | const { container } = render( 10 | 11 |
12 | , 13 | ); 14 | 15 | expect(container).toMatchSnapshot(); 16 | }); 17 | 18 | it('should have active link for Rockets', () => { 19 | const { getByText } = render( 20 | 21 |
22 | , 23 | ); 24 | 25 | const rocketsLink = getByText('Rockets'); 26 | expect(rocketsLink).toBeInTheDocument(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/__tests__/missions.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import { useSelector } from 'react-redux'; 4 | import Missions from '../components/missions'; 5 | 6 | // Mocking react-redux's useSelector 7 | jest.mock('react-redux'); 8 | 9 | // Mocking axios 10 | jest.mock('axios', () => ({ 11 | get: jest.fn(), 12 | })); 13 | 14 | test('Missions component renders correctly', () => { 15 | // Mock the useSelector behavior 16 | useSelector.mockReturnValue([ 17 | { 18 | mission_id: '9D1B7E0', 19 | mission_name: 'Thaicom', 20 | description: 'Thaicom is the name of a series of communications ... Thailand and modern communications technology.', 21 | reserved: true, 22 | }, 23 | { 24 | mission_id: 'F4F83DE', 25 | mission_name: 'Telstar', 26 | description: 'Telstar 19V (Telstar 19 Vantage) is a communicatio ... launched by Ariane 5ECA on 1 July 2009.', 27 | reserved: true, 28 | }, 29 | // Add more mission objects as needed 30 | ]); 31 | 32 | const { container } = render(); 33 | 34 | expect(container).toMatchSnapshot(); 35 | }); 36 | -------------------------------------------------------------------------------- /src/__tests__/myprofile.test.js: -------------------------------------------------------------------------------- 1 | import { Provider } from 'react-redux'; 2 | import renderer from 'react-test-renderer'; 3 | import Profile from '../components/profile'; 4 | import store from '../redux/store'; 5 | 6 | describe('Profile page', () => { 7 | it('it renders', () => { 8 | const profile = renderer 9 | .create( 10 | 11 | 12 | , 13 | ) 14 | .toJSON(); 15 | expect(profile).toMatchSnapshot(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/__tests__/rocket.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import { useSelector } from 'react-redux'; 4 | import Rockets from '../components/Rockets'; 5 | 6 | // Mocking react-redux's useSelector 7 | jest.mock('react-redux'); 8 | 9 | // Mocking axios 10 | jest.mock('axios', () => ({ 11 | get: jest.fn(), 12 | })); 13 | 14 | test('Rockets component renders correctly', () => { 15 | // Mock the useSelector behavior 16 | useSelector.mockReturnValue({ 17 | rockets: [ 18 | { 19 | id: 1, 20 | name: 'Falcon 9', 21 | description: 'A powerful rocket developed by SpaceX.', 22 | flickr_images: ['image1.jpg', 'image2.jpg'], 23 | reserved: false, 24 | }, 25 | // Add more mock rockets as needed 26 | ], 27 | }); 28 | 29 | const { container } = render(); 30 | 31 | expect(container).toMatchSnapshot(); 32 | }); 33 | -------------------------------------------------------------------------------- /src/__tests__/rocketInfo.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, fireEvent, screen } from '@testing-library/react'; 3 | import { useDispatch } from 'react-redux'; 4 | import RocketInfo from '../components/rocketInfo'; 5 | import { reserveRocket, cancelRocket } from '../redux/rockets/rocketsSlice'; 6 | 7 | // Mocking react-redux's useDispatch 8 | jest.mock('react-redux'); 9 | 10 | // Mocking actions 11 | jest.mock('../redux/rockets/rocketsSlice', () => ({ 12 | reserveRocket: jest.fn(), 13 | cancelRocket: jest.fn(), 14 | })); 15 | 16 | describe('RocketInfo component', () => { 17 | it('should render correctly', () => { 18 | const { container } = render( 19 | , 26 | ); 27 | 28 | expect(container).toMatchSnapshot(); 29 | }); 30 | 31 | it('should call reserveRocket when "Reserve Rocket" button is clicked', () => { 32 | const mockDispatch = jest.fn(); 33 | useDispatch.mockReturnValue(mockDispatch); 34 | 35 | render( 36 | , 43 | ); 44 | 45 | const reserveButton = screen.getByText('Reserve Rocket'); 46 | fireEvent.click(reserveButton); 47 | 48 | expect(mockDispatch).toHaveBeenCalledWith(reserveRocket(1)); 49 | }); 50 | 51 | it('should call cancelRocket when "Cancel Reservation" button is clicked', () => { 52 | const mockDispatch = jest.fn(); 53 | useDispatch.mockReturnValue(mockDispatch); 54 | 55 | render( 56 | , 63 | ); 64 | 65 | const cancelButton = screen.getByText('Cancel Reservation'); 66 | fireEvent.click(cancelButton); 67 | 68 | expect(mockDispatch).toHaveBeenCalledWith(cancelRocket(1)); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /src/components/Header.css: -------------------------------------------------------------------------------- 1 | header { 2 | display: flex; 3 | justify-content: space-between; 4 | align-items: center; 5 | padding: 20px; 6 | background-color: #fff; 7 | border-bottom: 1px solid #ddd; 8 | } 9 | 10 | header > * { 11 | margin: 0 3%; 12 | } 13 | 14 | header h1 { 15 | padding: 20px 0 20px 100px; 16 | font-size: 30px; 17 | font-weight: 400; 18 | color: #000; 19 | background: url(planet1.png) top left/contain no-repeat; 20 | } 21 | 22 | .navbar ul { 23 | display: flex; 24 | list-style: none; 25 | } 26 | 27 | .navbar a { 28 | margin: 0 20px; 29 | color: #02bab7; 30 | text-decoration: none; 31 | font-size: 20px; 32 | } 33 | 34 | .navbar ul li:last-of-type { 35 | border-right: none; 36 | } 37 | 38 | .navbar a:hover, 39 | .navbar .active { 40 | color: #9ad0cf; 41 | border-bottom: 2px solid #02bab7; 42 | } 43 | 44 | .profile-link { 45 | border-left: 1px solid #333; 46 | padding-left: 5px; 47 | } 48 | 49 | @media (max-width: 960px) { 50 | header { 51 | flex-direction: column; 52 | padding: 20px 0; 53 | } 54 | 55 | header h1 { 56 | padding: 10px 0 10px 150px; 57 | font-size: 30px; 58 | font-weight: 400; 59 | color: #000; 60 | background: url(planet1.png) top left/contain no-repeat; 61 | } 62 | 63 | .navbar ul { 64 | padding: 0; 65 | } 66 | 67 | .navbar a { 68 | padding: 0 10px; 69 | font-size: 12px; 70 | } 71 | 72 | .profile-link { 73 | padding-top: 2px; 74 | border-left: 1px solid #333; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | import './Header.css'; 2 | import { NavLink } from 'react-router-dom'; 3 | 4 | function Header() { 5 | return ( 6 |
7 |

Space Travelers' Hub

8 | 30 |
31 | ); 32 | } 33 | export default Header; 34 | -------------------------------------------------------------------------------- /src/components/Rockets.css: -------------------------------------------------------------------------------- 1 | .rockets-container { 2 | display: flex; 3 | } 4 | 5 | .rockets-pictures { 6 | width: 20rem; 7 | height: 15rem; 8 | margin: 1rem; 9 | } 10 | 11 | .reserve-rocket { 12 | background-color: rgb(70, 120, 238); 13 | color: white; 14 | border: none; 15 | padding: 0.8rem; 16 | border-radius: 5%; 17 | cursor: pointer; 18 | } 19 | 20 | .reserved-tag { 21 | background-color: rgba(94, 201, 166, 0.97); 22 | color: white; 23 | padding: 0.2rem; 24 | margin-right: 0.2rem; 25 | } 26 | 27 | .reserved-button { 28 | background-color: white; 29 | color: silver; 30 | border: 1px solid black; 31 | border-radius: 5%; 32 | padding: 0.5rem; 33 | cursor: pointer; 34 | } 35 | -------------------------------------------------------------------------------- /src/components/Rockets.js: -------------------------------------------------------------------------------- 1 | import { useSelector } from 'react-redux'; 2 | import RocketInfo from './rocketInfo'; 3 | import './Rockets.css'; 4 | 5 | const Rockets = () => { 6 | const { rockets } = useSelector((store) => store.rockets); 7 | 8 | return ( 9 |
10 | {rockets.map((rocket) => ( 11 | 19 | ))} 20 |
21 | ); 22 | }; 23 | 24 | export default Rockets; 25 | -------------------------------------------------------------------------------- /src/components/missions.css: -------------------------------------------------------------------------------- 1 | .missions-page { 2 | padding: 20px; 3 | } 4 | 5 | .mission-head { 6 | font-weight: 700; 7 | } 8 | 9 | .mission-set { 10 | margin: 0; 11 | padding: 15px 10px; 12 | list-style: none; 13 | display: flex; 14 | justify-content: space-between; 15 | border-bottom: 1px solid #ddd; 16 | } 17 | 18 | .mission-set:nth-of-type(odd) { 19 | background-color: #ddd; 20 | } 21 | 22 | .mission-name { 23 | font-weight: 700; 24 | } 25 | 26 | .mission-name, 27 | .mission-status, 28 | .mission-action { 29 | width: 10%; 30 | } 31 | 32 | .mission-description { 33 | width: 60%; 34 | } 35 | 36 | .membership, 37 | .membership_on { 38 | background-color: #6d757d; 39 | color: #fff; 40 | border-radius: 5px; 41 | border: 0; 42 | padding: 5px; 43 | } 44 | 45 | .membership_on { 46 | background-color: #02bab7; 47 | } 48 | 49 | .join, 50 | .join_on { 51 | background-color: transparent; 52 | color: #000; 53 | border-radius: 5px; 54 | border: 1px solid #000; 55 | padding: 5px; 56 | } 57 | 58 | .join_on { 59 | color: #f00; 60 | border-color: #f00; 61 | } 62 | 63 | @media (max-width: 960px) { 64 | .missions-page { 65 | padding: 20px 10px; 66 | } 67 | 68 | .mission-head { 69 | display: none; 70 | } 71 | 72 | .mission-set { 73 | flex-wrap: wrap; 74 | } 75 | 76 | .mission-name { 77 | width: 50%; 78 | } 79 | 80 | .mission-status, 81 | .mission-action { 82 | width: 20%; 83 | } 84 | 85 | .mission-description { 86 | padding-top: 20px; 87 | width: 100%; 88 | order: 1; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/components/missions.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | import { markMissionAsReserved, leaveMission } from '../redux/missions/missionsSlice'; 4 | import './missions.css'; 5 | 6 | const Missions = () => { 7 | const dispatch = useDispatch(); 8 | const missions = useSelector((state) => state.mission.missions); 9 | 10 | const handleMissionAction = (missionId) => { 11 | const mission = missions.find((mission) => mission.mission_id === missionId); 12 | 13 | if (mission) { 14 | if (mission.reserved) { 15 | dispatch(leaveMission({ missionId })); 16 | } else { 17 | dispatch(markMissionAsReserved({ missionId, reserved: true })); 18 | } 19 | } 20 | }; 21 | 22 | return ( 23 |
24 |

Missions

25 |
26 |
    27 |
  • Mission Name
  • 28 |
  • Description
  • 29 |
  • Status
  • 30 |
  • Action
  • 31 |
32 | {missions.map((mission) => ( 33 |
    34 |
  • {mission.mission_name}
  • 35 |
  • {mission.description}
  • 36 |
  • 37 | 40 |
  • 41 |
  • 42 | 45 |
  • 46 |
47 | ))} 48 |
49 |
50 | ); 51 | }; 52 | 53 | export default Missions; 54 | -------------------------------------------------------------------------------- /src/components/planet1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noelfoka/react-group-project/52fc523b517d7a0ba664fa50a8ad470dae4f6be8/src/components/planet1.png -------------------------------------------------------------------------------- /src/components/profile.css: -------------------------------------------------------------------------------- 1 | .profile-page { 2 | padding: 20px; 3 | display: flex; 4 | flex-wrap: wrap; 5 | justify-content: space-around; 6 | } 7 | 8 | .profile-section { 9 | width: 30%; 10 | } 11 | 12 | .profile-head { 13 | font-weight: normal; 14 | } 15 | 16 | .profile-content { 17 | padding: 0; 18 | border: 1px solid #ddd; 19 | list-style: none; 20 | border-radius: 5px; 21 | } 22 | 23 | .profile-item { 24 | padding: 10px 20px; 25 | border-bottom: 1px solid #ddd; 26 | } 27 | 28 | .profile-item:last-of-type { 29 | border-bottom: none; 30 | } 31 | 32 | @media (max-width: 960px) { 33 | .profile-page { 34 | flex-direction: column; 35 | } 36 | 37 | .profile-section { 38 | width: 100%; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/profile.js: -------------------------------------------------------------------------------- 1 | import { useSelector } from 'react-redux'; 2 | import './profile.css'; 3 | 4 | const MyProfile = () => { 5 | const missions = useSelector((state) => state.mission.missions); 6 | const reservedMissions = missions.filter( 7 | (mission) => mission.reserved === true, 8 | ); 9 | 10 | const { rockets } = useSelector((state) => state.rockets); 11 | const reserveRocket = rockets.filter((rocket) => rocket.reserved); 12 | 13 | return ( 14 |
15 |
16 |

My Missions

17 |
    18 | {reservedMissions.map((mission) => ( 19 |
  • 20 | {mission.mission_name} 21 |
  • 22 | ))} 23 |
24 |
25 |
26 |

My Rockets

27 |
    28 | {reserveRocket.map((rocket) => ( 29 |
  • 30 | {rocket.name} 31 |
  • 32 | ))} 33 |
34 |
35 |
36 | ); 37 | }; 38 | 39 | export default MyProfile; 40 | -------------------------------------------------------------------------------- /src/components/rocketInfo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { useDispatch } from 'react-redux'; 4 | import { reserveRocket, cancelRocket } from '../redux/rockets/rocketsSlice'; 5 | 6 | const RocketInfo = ({ 7 | name, description, image, id, reserved, 8 | }) => { 9 | const dispatch = useDispatch(); 10 | 11 | const toggleReservedStatus = () => { 12 | if (reserved) { 13 | dispatch(cancelRocket(id)); 14 | } else { 15 | dispatch(reserveRocket(id)); 16 | } 17 | }; 18 | 19 | const buttonClass = reserved ? 'reserved-button' : 'reserve-rocket'; 20 | 21 | return ( 22 |
23 |
24 | rockets-pictures 25 |
26 |
27 |

{name}

28 |

29 | {reserved ? Reserved : null} 30 | {description} 31 |

32 | 39 |
40 |
41 | ); 42 | }; 43 | 44 | export default RocketInfo; 45 | 46 | RocketInfo.propTypes = { 47 | name: PropTypes.string.isRequired, 48 | description: PropTypes.string.isRequired, 49 | image: PropTypes.arrayOf(PropTypes.string).isRequired, 50 | id: PropTypes.string.isRequired, 51 | reserved: PropTypes.bool, 52 | }; 53 | 54 | RocketInfo.defaultProps = { 55 | reserved: false, 56 | }; 57 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | div { 6 | margin: 0; 7 | padding: 0; 8 | } 9 | 10 | body, 11 | input { 12 | font-family: Verdana, Geneva, Tahoma, sans-serif; 13 | font-size: 14px; 14 | } 15 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import { BrowserRouter } from 'react-router-dom'; 5 | import { Provider } from 'react-redux'; 6 | import App from './App'; 7 | import store from './redux/store'; 8 | 9 | const root = ReactDOM.createRoot(document.getElementById('root')); 10 | root.render( 11 | 12 | 13 | 14 | 15 | 16 | 17 | , 18 | ); 19 | -------------------------------------------------------------------------------- /src/redux/missions/missionsSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; 2 | import axios from 'axios'; 3 | 4 | const initialState = { 5 | missions: [], 6 | }; 7 | 8 | export const fetchMissionData = createAsyncThunk( 9 | 'mission/fetchMissionData', 10 | async () => { 11 | const response = await axios.get('https://api.spacexdata.com/v3/missions'); 12 | return response.data; 13 | }, 14 | ); 15 | 16 | const missionSlice = createSlice({ 17 | name: 'mission', 18 | initialState, 19 | reducers: { 20 | markMissionAsReserved: (state, action) => { 21 | const { missionId } = action.payload; 22 | 23 | state.missions = state.missions.map((mission) => { 24 | if (mission.mission_id === missionId) { 25 | return { ...mission, reserved: true }; 26 | } 27 | return mission; 28 | }); 29 | }, 30 | 31 | leaveMission: (state, action) => { 32 | const { missionId } = action.payload; 33 | 34 | state.missions = state.missions.map((mission) => { 35 | if (mission.mission_id === missionId) { 36 | return { ...mission, reserved: false }; 37 | } 38 | return mission; 39 | }); 40 | }, 41 | }, 42 | extraReducers: (builder) => { 43 | builder.addCase(fetchMissionData.fulfilled, (state, action) => { 44 | const missionsData = action.payload.map((mission) => ({ 45 | mission_id: mission.mission_id, 46 | mission_name: mission.mission_name, 47 | description: mission.description, 48 | reserved: false, 49 | })); 50 | state.missions = missionsData; 51 | }); 52 | }, 53 | }); 54 | 55 | export const { markMissionAsReserved, leaveMission } = missionSlice.actions; 56 | 57 | export default missionSlice.reducer; 58 | -------------------------------------------------------------------------------- /src/redux/rockets/rocketsSlice.js: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; 2 | import axios from 'axios'; 3 | 4 | const initialState = { 5 | rockets: [], 6 | }; 7 | 8 | export const fetchRockets = createAsyncThunk('rockets/fetchRockets', async () => { 9 | const response = await axios.get('https://api.spacexdata.com/v4/rockets'); 10 | return response.data; 11 | }); 12 | 13 | const rocketsSlice = createSlice({ 14 | name: 'rockets', 15 | initialState, 16 | reducers: { 17 | reserveRocket: (state, action) => { 18 | const id = action.payload; 19 | state.rockets = state.rockets.map((rocket) => { 20 | if (rocket.id !== id) return rocket; 21 | return { ...rocket, reserved: true }; 22 | }); 23 | }, 24 | cancelRocket: (state, action) => { 25 | const id = action.payload; 26 | state.rockets = state.rockets.map((rocket) => { 27 | if (rocket.id !== id) return rocket; 28 | return { ...rocket, reserved: false }; 29 | }); 30 | }, 31 | }, 32 | extraReducers: (builders) => { 33 | builders.addCase(fetchRockets.fulfilled, (state, action) => { 34 | state.rockets = action.payload; 35 | }); 36 | }, 37 | }); 38 | 39 | export const { reserveRocket, cancelRocket } = rocketsSlice.actions; 40 | export default rocketsSlice.reducer; 41 | -------------------------------------------------------------------------------- /src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore, combineReducers } from '@reduxjs/toolkit'; 2 | import missionReducer from './missions/missionsSlice'; 3 | import rocketsReducer from './rockets/rocketsSlice'; 4 | 5 | const rootReducer = combineReducers({ 6 | mission: missionReducer, 7 | rockets: rocketsReducer, 8 | }); 9 | 10 | const store = configureStore({ 11 | reducer: rootReducer, 12 | }); 13 | 14 | export default store; 15 | --------------------------------------------------------------------------------