├── .circleci └── config.yml ├── .github ├── renovate.json5 └── workflows │ └── update-dependencies.yml ├── .gitignore ├── CODEOWNERS ├── README.md ├── client ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── public │ ├── _redirects │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ ├── robots.txt │ └── space_kitty_pattern.png ├── src │ ├── assets │ │ ├── cat_logo.png │ │ ├── cat_logo@2x.png │ │ ├── space_cat_logo.png │ │ ├── space_cat_logo@2x.png │ │ └── space_kitty_pattern.svg │ ├── components │ │ ├── __tests__ │ │ │ ├── module-detail.js │ │ │ ├── modules-navigation.js │ │ │ ├── query-result.js │ │ │ └── track-detail.js │ │ ├── content-section.js │ │ ├── footer.js │ │ ├── header.js │ │ ├── index.js │ │ ├── layout.js │ │ ├── md-content.js │ │ ├── module-detail.js │ │ ├── modules-navigation.js │ │ ├── query-result.js │ │ └── track-detail.js │ ├── containers │ │ ├── __tests__ │ │ │ └── track-card.js │ │ └── track-card.js │ ├── index.js │ ├── pages │ │ ├── __tests__ │ │ │ └── tracks.js │ │ ├── index.js │ │ └── tracks.js │ ├── styles.js │ └── utils │ │ ├── helpers.js │ │ ├── test-utils.js │ │ └── useWindowDimensions.js └── vite.config.js ├── final ├── client │ ├── README.md │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── _redirects │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ ├── robots.txt │ │ └── space_kitty_pattern.png │ ├── src │ │ ├── assets │ │ │ ├── cat_logo.png │ │ │ ├── cat_logo@2x.png │ │ │ ├── space_cat_logo.png │ │ │ ├── space_cat_logo@2x.png │ │ │ └── space_kitty_pattern.svg │ │ ├── components │ │ │ ├── __tests__ │ │ │ │ ├── module-detail.js │ │ │ │ ├── modules-navigation.js │ │ │ │ ├── query-result.js │ │ │ │ └── track-detail.js │ │ │ ├── content-section.js │ │ │ ├── footer.js │ │ │ ├── header.js │ │ │ ├── index.js │ │ │ ├── layout.js │ │ │ ├── md-content.js │ │ │ ├── module-detail.js │ │ │ ├── modules-navigation.js │ │ │ ├── query-result.js │ │ │ └── track-detail.js │ │ ├── containers │ │ │ ├── __tests__ │ │ │ │ └── track-card.js │ │ │ └── track-card.js │ │ ├── index.js │ │ ├── pages │ │ │ ├── __tests__ │ │ │ │ └── tracks.js │ │ │ ├── index.js │ │ │ └── tracks.js │ │ ├── styles.js │ │ └── utils │ │ │ ├── helpers.js │ │ │ ├── test-utils.js │ │ │ └── useWindowDimensions.js │ └── vite.config.js └── server │ ├── README.md │ ├── package-lock.json │ ├── package.json │ └── src │ ├── datasources │ └── track-api.js │ ├── index.js │ ├── resolvers.js │ └── schema.js └── server ├── README.md ├── package-lock.json ├── package.json └── src ├── index.js └── schema.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | secops: apollo/circleci-secops-orb@2.0.6 5 | 6 | workflows: 7 | security-scans: 8 | jobs: 9 | - secops/gitleaks: 10 | context: 11 | - platform-docker-ro 12 | - github-orb 13 | - secops-oidc 14 | git-base-revision: <<#pipeline.git.base_revision>><><> 15 | git-revision: << pipeline.git.revision >> 16 | - secops/semgrep: 17 | context: 18 | - secops-oidc 19 | - github-orb 20 | git-base-revision: <<#pipeline.git.base_revision>><><> 21 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | // Boilerplate 3 | $schema: "https://docs.renovatebot.com/renovate-schema.json", 4 | 5 | // This will group all patch- and minor-level updates into a single PR 6 | // rather than generating 1 PR per dependency (which is the current default) 7 | // This just helps reduce noise on the repo. 8 | extends: ["group:allNonMajor"], 9 | 10 | // Disables dependency dashboard - it doesn't do anything on 11 | // repos that don't support issues 12 | dependencyDashboard: false, 13 | 14 | // Resetting to the default PR creation configuration 15 | // The default config overrides this. 16 | // Realized I also need to change this because you don't have issues 17 | // Normally, the dependency dashboard would tell you if PRs are holding 18 | // because they're waiting for tests to pass. 19 | // With this set to "immediate", you'll be able to see any stuck pulls 20 | prCreation: "immediate", 21 | 22 | // Prevent automerging any lockfile mainteance 23 | // there's an ongoing discussion around whether or not 24 | // we should even have lockfile maintenance on by default 25 | // here: https://apollograph.slack.com/archives/C02TG9NHM/p1689259568243699 26 | lockFileMaintenance: { 27 | automerge: false, 28 | }, 29 | 30 | packageRules: [ 31 | // Prevent automerging for PRs that aren't lockfile maintenance 32 | { 33 | matchPackagePatterns: ["*"], 34 | automerge: false, 35 | rangeStrategy: "replace" 36 | }, 37 | // Example of pinning a dependency to a specific version or range of versions 38 | // There's a ton of flexibility for doing this type of thing 39 | // if you're looking for other options, check out the various config options: 40 | // https://docs.renovatebot.com/configuration-options/#packagerules 41 | { 42 | matchPackageNames: ["react"], 43 | allowedVersions: "16.x", 44 | }, 45 | ], 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/update-dependencies.yml: -------------------------------------------------------------------------------- 1 | # What to call this GitHub Action workflow in the GitHub browser UI 2 | name: Update Dependencies 3 | 4 | # Set up environment variables for this runner 5 | env: 6 | # For using `gh`, the GitHub CLI (to create a PR): 7 | GH_TOKEN: ${{secrets.GITHUB_TOKEN}} 8 | # The branch to commit updated dependency versions to 9 | BRANCH_NAME: update-deps 10 | 11 | # When to run this GitHub Action 12 | on: 13 | # Run when changes pushed to main branch (Uncomment for debugging) 14 | # push: 15 | # branches: 16 | # - main 17 | schedule: 18 | # Run every day at midnight UTC 19 | # Note: During peak load times, GitHub may delay this time 20 | - cron: '0 0 * * *' 21 | 22 | jobs: 23 | update_deps: # job name 24 | # Only run this GitHub Action on the original repo, not on forks 25 | if: github.repository_owner == 'apollographql' 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v3 29 | with: 30 | # Fetch all branches when checking out the repo 31 | fetch-depth: 0 32 | - uses: actions/setup-node@v3 33 | with: 34 | node-version: 18 35 | - name: 'Check out branch' 36 | run: | 37 | git branch --remotes 38 | echo "*********" 39 | 40 | # Check if a branch already exists with the specified BRANCH_NAME. 41 | CHECK_BRANCH_OUTPUT=$(git branch --remotes | grep "origin/${BRANCH_NAME}" | wc -l ) 42 | # Strip the spaces from CHECK_BRANCHES_OUTPUT. 43 | # Will be set to 1 if a matching branch is found, or 0 otherwise. 44 | DOES_BRANCH_EXIST=${CHECK_BRANCH_OUTPUT// } 45 | 46 | # If it does, check it out 47 | if [ $DOES_BRANCH_EXIST = 1 ] 48 | then 49 | echo "Checking out existing branch: ${BRANCH_NAME}" 50 | git checkout $BRANCH_NAME 51 | # If it doesn't, check out a new branch with that name 52 | else 53 | echo "Checking out a new branch: ${BRANCH_NAME}" 54 | git checkout -b $BRANCH_NAME 55 | fi 56 | - name: 'Install dependencies' 57 | run: | 58 | cd client && npm install && cd .. 59 | cd server && npm install && cd .. 60 | cd final/client && npm install && cd ../.. 61 | cd final/server && npm install && cd ../.. 62 | - name: 'Update dependencies' 63 | run: | 64 | cd client && npm update && cd .. 65 | cd server && npm update && cd .. 66 | cd final/client && npm update && cd ../.. 67 | cd final/server && npm update && cd ../.. 68 | - name: 'Commit & push changes' 69 | run: | 70 | git status 71 | echo "*********" 72 | 73 | # Check if there are unstaged changes to commit 74 | CHECK_FOR_UNSTAGED_CHANGES=$(git status | grep "Changes not staged for commit:" | wc -l) 75 | # Strip the spaces from CHECK_FOR_UNSTAGED_CHANGES. 76 | # Will be set to 1 if there are changes to be commited, or 0 otherwise. 77 | UNSTAGED_CHANGES_EXIST=${CHECK_FOR_UNSTAGED_CHANGES// } 78 | 79 | if [ $UNSTAGED_CHANGES_EXIST = 1 ] 80 | then 81 | # Commit and push changes 82 | git config user.name github-actions 83 | git config user.email github-actions@github.com 84 | git add . 85 | git commit -m "chore: updated dependencies" 86 | git push --set-upstream origin $BRANCH_NAME 87 | echo "Pushed changes to remote branch $BRANCH_NAME" 88 | fi 89 | - name: 'Open pull request' 90 | run: | 91 | git branch --remotes 92 | echo "*********" 93 | 94 | # Check if a remote branch exists with the specified BRANCH_NAME. 95 | CHECK_BRANCH_OUTPUT=$(git branch --remotes | grep "origin/${BRANCH_NAME}" | wc -l ) 96 | # Strip the spaces from CHECK_BRANCHES_OUTPUT. 97 | # Will be set to 1 if a matching branch is found, or 0 otherwise. 98 | DOES_BRANCH_EXIST=${CHECK_BRANCH_OUTPUT// } 99 | 100 | # If there isn't a remote branch, we can't create a PR, so return early. 101 | if [ $DOES_BRANCH_EXIST != 1 ] 102 | then 103 | echo "No remote branch $BRANCH_NAME. Exiting without opening a pull request." 104 | return 105 | fi 106 | 107 | gh pr status --json number,state 108 | echo "*********" 109 | 110 | # Check if an open pull request already exists for the current branch. 111 | # Will be set to "OPEN" if PR is found for the current branch. 112 | # Otherwise, may be set to "CLOSED", "MERGED", or empty string (if no PRs exist for current branch yet). 113 | CURRENT_BRANCH_PR_STATE=$(gh pr status --json number,state --jq ".currentBranch?.state") 114 | 115 | # If there isn't a PR for this branch yet, create one. 116 | if [ "$CURRENT_BRANCH_PR_STATE" != "OPEN" ] 117 | then 118 | echo "Creating a new PR using branch: ${BRANCH_NAME}" 119 | gh pr create --fill 120 | fi 121 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .env -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the Apollo SecOps team 2 | # Please customize this file as needed prior to merging. 3 | 4 | * @apollographql/deved 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Odyssey Lift-off II: Resolvers 2 | 3 | Welcome to the companion app of Odyssey Lift-off II! You can [find the course lessons and instructions on Odyssey](https://odyssey.apollographql.com/lift-off-part2), Apollo's learning platform. 4 | 5 | You can [preview the completed demo app here](https://lift-off-client-demo.netlify.app/). 6 | 7 | ## How to use this repo 8 | 9 | The course will walk you step by step on how to implement the features you see in the demo app. This codebase is the starting point of your journey! 10 | 11 | There are 3 main folders: 12 | 13 | - `server`: The starting point of our GraphQL server. 14 | - `client`: The starting point of our React application. 15 | - `final`: The final stage of both the server and client folders, with all of the steps and code completed! 16 | 17 | To get started: 18 | 19 | 1. Navigate to the `server` folder. 20 | 1. Run `npm install`. 21 | 1. Run `npm start`. 22 | 23 | This will start the GraphQL API server. 24 | 25 | In another Terminal window, 26 | 27 | 1. Navigate to the `client` folder. 28 | 1. Run `npm install`. 29 | 1. Run `npm start`. 30 | 31 | This will open up `localhost:3000` in your web browser. 32 | 33 | ## Getting Help 34 | 35 | For any issues or problems concerning the course content, please refer to the [Odyssey topic in our community forums](https://community.apollographql.com/tags/c/help/6/odyssey). 36 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Catstronauts - client 2 | 3 | The starting point of the `client` code for Odyssey Lift-off II course. 4 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | Catstronauts 16 | 17 | 18 | 19 |
20 | 21 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "catstronauts-client", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "front-end demo app for Apollo's lift-off II course", 6 | "dependencies": { 7 | "@apollo/client": "^3.7.16", 8 | "@apollo/space-kit": "^9.3.1", 9 | "@emotion/cache": "^11.4.0", 10 | "@emotion/core": "^10.1.1", 11 | "@emotion/react": "^11.4.0", 12 | "@emotion/styled": "^11.3.0", 13 | "graphql": "^15.3.0", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0", 16 | "react-markdown": "^9.0.1", 17 | "react-player": "^2.14.1", 18 | "react-router-dom": "^6.22.0" 19 | }, 20 | "scripts": { 21 | "test": "vitest", 22 | "start": "vite", 23 | "build": "vite build" 24 | }, 25 | "browserslist": { 26 | "production": [ 27 | ">0.2%", 28 | "not dead", 29 | "not op_mini all" 30 | ], 31 | "development": [ 32 | "last 1 chrome version", 33 | "last 1 firefox version", 34 | "last 1 safari version" 35 | ] 36 | }, 37 | "devDependencies": { 38 | "@testing-library/jest-dom": "^4.2.4", 39 | "@testing-library/react": "^9.3.2", 40 | "@testing-library/user-event": "^7.1.2", 41 | "@vitejs/plugin-react": "^4.0.1", 42 | "@vitest/ui": "^0.34.0", 43 | "happy-dom": "^9.20.3", 44 | "jest": "^27.5.1", 45 | "vite": "^4.5.1", 46 | "vitest": "^0.34.0" 47 | }, 48 | "overrides": { 49 | "nth-check": "2.1.1" 50 | }, 51 | "main": "src/index.js", 52 | "author": "Raphael Terrier @R4ph-t", 53 | "license": "MIT" 54 | } 55 | -------------------------------------------------------------------------------- /client/public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/odyssey-lift-off-part2/c6f00b826d4973fdc7198a48cc275cb90f95d455/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/odyssey-lift-off-part2/c6f00b826d4973fdc7198a48cc275cb90f95d455/client/public/logo192.png -------------------------------------------------------------------------------- /client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/odyssey-lift-off-part2/c6f00b826d4973fdc7198a48cc275cb90f95d455/client/public/logo512.png -------------------------------------------------------------------------------- /client/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 | -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/public/space_kitty_pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/odyssey-lift-off-part2/c6f00b826d4973fdc7198a48cc275cb90f95d455/client/public/space_kitty_pattern.png -------------------------------------------------------------------------------- /client/src/assets/cat_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/odyssey-lift-off-part2/c6f00b826d4973fdc7198a48cc275cb90f95d455/client/src/assets/cat_logo.png -------------------------------------------------------------------------------- /client/src/assets/cat_logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/odyssey-lift-off-part2/c6f00b826d4973fdc7198a48cc275cb90f95d455/client/src/assets/cat_logo@2x.png -------------------------------------------------------------------------------- /client/src/assets/space_cat_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/odyssey-lift-off-part2/c6f00b826d4973fdc7198a48cc275cb90f95d455/client/src/assets/space_cat_logo.png -------------------------------------------------------------------------------- /client/src/assets/space_cat_logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/odyssey-lift-off-part2/c6f00b826d4973fdc7198a48cc275cb90f95d455/client/src/assets/space_cat_logo@2x.png -------------------------------------------------------------------------------- /client/src/assets/space_kitty_pattern.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/components/__tests__/module-detail.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderWithRouter, cleanup } from '../../utils/test-utils'; 3 | import ModuleDetail from '../module-detail'; 4 | 5 | const mockModule = { 6 | id: 'l_1', 7 | title: 'The Night Sky', 8 | content: 9 | '# Et tempus voces tigride remisso fer coimus\n\n## Montibus arbusta detrectas haud\n\n', 10 | thumbnail: null, 11 | videoUrl: 'https://youtu.be/dlKzlksOUtU', 12 | topic: 'Cat-stronomy', 13 | length: 164, 14 | }; 15 | 16 | const mockParentTrack = { 17 | id: 'c_0', 18 | title: 'Cat-stronomy, an introduction', 19 | description: '# Pulchra vehi vidit misera sola armenta secabatur\n\n', 20 | thumbnail: 21 | 'https://res.cloudinary.com/dety84pbu/image/upload/v1598465568/nebula_cat_djkt9r.jpg', 22 | trackLength: 2377, 23 | modulesCount: 10, 24 | numberOfViews: 51, 25 | author: { 26 | name: 'Henri, le Chat Noir', 27 | photo: 28 | 'https://images.unsplash.com/photo-1442291928580-fb5d0856a8f1?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjExNzA0OH0', 29 | }, 30 | modules: [ 31 | { 32 | id: 'l_0', 33 | title: 'Exploring Time and Space', 34 | length: 258, 35 | }, 36 | ], 37 | }; 38 | 39 | describe('Module Detail View', () => { 40 | // automatically unmount and cleanup DOM after the test is finished. 41 | afterEach(cleanup); 42 | 43 | it('renders without error', () => { 44 | renderWithRouter(); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /client/src/components/__tests__/modules-navigation.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderWithRouter, cleanup } from '../../utils/test-utils'; 3 | import ModuleNav from '../modules-navigation'; 4 | 5 | const mockModule = { 6 | id: 'l_1', 7 | title: 'The Night Sky', 8 | content: 9 | '# Et tempus voces tigride remisso fer coimus\n\n## Montibus arbusta detrectas haud\n\n', 10 | thumbnail: null, 11 | videoUrl: 'https://youtu.be/dlKzlksOUtU', 12 | topic: 'Cat-stronomy', 13 | length: 164, 14 | }; 15 | 16 | const mockParentTrack = { 17 | id: 'c_0', 18 | title: 'Cat-stronomy, an introduction', 19 | description: '# Pulchra vehi vidit misera sola armenta secabatur\n\n', 20 | thumbnail: 21 | 'https://res.cloudinary.com/dety84pbu/image/upload/v1598465568/nebula_cat_djkt9r.jpg', 22 | trackLength: 2377, 23 | modulesCount: 10, 24 | numberOfViews: 51, 25 | author: { 26 | name: 'Henri, le Chat Noir', 27 | photo: 28 | 'https://images.unsplash.com/photo-1442291928580-fb5d0856a8f1?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjExNzA0OH0', 29 | }, 30 | modules: [ 31 | { 32 | id: 'l_0', 33 | title: 'Exploring Time and Space', 34 | length: 258, 35 | }, 36 | ], 37 | }; 38 | 39 | describe('Modules Navigation View', () => { 40 | // automatically unmount and cleanup DOM after the test is finished. 41 | afterEach(cleanup); 42 | 43 | it('renders without error', () => { 44 | renderWithRouter(); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /client/src/components/__tests__/query-result.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, cleanup } from '../../utils/test-utils'; 3 | import QueryResult from '../query-result'; 4 | 5 | describe('Query Result', () => { 6 | // automatically unmount and cleanup DOM after the test is finished. 7 | afterEach(cleanup); 8 | 9 | it('renders loading state', async () => { 10 | const { getByTestId } = render(); 11 | getByTestId(/spinner/i); 12 | }); 13 | 14 | it('renders No Data message', async () => { 15 | // passing no error and no data 16 | const { getByText } = render(); 17 | getByText(/nothing to show/i); 18 | }); 19 | 20 | it('renders Error', async () => { 21 | const { getByText } = render(); 22 | getByText(/you lose/i); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /client/src/components/__tests__/track-detail.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderWithRouter, cleanup } from '../../utils/test-utils'; 3 | import TrackDetail from '../track-detail'; 4 | 5 | const mockTrack = { 6 | track: { 7 | id: 'c_0', 8 | title: 'Cat-stronomy, an introduction', 9 | description: '# Pulchra vehi vidit misera sola armenta secabatur\n\n', 10 | thumbnail: 11 | 'https://res.cloudinary.com/dety84pbu/image/upload/v1598465568/nebula_cat_djkt9r.jpg', 12 | length: 2377, 13 | modulesCount: 10, 14 | numberOfViews: 51, 15 | author: { 16 | name: 'Henri, le Chat Noir', 17 | photo: 18 | 'https://images.unsplash.com/photo-1442291928580-fb5d0856a8f1?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjExNzA0OH0', 19 | }, 20 | modules: [ 21 | { 22 | id: 'l_0', 23 | title: 'Exploring Time and Space', 24 | length: 258, 25 | }, 26 | ], 27 | }, 28 | }; 29 | 30 | describe('Module Detail View', () => { 31 | // automatically unmount and cleanup DOM after the test is finished. 32 | afterEach(cleanup); 33 | 34 | it('renders without error', () => { 35 | renderWithRouter(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /client/src/components/content-section.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { widths, colors } from '../styles'; 4 | 5 | /** 6 | * Content Section component renders content (mainly text/mdown based) 7 | * for course detail and lesson detail 8 | */ 9 | const ContentSection = ({ children }) => { 10 | return {children}; 11 | }; 12 | 13 | export default ContentSection; 14 | 15 | /** ContentSection styled component */ 16 | const ContentDiv = styled.div({ 17 | marginTop: 10, 18 | display: 'flex', 19 | flexDirection: 'column', 20 | maxWidth: widths.textPageWidth, 21 | width: '100%', 22 | alignSelf: 'center', 23 | backgroundColor: colors.background, 24 | }); 25 | -------------------------------------------------------------------------------- /client/src/components/footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { colors, ApolloIcon } from '../styles'; 4 | 5 | /** 6 | * Footer is useless component to make our app look a little closer to a real website! 7 | */ 8 | const Footer = ({ children }) => { 9 | return ( 10 | 11 | {new Date().getFullYear()} ©{' '} 12 | 13 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | export default Footer; 20 | 21 | /** Footer styled components */ 22 | const FooterContainer = styled.div({ 23 | display: 'flex', 24 | flexDirection: 'row', 25 | justifyContent: 'center', 26 | alignItems: 'center', 27 | color: colors.pink.base, 28 | marginTop: 30, 29 | height: 200, 30 | padding: 20, 31 | backgroundColor: 'white', 32 | borderTop: `solid 1px ${colors.pink.light}`, 33 | }); 34 | 35 | const LogoContainer = styled.div({ 36 | height: 40, 37 | marginLeft: 5, 38 | svg: { 39 | height: 40, 40 | }, 41 | }); 42 | -------------------------------------------------------------------------------- /client/src/components/header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { colors, widths } from '../styles'; 3 | import styled from '@emotion/styled'; 4 | import { Link } from 'react-router-dom'; 5 | import logo from '../assets/space_cat_logo.png'; 6 | 7 | /** 8 | * Header renders the top navigation 9 | * for this particular tutorial level, it only holds the home button 10 | */ 11 | const Header = ({ children }) => { 12 | return ( 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | <h3>Catstronaut</h3> 23 | <div>Kitty space academy</div> 24 | 25 | 26 | 27 | 28 | {children} 29 | 30 | 31 | ); 32 | }; 33 | 34 | export default Header; 35 | 36 | /** Header styled components */ 37 | const HeaderBar = styled.div({ 38 | display: 'flex', 39 | flexDirection: 'row', 40 | alignItems: 'center', 41 | justifyContent: 'center', 42 | borderBottom: `solid 1px ${colors.pink.light}`, 43 | boxShadow: '0px 1px 5px 0px rgba(0,0,0,0.15)', 44 | padding: '5px 30px', 45 | minHeight: 80, 46 | backgroundColor: 'white', 47 | }); 48 | 49 | const Container = styled.div({ 50 | width: `${widths.regularPageWidth}px`, 51 | }); 52 | 53 | const HomeLink = styled(Link)({ 54 | textDecoration: 'none', 55 | }); 56 | 57 | const HomeButtonContainer = styled.div({ 58 | display: 'flex', 59 | flex: 1, 60 | }); 61 | 62 | const HomeButton = styled.div({ 63 | display: 'flex', 64 | flexDirection: 'row', 65 | color: colors.accent, 66 | alignItems: 'center', 67 | ':hover': { 68 | color: colors.pink.dark, 69 | }, 70 | }); 71 | 72 | const LogoContainer = styled.div({ display: 'flex', alignSelf: 'center' }); 73 | 74 | const Logo = styled.img({ 75 | height: 60, 76 | width: 60, 77 | marginRight: 8, 78 | }); 79 | 80 | const Title = styled.div({ 81 | display: 'flex', 82 | flexDirection: 'column', 83 | h3: { 84 | lineHeight: '1em', 85 | marginBottom: 0, 86 | }, 87 | div: { 88 | fontSize: '0.9em', 89 | lineHeight: '0.8em', 90 | paddingLeft: 2, 91 | }, 92 | }); 93 | -------------------------------------------------------------------------------- /client/src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Footer } from './footer'; 2 | export { default as Header } from './header'; 3 | export { default as Layout } from './layout'; 4 | export { default as ModuleDetail } from './module-detail'; 5 | export { default as QueryResult } from './query-result'; 6 | -------------------------------------------------------------------------------- /client/src/components/layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Header, Footer } from '../components'; 3 | import styled from '@emotion/styled'; 4 | import { widths, unit } from '../styles'; 5 | 6 | /** 7 | * Layout renders the full page content: 8 | * with header, Page container and footer 9 | */ 10 | const Layout = ({ fullWidth, children, grid }) => { 11 | return ( 12 | <> 13 |
14 | 15 | {children} 16 | 17 |