├── public ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo512.png ├── manifest.json └── index.html ├── src ├── images │ └── planet.png ├── http-common.js ├── __tests__ │ └── component-testing │ │ ├── __snapshots__ │ │ ├── Rockets.test.js.snap │ │ ├── Missions.test.js.snap │ │ ├── profile.test.js.snap │ │ └── Nav.test.js.snap │ │ ├── Rockets.test.js │ │ ├── profile.test.js │ │ ├── Missions.test.js │ │ └── Nav.test.js ├── setupTests.js ├── redux │ ├── configureStore.js │ ├── missions │ │ └── missions.js │ └── rockets │ │ └── rockets.js ├── reportWebVitals.js ├── index.css ├── App.css ├── index.js ├── App.js ├── styles │ ├── Nav.css │ ├── Profile.css │ ├── Rocket.css │ └── Missions.css ├── pages │ ├── Nav.js │ ├── Rockets.js │ ├── Missions.js │ └── Profile.js └── logo.svg ├── .babelrc ├── .gitignore ├── .stylelintrc.json ├── .eslintrc.json ├── package.json ├── .github └── workflows │ └── linters.yml └── README.md /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Oklukeok/space-travellers-hub/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Oklukeok/space-travellers-hub/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Oklukeok/space-travellers-hub/HEAD/public/logo512.png -------------------------------------------------------------------------------- /src/images/planet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Oklukeok/space-travellers-hub/HEAD/src/images/planet.png -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-react" 4 | ], 5 | "plugins": ["@babel/plugin-syntax-jsx"] 6 | } -------------------------------------------------------------------------------- /src/http-common.js: -------------------------------------------------------------------------------- 1 | export const urlRockets = 'https://api.spacexdata.com/v3/rockets'; 2 | export const urlMissions = 'https://api.spacexdata.com/v3/missions'; 3 | -------------------------------------------------------------------------------- /src/__tests__/component-testing/__snapshots__/Rockets.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Testing if Rockets component renders correctly 1`] = ` 4 |
7 | `; 8 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/redux/configureStore.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import rocketsReducer from './rockets/rockets'; 3 | import missionsReducer from './missions/missions'; 4 | 5 | const store = configureStore({ 6 | reducer: { 7 | rockets: rocketsReducer, 8 | missions: missionsReducer, 9 | }, 10 | }); 11 | 12 | export default store; 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | 25 | # Local Netlify folder 26 | .netlify 27 | -------------------------------------------------------------------------------- /src/__tests__/component-testing/Rockets.test.js: -------------------------------------------------------------------------------- 1 | import renderer from 'react-test-renderer'; 2 | import { Provider } from 'react-redux'; 3 | import Rockets from '../../pages/Rockets'; 4 | import store from '../../redux/configureStore'; 5 | 6 | test('Testing if Rockets component renders correctly', () => { 7 | const rocketsSnapshot = renderer.create( 8 | 9 | 10 | , 11 | ).toJSON(); 12 | expect(rocketsSnapshot).toMatchSnapshot(); 13 | }); 14 | -------------------------------------------------------------------------------- /src/__tests__/component-testing/profile.test.js: -------------------------------------------------------------------------------- 1 | import { Provider } from 'react-redux'; 2 | import renderer from 'react-test-renderer'; 3 | import Profile from '../../pages/Profile'; 4 | import store from '../../redux/configureStore'; 5 | 6 | test('Testing if Profile component renders correctly', () => { 7 | const profileSnapshot = renderer.create( 8 | 9 | 10 | , 11 | ).toJSON(); 12 | expect(profileSnapshot).toMatchSnapshot(); 13 | }); 14 | -------------------------------------------------------------------------------- /src/__tests__/component-testing/Missions.test.js: -------------------------------------------------------------------------------- 1 | import renderer from 'react-test-renderer'; 2 | import { Provider } from 'react-redux'; 3 | import store from '../../redux/configureStore'; 4 | import Missions from '../../pages/Missions'; 5 | 6 | test('Testing if Missions component renders correctly', () => { 7 | const missionsSnapshot = renderer.create( 8 | 9 | 10 | , 11 | ).toJSON(); 12 | expect(missionsSnapshot).toMatchSnapshot(); 13 | }); 14 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: 4 | -apple-system, 5 | BlinkMacSystemFont, 6 | 'Segoe UI', 7 | 'Roboto', 8 | 'Oxygen', 9 | 'Ubuntu', 10 | 'Cantarell', 11 | 'Fira Sans', 12 | 'Droid Sans', 13 | 'Helvetica Neue', 14 | sans-serif; 15 | -webkit-font-smoothing: antialiased; 16 | -moz-osx-font-smoothing: grayscale; 17 | } 18 | 19 | code { 20 | font-family: 21 | source-code-pro, 22 | Menlo, 23 | Monaco, 24 | Consolas, 25 | 'Courier New', 26 | monospace; 27 | } 28 | -------------------------------------------------------------------------------- /src/__tests__/component-testing/Nav.test.js: -------------------------------------------------------------------------------- 1 | import { Provider } from 'react-redux'; 2 | import { BrowserRouter } from 'react-router-dom'; 3 | import renderer from 'react-test-renderer'; 4 | import NavBar from '../../pages/Nav'; 5 | import store from '../../redux/configureStore'; 6 | 7 | test('Testing if NavBar component renders correctly', () => { 8 | const navBarSnapshot = renderer.create( 9 | 10 | 11 | 12 | 13 | , 14 | ).toJSON(); 15 | expect(navBarSnapshot).toMatchSnapshot(); 16 | }); 17 | -------------------------------------------------------------------------------- /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 | } 21 | -------------------------------------------------------------------------------- /.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 | "ignorePatterns": [ 24 | "dist/", 25 | "build/" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /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/__tests__/component-testing/__snapshots__/Missions.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Testing if Missions component renders correctly 1`] = ` 4 |
7 |
  • 10 |
    13 |

    14 | Mission 15 |

    16 |
    17 |
    20 |

    21 | Description 22 |

    23 |
    24 |
    27 |

    28 | Status 29 |

    30 |
    31 |
    34 |
  • 35 |
    36 | `; 37 | -------------------------------------------------------------------------------- /src/__tests__/component-testing/__snapshots__/profile.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Testing if Profile component renders correctly 1`] = ` 4 |
    7 |
    10 |

    13 | My Missions 14 |

    15 |
    18 |
    19 |
    22 |

    25 | My Rockets 26 |

    27 |
    30 |
    31 |
    32 | `; 33 | -------------------------------------------------------------------------------- /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 reportWebVitals from './reportWebVitals'; 8 | import store from './redux/configureStore'; 9 | 10 | const root = ReactDOM.createRoot(document.getElementById('root')); 11 | root.render( 12 | 13 | 14 | 15 | 16 | 17 | 18 | , 19 | ); 20 | 21 | // If you want to start measuring performance in your app, pass a function 22 | // to log results (for example: reportWebVitals(console.log)) 23 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 24 | reportWebVitals(); 25 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Routes, Route } from 'react-router-dom'; 3 | import { useDispatch } from 'react-redux'; 4 | import NavBar from './pages/Nav'; 5 | import Rockets from './pages/Rockets'; 6 | import Missions from './pages/Missions'; 7 | import Profile from './pages/Profile'; 8 | 9 | import { fetchMissions } from './redux/missions/missions'; 10 | import { fetchRockets } from './redux/rockets/rockets'; 11 | 12 | function App() { 13 | const dispatch = useDispatch(); 14 | dispatch(fetchMissions()); 15 | dispatch(fetchRockets); 16 | 17 | return ( 18 |
    19 | 20 | 21 | 22 | 23 | } /> 24 | } /> 25 | } /> 26 | 27 |
    28 | ); 29 | } 30 | 31 | export default App; 32 | -------------------------------------------------------------------------------- /src/styles/Nav.css: -------------------------------------------------------------------------------- 1 | header { 2 | display: flex; 3 | align-items: center; 4 | justify-content: space-between; 5 | padding: 0 120px 0 200px; 6 | } 7 | 8 | .logo-and-title { 9 | display: flex; 10 | align-items: center; 11 | } 12 | 13 | .logo { 14 | width: 20%; 15 | } 16 | 17 | .navbar { 18 | display: flex; 19 | align-items: center; 20 | list-style: none; 21 | text-decoration: none; 22 | } 23 | 24 | .link-item { 25 | padding-left: 10px; 26 | width: 76px; 27 | text-decoration: none; 28 | } 29 | 30 | .item { 31 | text-decoration: none; 32 | color: rgb(26, 125, 182); 33 | } 34 | 35 | .active-link { 36 | text-decoration: underline; 37 | color: rgb(26, 125, 182); 38 | } 39 | 40 | .vertical-bar-links { 41 | width: 2px; 42 | height: 12px; 43 | background-color: #777; 44 | } 45 | 46 | .horizontal-bar { 47 | width: 975px; 48 | height: 1px; 49 | background-color: #999; 50 | margin: 20px 120px 0 200px; 51 | } 52 | -------------------------------------------------------------------------------- /src/styles/Profile.css: -------------------------------------------------------------------------------- 1 | .profile { 2 | display: flex; 3 | align-items: flex-start; 4 | padding: 0 120px 0 200px; 5 | } 6 | 7 | .reserved-rockets-wrapper, 8 | .reserved-missions-wrapper { 9 | display: flex; 10 | flex-direction: column; 11 | align-items: flex-start; 12 | justify-content: center; 13 | width: 40vw; 14 | margin-left: 10px; 15 | } 16 | 17 | .reserved-profile { 18 | list-style: none; 19 | border-top: 1px solid #999; 20 | border-right: 1px solid #999; 21 | border-left: 1px solid #999; 22 | width: 100%; 23 | } 24 | 25 | .reserved-item { 26 | border-bottom: 1px solid #999; 27 | padding: 7px 0 20px 10px; 28 | } 29 | 30 | .reserved-rocket-item { 31 | width: 39vw; 32 | } 33 | 34 | .reserved-profile-rocket { 35 | width: 100%; 36 | } 37 | 38 | .flag-text { 39 | padding-top: 20px; 40 | color: blue; 41 | } 42 | 43 | .no-joined-reserved { 44 | padding-top: 10px; 45 | font-size: 24px; 46 | color: #459a; 47 | text-align: center; 48 | } 49 | -------------------------------------------------------------------------------- /src/styles/Rocket.css: -------------------------------------------------------------------------------- 1 | .rockets-wrapper { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 10px; 5 | padding: 0 120px 0 200px; 6 | } 7 | 8 | .rocket-card { 9 | display: flex; 10 | align-items: flex-start; 11 | gap: 10px; 12 | } 13 | 14 | .rocket-img { 15 | width: 150px; 16 | height: 110px; 17 | display: flex; 18 | align-items: center; 19 | padding-top: 14px; 20 | } 21 | 22 | .rocket-body { 23 | display: flex; 24 | flex-direction: column; 25 | align-items: flex-start; 26 | } 27 | 28 | .rocket-title, 29 | .rocket-description, 30 | .reserve-rocket-btn { 31 | text-align: left; 32 | font-size: 12px; 33 | } 34 | 35 | .rocket-title { 36 | font-weight: 400; 37 | } 38 | 39 | .reserve-rocket-btn { 40 | background-color: rgb(4, 96, 216); 41 | color: #fff; 42 | padding: 7px; 43 | border-radius: 7px; 44 | border: none; 45 | } 46 | 47 | .reserved-text { 48 | color: #fff; 49 | background-color: rgb(24, 157, 161); 50 | margin-right: 10px; 51 | border-radius: 5px; 52 | padding: 0.5px 3px; 53 | } 54 | -------------------------------------------------------------------------------- /src/__tests__/component-testing/__snapshots__/Nav.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Testing if NavBar component renders correctly 1`] = ` 4 | Array [ 5 |
    8 |
    11 | Logo 16 |

    19 | Space Traveler\`s Hub 20 |

    21 |
    22 | 60 |
    , 61 |
    , 64 | ] 65 | `; 66 | -------------------------------------------------------------------------------- /src/pages/Nav.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NavLink } from 'react-router-dom'; 3 | import planet from '../images/planet.png'; 4 | import '../styles/Nav.css'; 5 | 6 | const NavBar = () => ( 7 | <> 8 |
    9 | 10 |
    11 | Logo 12 |

    Space Traveler`s Hub

    13 |
    14 | 15 |
      16 |
    • 17 | ( 19 | isActive ? 'active-link' : 'item')} 20 | to="/" 21 | > 22 | Rockets 23 | 24 |
    • 25 |
    • 26 | ( 29 | isActive ? 'active-link' : 'item')} 30 | > 31 | Missions 32 | 33 |
    • 34 |
    • 35 |
    • 36 | ( 39 | isActive ? 'active-link' : 'item')} 40 | > 41 | My Profile 42 | 43 |
    • 44 |
    45 |
    46 |
    47 | 48 | 49 | ); 50 | export default NavBar; 51 | -------------------------------------------------------------------------------- /src/styles/Missions.css: -------------------------------------------------------------------------------- 1 | .missions-wrapper { 2 | margin: 20px 120px 0 200px; 3 | } 4 | 5 | .mission-card { 6 | list-style-type: none; 7 | display: flex; 8 | flex-direction: row; 9 | justify-content: center; 10 | margin: auto; 11 | } 12 | 13 | .mission-card:nth-child(1n) { 14 | background-color: white; 15 | } 16 | 17 | .mission-card:nth-child(2n) { 18 | background-color: lightgray; 19 | } 20 | 21 | .missionName { 22 | width: 10vw; 23 | display: flex; 24 | justify-content: flex-start; 25 | align-items: flex-start; 26 | border: solid 1px #444; 27 | } 28 | 29 | .missionName h3 { 30 | text-align: left; 31 | margin-left: 5px; 32 | } 33 | 34 | .missionDescription { 35 | width: 60vw; 36 | border: solid 1px #444; 37 | } 38 | 39 | .missionDescription p { 40 | margin: 5px; 41 | } 42 | 43 | .missionMember { 44 | width: 10vw; 45 | display: flex; 46 | justify-content: center; 47 | align-items: center; 48 | border: solid 1px #444; 49 | } 50 | 51 | .missionMemberNot { 52 | background-color: #666; 53 | color: white; 54 | border: none; 55 | border-radius: 5px; 56 | } 57 | 58 | .missionMemberActive { 59 | background-color: rgb(3, 201, 164); 60 | color: white; 61 | border: none; 62 | border-radius: 5px; 63 | } 64 | 65 | .missionButton { 66 | width: 10vw; 67 | display: flex; 68 | justify-content: center; 69 | align-items: center; 70 | border: solid 1px #444; 71 | } 72 | 73 | .missionButton button { 74 | background-color: rgba(255, 255, 255, 0); 75 | border-radius: 5px; 76 | height: 40px; 77 | } 78 | 79 | .leavebutton { 80 | color: red; 81 | border: red solid; 82 | } 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "space-travellers-hub", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reduxjs/toolkit": "^1.9.0", 7 | "@testing-library/jest-dom": "^5.16.5", 8 | "@testing-library/react": "^13.4.0", 9 | "@testing-library/user-event": "^13.5.0", 10 | "react": "^18.2.0", 11 | "react-dom": "^18.2.0", 12 | "react-redux": "^8.0.5", 13 | "react-router-dom": "^6.4.3", 14 | "react-scripts": "5.0.1", 15 | "react-test-renderer": "^18.2.0", 16 | "redux-logger": "^3.0.6", 17 | "redux-thunk": "^2.4.2", 18 | "web-vitals": "^2.1.4" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | }, 44 | "devDependencies": { 45 | "@babel/core": "^7.20.2", 46 | "@babel/eslint-parser": "^7.19.1", 47 | "@babel/plugin-syntax-jsx": "^7.18.6", 48 | "@babel/preset-react": "^7.18.6", 49 | "eslint": "^7.32.0", 50 | "eslint-config-airbnb": "^18.2.1", 51 | "eslint-plugin-import": "^2.26.0", 52 | "eslint-plugin-jsx-a11y": "^6.6.1", 53 | "eslint-plugin-react": "^7.31.11", 54 | "eslint-plugin-react-hooks": "^4.6.0", 55 | "stylelint": "^13.13.1", 56 | "stylelint-config-standard": "^21.0.0", 57 | "stylelint-csstree-validator": "^1.9.0", 58 | "stylelint-scss": "^3.21.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/redux/missions/missions.js: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; 2 | 3 | export const fetchMissions = createAsyncThunk( 4 | 'missions/fetchMissions', 5 | async () => { 6 | const response = await fetch('https://api.spacexdata.com/v3/missions'); 7 | const missions = await response.json(); 8 | return missions; 9 | }, 10 | ); 11 | 12 | const missionsSlice = createSlice({ 13 | name: 'missions', 14 | initialState: { 15 | missions: [], 16 | status: 'idle', 17 | }, 18 | reducers: { 19 | reserveMission: (state, action) => ({ 20 | ...state, 21 | missions: state.missions.map((thisMission) => { 22 | if (thisMission.mission_id === action.payload) { 23 | return { 24 | ...thisMission, 25 | reserved: true, 26 | }; 27 | } 28 | return thisMission; 29 | }), 30 | }), 31 | cancelMission: (state, action) => ({ 32 | ...state, 33 | missions: state.missions.map((thisMission) => { 34 | if (thisMission.mission_id === action.payload) { 35 | return { 36 | ...thisMission, 37 | reserved: false, 38 | }; 39 | } 40 | 41 | return thisMission; 42 | }), 43 | }), 44 | }, 45 | extraReducers: (builder) => { 46 | builder.addCase(fetchMissions.fulfilled, (state, action) => { 47 | const value = state; 48 | value.missions = action.payload.map((mission) => ({ 49 | mission_id: mission.mission_id, 50 | mission_name: mission.mission_name, 51 | description: mission.description, 52 | reserved: false, 53 | })); 54 | }); 55 | 56 | builder.addCase(fetchMissions.rejected, (state) => { 57 | const failedState = state; failedState.status = 'failed'; 58 | }); 59 | builder.addCase(fetchMissions.pending, (_, action) => action.payload); 60 | }, 61 | 62 | }); 63 | 64 | export default missionsSlice.reducer; 65 | export const { reserveMission, cancelMission } = missionsSlice.actions; 66 | -------------------------------------------------------------------------------- /src/redux/rockets/rockets.js: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; 2 | 3 | export const fetchRockets = createAsyncThunk( 4 | 'rockets/fetchRockets', 5 | async () => { 6 | const response = await fetch('https://api.spacexdata.com/v3/rockets'); 7 | const responseJSON = await response.json(); 8 | return responseJSON; 9 | }, 10 | ); 11 | 12 | const rocketsSlice = createSlice({ 13 | name: 'rockets', 14 | initialState: { 15 | rockets: [], 16 | status: 'Not Requested Yet', 17 | }, 18 | reducers: { 19 | reserveRocket: (state, action) => ({ 20 | ...state, 21 | rockets: state.rockets.map((rocket) => { 22 | if (rocket.rocket_id === action.payload) { 23 | return { 24 | ...rocket, 25 | reserved: true, 26 | }; 27 | } 28 | return rocket; 29 | }), 30 | }), 31 | cancelRocket: (state, action) => ({ 32 | ...state, 33 | rockets: state.rockets.map((rocket) => { 34 | if (rocket.rocket_id === action.payload) { 35 | return { 36 | ...rocket, 37 | reserved: false, 38 | }; 39 | } 40 | return rocket; 41 | }), 42 | }), 43 | }, 44 | extraReducers: (builder) => { 45 | builder.addCase(fetchRockets.fulfilled, (state, action) => { 46 | const rocketsState = state; 47 | rocketsState.rockets = action.payload.map((rocket) => ({ 48 | rocket_id: rocket.rocket_id, 49 | rocket_name: rocket.rocket_name, 50 | flickr_images: rocket.flickr_images, 51 | description: rocket.description, 52 | reserved: false, 53 | })); 54 | }); 55 | 56 | builder.addCase(fetchRockets.rejected, (state) => { 57 | const failedState = state; 58 | failedState.status = 'failed'; 59 | }); 60 | builder.addCase(fetchRockets.pending, (state) => { 61 | const pendingState = state; 62 | pendingState.status = 'Pending'; 63 | }); 64 | }, 65 | 66 | }); 67 | 68 | export default rocketsSlice.reducer; 69 | export const { reserveRocket, cancelRocket } = rocketsSlice.actions; 70 | -------------------------------------------------------------------------------- /.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@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-22.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-22.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/pages/Rockets.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | import { fetchRockets, reserveRocket, cancelRocket } from '../redux/rockets/rockets'; 4 | import '../styles/Rocket.css'; 5 | 6 | function Rockets() { 7 | const rocketsArray = useSelector((state) => state.rockets.rockets); 8 | const dispatch = useDispatch(); 9 | 10 | useEffect(() => { 11 | if (rocketsArray.length === 0) { 12 | dispatch(fetchRockets()); 13 | } 14 | }); 15 | 16 | return ( 17 | 18 |
    19 | { rocketsArray.map((rocket) => ( 20 | 21 |
  • 22 | {rocket.rocket_name} 27 |
    28 |

    29 | {rocket.rocket_name} 30 |

    31 |

    32 | {rocket.reserved ? ( 33 | 34 | Reserved 35 | 36 | ) 37 | : ''} 38 | {rocket.description} 39 |

    40 | 41 | {rocket.reserved 42 | ? ( 43 | 50 | ) 51 | : ( 52 | 59 | )} 60 | 61 |
    62 | 63 |
  • 64 | ))} 65 |
    66 | ); 67 | } 68 | 69 | export default Rockets; 70 | -------------------------------------------------------------------------------- /src/pages/Missions.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | import { reserveMission, fetchMissions, cancelMission } from '../redux/missions/missions'; 4 | import '../styles/Missions.css'; 5 | 6 | const Missions = () => { 7 | const missionsArray = useSelector((state) => state.missions.missions); 8 | const dispatch = useDispatch(); 9 | 10 | useEffect(() => { 11 | if (!missionsArray.length) { 12 | dispatch(fetchMissions()); 13 | } 14 | }); 15 | 16 | return ( 17 | 18 |
    19 | 20 |
  • 21 |

    Mission

    22 |

    Description

    23 |

    Status

    24 |
    25 |
  • 26 | 27 | { missionsArray.map((mission) => ( 28 | 29 |
  • 30 |

    {mission.mission_name}

    31 |

    {mission.description}

    32 |
    33 | {mission.reserved ? () 34 | : ()} 35 |
    36 |
    37 | {mission.reserved ? ( 38 | 45 | ) 46 | : ( 47 | 53 | )} 54 |
    55 |
  • 56 | ))} 57 | 58 |
    59 | ); 60 | }; 61 | 62 | export default Missions; 63 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/Profile.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | import { fetchRockets } from '../redux/rockets/rockets'; 4 | import { fetchMissions } from '../redux/missions/missions'; 5 | 6 | import '../styles/Profile.css'; 7 | 8 | const Profile = () => { 9 | const rockets = useSelector((state) => state.rockets.rockets); 10 | const missions = useSelector((state) => state.missions.missions); 11 | const dispatch = useDispatch(); 12 | useEffect(() => { 13 | if (rockets.length === 0) { 14 | dispatch(fetchRockets()); 15 | } 16 | if (missions.length === 0) { 17 | dispatch(fetchMissions()); 18 | } 19 | }); 20 | 21 | const joinedMissions = missions.filter((mission) => mission.reserved); 22 | let flagMission = false; 23 | if (joinedMissions.length !== 0) { 24 | flagMission = true; 25 | } 26 | 27 | const reservedRockets = rockets.filter((rocket) => rocket.reserved); 28 | let flagRocket = false; 29 | if (reservedRockets.length !== 0) { 30 | flagRocket = true; 31 | } 32 | 33 | return ( 34 |
    35 | 36 |
    37 |

    38 | My Missions 39 |

    40 | 41 |
    42 | { 43 | missions.map((mission) => ( 44 | mission.reserved ? ( 45 |
    46 | {mission.mission_name} 47 |
    48 | ) : '' 49 | )) 50 | } 51 | 52 |
    53 | 54 |

    55 | {flagMission ? '' : 'No Missions Selected Yet!'} 56 |

    57 | 58 |
    59 | 60 |
    61 |

    62 | My Rockets 63 |

    64 |
    65 | { 66 | rockets.map((rocket) => ( 67 | rocket.reserved ? ( 68 |
    69 | {rocket.rocket_name} 70 | 71 |
    72 | ) : '' 73 | )) 74 | } 75 | 76 |
    77 |

    78 | { flagRocket ? '' : 'No Rockets Selected Yet!' } 79 |

    80 |
    81 | 82 |
    83 | ); 84 | }; 85 | 86 | export default Profile; 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    4 | 5 | logo 6 |
    7 | 8 |

    Space travellers hub

    9 | 10 |
    11 | 12 | # 📗 Table of Contents 13 | 14 | - [📖 About the Project](#about-project) 15 | - [🛠 Built With](#built-with) 16 | - [Tech Stack](#tech-stack) 17 | - [Key Features](#key-features) 18 | - [🚀 Live Demo](#live-demo) 19 | - [💻 Getting Started](#getting-started) 20 | - [Setup](#setup) 21 | - [Prerequisites](#prerequisites) 22 | - [Install](#install) 23 | - [Usage](#usage) 24 | - [Run tests](#run-tests) 25 | - [Deployment](#triangular_flag_on_post-deployment) 26 | - [👥 Authors](#authors) 27 | - [🔭 Future Features](#future-features) 28 | - [🤝 Contributing](#contributing) 29 | - [⭐️ Show your support](#support) 30 | - [🙏 Acknowledgements](#acknowledgements) 31 | - [❓ FAQ](#faq) 32 | - [📝 License](#license) 33 | 34 | # 📖 Space Travellers Hub 35 | 36 | **This is a web application developed using React library and Redux state management framework that allows users to display real data of the space traveller's data. Users can reserve rockets, cancel rocket reservation, join space traveller's mission and leave the mission. The web application also allows users to see their profile, the rockets they reserved and the missions they joined.** 37 | 38 | ## 🛠 Built With 39 | 40 | ### Tech Stack 41 | 42 | 43 |
    44 | Client 45 | 51 |
    52 | 53 |
    54 | Server 55 | 58 |
    59 | 60 | 61 | ### Key Features 62 | 63 | 64 | - **Fetch real Rocket and missions data from the Space Travellers Hub hosting API using `fetch` JS API and `createAsyncThunk` redux method** 65 | - **Reserve rocket and cancel rocket reservation** 66 | - **Join and leave space traveller's mission** 67 | - **Allow customers to show the reserved rockets and joined missions** 68 | 69 |

    (back to top)

    70 | 71 | ## 🚀 Live Demo 72 | 73 | 74 | - [Live Demo Link](https://stunning-crumble-1bf3cb.netlify.app/Profile) 75 | 76 |

    (back to top)

    77 | 78 | 79 | ## 💻 Getting Started 80 | 81 | To get a local copy up and running, follow these steps. 82 | 83 | ### Prerequisites 84 | 85 | In order to run this project you need: 86 | - `sudo apt-get install node.js npm` 87 | - `sudo apt-get install git` 88 | - `npx create-react-app .` inside the root directory of your repository. 89 | - `npx install @redux-toolkit` 90 | 91 | 92 | ### Setup 93 | 94 | Clone this repository to your desired folder: 95 | 96 | Use `git clone git@github.com:Oklukeok/space-travellers-hub.git` command to clone our repo in your local folder you want. 97 | 98 | ### Install 99 | 100 | Install this project with: 101 | 102 | `npm run build` 103 | 104 | ### Usage 105 | 106 | To run the project, execute the following command: 107 | 108 | `npm start` 109 | 110 | ### Run tests 111 | 112 | To run tests, run the following command: 113 | 114 | `npm run test` 115 | 116 | ### Deployment 117 | 118 | - You can deploy this project using: 119 | First install `netlify` using the command `npm install netlify-cli -g` 120 | 121 | - Run the command `netlify deploy` in your root directory. 122 | 123 |

    (back to top)

    124 | 125 | 126 | ## 👥 Authors 127 | 128 | 👤 **Lucas Bermudez** 129 | 130 | - GitHub: [@Oklukeok](https://github.com/Oklukeok) 131 | - LinkedIn: [LinkedIn](https://linkedin.com/in/lucas-bermudez/) 132 | 133 | 👤 **Amare Kassa** 134 | 135 | - GitHub: [@githubhandle](https://github.com/amare1990) 136 | - Twitter: [@twitterhandle](https://twitter.com/@amaremek) 137 | - LinkedIn: [LinkedIn](https://linkedin.com/in/amare-kassa-90) 138 | 139 |

    (back to top)

    140 | 141 | 142 | ## 🔭 Future Features 143 | 144 | 145 | - [ ] **Saving the rserved states of the rocket in the database** 146 | - [ ] **Saving the joined states of missions in the database** 147 | 148 |

    (back to top)

    149 | 150 | 151 | 152 | ## 🤝 Contributing 153 | 154 | Contributions, issues, and feature requests are welcome! 155 | 156 | Feel free to check the [issues page](https://github.com/amare1990/space-travellers-hub/issues). 157 | 158 |

    (back to top)

    159 | 160 | 161 | ## ⭐️ Show your support 162 | 163 | 164 | If you like this project, feel free to modify it add new features. 165 | 166 |

    (back to top)

    167 | 168 | 169 | ## 🙏 Acknowledgments 170 | 171 | 172 | I would like to thank my coding partner for his flexible approach to work. 173 | 174 |

    (back to top)

    175 | 176 | 177 | ## ❓ FAQ 178 | 179 | 180 | - **Which way of fetching data is recommended?** 181 | 182 | - I recommend to use `createAsyncThunk` redux method to handle non-unit functions. 183 | 184 | - **Is there a way of posting reserved states of rocket and joined states of missions** 185 | 186 | - No. You need to have permission from the space traveller's hub to post data to the hosting API. 187 | 188 |

    (back to top)

    189 | 190 | 191 | ## 📝 License 192 | 193 | This project is [MIT](https://github.com/amare1990/space-travellers-hub/.LICENSE) licensed. 194 | 195 | 196 | 197 |

    (back to top)

    198 | --------------------------------------------------------------------------------