├── .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 | You need to enable JavaScript to run this app.
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 |
12 |
39 |
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 |
59 | Active Member
60 |
61 |
62 |
65 |
70 | Leave Mission
71 |
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 |
95 | Active Member
96 |
97 |
98 |
101 |
106 | Leave Mission
107 |
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 |
15 |
16 |
19 |
20 | Falcon 9
21 |
22 |
23 | A powerful rocket developed by SpaceX.
24 |
25 |
29 | Reserve Rocket
30 |
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 |
14 |
15 |
18 |
19 | Falcon 9
20 |
21 |
22 | A powerful rocket developed by SpaceX.
23 |
24 |
28 | Reserve Rocket
29 |
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 |
9 |
10 |
11 | (isActive ? 'active link' : 'pending link')}
14 | >
15 | Rockets
16 |
17 |
18 |
19 |
20 | Missions
21 |
22 |
23 |
24 |
25 | My profile
26 |
27 |
28 |
29 |
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 |
38 | {mission.reserved ? 'Active Member' : 'NOT A MEMBER'}
39 |
40 |
41 |
42 | handleMissionAction(mission.mission_id)}>
43 | {mission.reserved ? 'Leave Mission' : 'Join Mission'}
44 |
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 |
25 |
26 |
27 |
{name}
28 |
29 | {reserved ? Reserved : null}
30 | {description}
31 |
32 |
37 | {reserved ? 'Cancel Reservation' : 'Reserve Rocket'}
38 |
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 |
--------------------------------------------------------------------------------