├── .DS_Store ├── .backtracejsrc ├── .dockerignore ├── .env ├── .github ├── CODEOWNERS └── workflows │ ├── github-pages.yml │ └── sample-app-web.yml ├── .gitignore ├── .sauce └── config.yml ├── .storybook ├── main.js └── preview.js ├── Dockerfile ├── Jenkinsfile ├── LICENSE ├── README.md ├── babel.config.js ├── orchestrate.sh ├── package-lock.json ├── package.json ├── public ├── 404.html ├── CNAME ├── SL-symbol-color.png ├── favicon.ico ├── frames │ ├── frame-content.html │ ├── frame.html │ ├── global.css │ ├── header.html │ ├── iframe-content.html │ ├── iframe.html │ ├── index.html │ ├── main.html │ └── sidebar.html ├── icon-192x192.png ├── icon-256x256.png ├── icon-384x384.png ├── icon-512x512.png ├── index.html ├── manifest.json ├── manifest.webmanifest ├── robots.txt └── v1 │ ├── cart.html │ ├── checkout-complete.html │ ├── checkout-step-one.html │ ├── checkout-step-two.html │ ├── css │ └── sample-app-web.css │ ├── img │ ├── HeaderBar_Bot.png │ ├── Login_Bot_graphic.png │ ├── SwagBot_Footer_graphic.png │ ├── SwagLabs_logo.png │ ├── bike-light-1200x1500.jpg │ ├── bolt-shirt-1200x1500.jpg │ ├── cart.png │ ├── facebook.png │ ├── linkedIn.png │ ├── peek.png │ ├── pony-express.png │ ├── red-onesie-1200x1500.jpg │ ├── red-tatt-1200x1500.jpg │ ├── sauce-backpack-1200x1500.jpg │ ├── sauce-pullover-1200x1500.jpg │ ├── sl-404.jpg │ └── twitter.png │ ├── index.html │ ├── inventory-item.html │ ├── inventory.html │ └── main.js ├── scripts └── postbuild.sh ├── src ├── .DS_Store ├── assets │ ├── img │ │ ├── arrow.png │ │ ├── back-arrow.png │ │ ├── bike-light-1200x1500.jpg │ │ ├── bolt-shirt-1200x1500.jpg │ │ ├── cart.png │ │ ├── checkmark.png │ │ ├── close.png │ │ ├── facebook.png │ │ ├── filter.png │ │ ├── linkedIn.png │ │ ├── menu.png │ │ ├── pony-express.png │ │ ├── red-onesie-1200x1500.jpg │ │ ├── red-tatt-1200x1500.jpg │ │ ├── sauce-backpack-1200x1500.jpg │ │ ├── sauce-pullover-1200x1500.jpg │ │ ├── sl-404.jpg │ │ └── twitter.png │ └── svg │ │ ├── arrow3x.svg │ │ ├── cart3x.svg │ │ ├── close@3x.svg │ │ ├── filter3x.svg │ │ ├── logo3x.svg │ │ └── menu3x.svg ├── components │ ├── BrokenComponent.jsx │ ├── Button.css │ ├── Button.jsx │ ├── CartButton.css │ ├── CartButton.jsx │ ├── CartItem.css │ ├── CartItem.jsx │ ├── DrawerMenu.css │ ├── DrawerMenu.jsx │ ├── ErrorMessage.css │ ├── ErrorMessage.jsx │ ├── Footer.css │ ├── Footer.jsx │ ├── HeaderContainer.css │ ├── HeaderContainer.jsx │ ├── InputError.css │ ├── InputError.jsx │ ├── InventoryListItem.css │ ├── InventoryListItem.jsx │ ├── PrivateRoute.jsx │ ├── Select.css │ ├── Select.jsx │ ├── SubmitButton.css │ ├── SubmitButton.jsx │ └── __tests__ │ │ ├── BrokenComponent.tests.js │ │ ├── Button.tests.js │ │ ├── CartButton.tests.js │ │ ├── CartItem.tests.js │ │ ├── DrawerMenu.tests.js │ │ ├── ErrorMessage.tests.js │ │ ├── Footer.tests.js │ │ ├── HeaderContainer.tests.js │ │ ├── InputError.tests.js │ │ ├── InventoryListItem.tests.js │ │ ├── Select.tests.js │ │ ├── SubmitButton.tests.js │ │ └── __snapshots__ │ │ ├── Button.tests.js.snap │ │ ├── CartButton.tests.js.snap │ │ ├── CartItem.tests.js.snap │ │ ├── DrawerMenu.tests.js.snap │ │ ├── ErrorMessage.tests.js.snap │ │ ├── Footer.tests.js.snap │ │ ├── HeaderContainer.tests.js.snap │ │ ├── InputError.tests.js.snap │ │ ├── InventoryListItem.tests.js.snap │ │ ├── Select.tests.js.snap │ │ └── SubmitButton.tests.js.snap ├── img │ └── .DS_Store ├── index.css ├── index.jsx ├── pages │ ├── Cart.css │ ├── Cart.jsx │ ├── CheckOutStepOne.css │ ├── CheckOutStepOne.jsx │ ├── CheckOutStepTwo.css │ ├── CheckOutStepTwo.jsx │ ├── Finish.css │ ├── Finish.jsx │ ├── Inventory.css │ ├── Inventory.jsx │ ├── InventoryItem.css │ ├── InventoryItem.jsx │ ├── Login.css │ ├── Login.jsx │ └── __tests__ │ │ ├── Cart.tests.js │ │ ├── CheckOutStepOne.tests.js │ │ ├── CheckOutStepTwo.tests.js │ │ ├── Finish.tests.js │ │ ├── Inventory.tests.js │ │ ├── InventoryItem.tests.js │ │ ├── Login.tests.js │ │ └── __snapshots__ │ │ ├── Cart.tests.js.snap │ │ ├── CheckOutStepOne.tests.js.snap │ │ ├── CheckOutStepTwo.tests.js.snap │ │ ├── Finish.tests.js.snap │ │ ├── Inventory.tests.js.snap │ │ ├── InventoryItem.tests.js.snap │ │ └── Login.tests.js.snap ├── service-worker.js ├── serviceWorkerRegistration.js ├── setupTests.js ├── storybook │ └── stories │ │ ├── Button.stories.js │ │ ├── CartItem.stories.js │ │ ├── ErrorMessage.stories.js │ │ ├── HeaderContainer.stories.js │ │ ├── InputError.stories.js │ │ ├── InventoryListItem.stories.js │ │ ├── Select.stories.js │ │ ├── SubmitButton.stories.js │ │ └── assets │ │ ├── code-brackets.svg │ │ ├── colors.svg │ │ ├── comments.svg │ │ ├── direction.svg │ │ ├── flow.svg │ │ ├── plugin.svg │ │ ├── repo.svg │ │ └── stackalt.svg └── utils │ ├── Constants.js │ ├── Credentials.js │ ├── InventoryData.js │ ├── InventoryDataLong.js │ ├── Sorting.js │ ├── __mocks__ │ └── fileMock.js │ ├── __tests__ │ ├── Credentials.tests.js │ ├── Sorting.tests.js │ ├── __snapshots__ │ │ └── Sorting.tests.js.snap │ └── shopping-cart.tests.js │ └── shopping-cart.js └── test ├── e2e ├── configs │ ├── e2eConstants.js │ ├── wdio.local.chrome.conf.js │ ├── wdio.saucelabs-orchestrate.conf.js │ ├── wdio.saucelabs.conf.js │ └── wdio.shared.conf.js ├── helpers │ └── index.js ├── page-objects │ ├── AppHeaderPage.js │ ├── BasePage.js │ ├── CartSummaryPage.js │ ├── CheckoutCompletePage.js │ ├── CheckoutPersonalInfoPage.js │ ├── CheckoutSummaryPage.js │ ├── LoginPage.js │ ├── MenuPage.js │ ├── SwagDetailsPage.js │ └── SwagOverviewPage.js └── specs │ ├── cart.summary.spec.js │ ├── checkout.complete.spec.js │ ├── checkout.personal.info.spec.js │ ├── checkout.summary.spec.js │ ├── login.spec.js │ ├── menu.spec.js │ ├── swag.item.details.spec.js │ └── swag.items.list.spec.js └── visual └── storybook ├── ci.config.js ├── desktop.config.js └── mobile.config.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/.DS_Store -------------------------------------------------------------------------------- /.backtracejsrc: -------------------------------------------------------------------------------- 1 | { 2 | "path": "./build/static/js", 3 | "run": { 4 | "upload": true, 5 | "process": true 6 | }, 7 | "upload": { 8 | "url": "https://submit.backtrace.io/UNIVERSE/TOKEN/sourcemap", 9 | "include-sources": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | artifacts 2 | sc-orchestrate.log 3 | servier-orchestrate.log 4 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @wswebcreation @diemol 2 | -------------------------------------------------------------------------------- /.github/workflows/github-pages.yml: -------------------------------------------------------------------------------- 1 | name: github pages release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | # paths: 8 | # - '.storybook' 9 | # - 'public' 10 | # - 'src' 11 | 12 | jobs: 13 | deploy: 14 | runs-on: ubuntu-latest 15 | defaults: 16 | run: 17 | working-directory: ./ 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - name: Build 22 | uses: actions/setup-node@v2 23 | with: 24 | node-version: '14.x' 25 | 26 | - name: Build site 27 | env: 28 | CI: false 29 | run: | 30 | ls 31 | npm ci 32 | npm run build 33 | 34 | - name: Build storybook 35 | env: 36 | CI: false 37 | run: | 38 | ls 39 | npm run build.storybook 40 | 41 | - name: Deploy 42 | uses: peaceiris/actions-gh-pages@v3 43 | with: 44 | github_token: ${{ secrets.GITHUB_TOKEN }} 45 | publish_dir: ./build 46 | cname: www.saucedemo.com 47 | -------------------------------------------------------------------------------- /.github/workflows/sample-app-web.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Swag Labs Sample App Workflow 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | pull_request: 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | defaults: 16 | run: 17 | working-directory: ./ 18 | env: 19 | BUILD_PREFIX: true 20 | IS_MAIN: ${{ github.ref == 'refs/heads/main' }} 21 | SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} 22 | SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} 23 | SCREENER_API_KEY: ${{ secrets.SCREENER_API_KEY }} 24 | 25 | strategy: 26 | matrix: 27 | node-version: [14.x] 28 | 29 | steps: 30 | - name: Checkout Repository 31 | uses: actions/checkout@v2 32 | 33 | - name: Setup Node.js ${{ matrix.node-version }} 34 | uses: actions/setup-node@v1 35 | with: 36 | node-version: ${{ matrix.node-version }} 37 | 38 | # Site Testing steps 39 | - name: Install dependencies 40 | run: npm ci 41 | 42 | - name: Build 43 | run: CI=false npm run build 44 | 45 | - name: Run Unit Tests and generate coverage report 46 | run: npm run test.coverage 47 | 48 | - name: Upload coverage to Codecov 49 | uses: codecov/codecov-action@v1 50 | with: 51 | token: ${{ secrets.CODECOV_TOKEN }} 52 | 53 | # Only run the last 2 steps when we are not on the main branch 54 | - name: Run Storybook tests 55 | if: ${{ !env.IS_MAIN }} 56 | run: npm run test.storybook.ci 57 | 58 | - name: Build and E2E test the site 59 | if: ${{ !env.IS_MAIN }} 60 | run: npm run start & npx wait-on --timeout 60000 http://localhost:3000 && npm run test.e2e.sauce.us 61 | -------------------------------------------------------------------------------- /.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 | *.log 21 | .project 22 | bin 23 | dist 24 | screenshots 25 | .idea 26 | .coverage 27 | storybook-static 28 | .tmp 29 | .eslintcache 30 | -------------------------------------------------------------------------------- /.sauce/config.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1alpha 2 | kind: imagerunner 3 | sauce: 4 | region: us-west-1 5 | concurrency: 2 6 | suites: 7 | - name: Start Web App 8 | workload: webdriver 9 | image: mikedonovan1987/sample-app-web-orchestrate:0.0.4 10 | imagePullAuth: 11 | user: $DOCKER_USERNAME 12 | token: $DOCKER_PASSWORD 13 | entrypoint: "./orchestrate.sh" 14 | 15 | - name: Demo App Tests 16 | workload: webdriver 17 | image: mikedonovan1987/sample-app-web-orchestrate:0.0.4 18 | imagePullAuth: 19 | user: $DOCKER_USERNAME 20 | token: $DOCKER_PASSWORD 21 | entrypoint: "npm run test.e2e.sauce.us-orchestrate" 22 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "stories": [ 3 | "../src/storybook/**/*.stories.mdx", 4 | "../src/storybook/**/*.stories.@(js|jsx|ts|tsx)" 5 | ], 6 | "addons": [ 7 | "@storybook/addon-links", 8 | "@storybook/addon-essentials", 9 | "@storybook/preset-create-react-app" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import '../src/index.css'; 2 | 3 | export const parameters = { 4 | actions: { argTypesRegex: "^on[A-Z].*" }, 5 | } 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14 2 | 3 | # install sauce connect 4 | RUN curl -LO https://saucelabs.com/downloads/sc-4.8.2-linux.tar.gz 5 | RUN tar xvf ./sc-4.8.2-linux.tar.gz 6 | ENV PATH="$HOME/sc-4.8.2-linux/bin:$PATH" 7 | 8 | # web app 9 | WORKDIR /sample-app-web 10 | COPY . . 11 | RUN npm ci 12 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | 4 | tools {nodejs "12.6"} 5 | 6 | stages { 7 | 8 | stage('Install dependencies') { 9 | steps { 10 | sh "npm install" 11 | } 12 | } 13 | 14 | stage('Build application') { 15 | steps { 16 | sh "npm start & npx wait-on --timeout 60000 http://localhost:3000 &" 17 | } 18 | } 19 | 20 | stage('Run Functional Tests') { 21 | steps { 22 | sh "npm run test.e2e.sauce.eu ${env.CLI_ARGS}" 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sauce Labs 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 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', { 4 | targets: { 5 | node: 12 6 | } 7 | }] 8 | ], 9 | plugins: ['@babel/plugin-proposal-private-methods'] 10 | }; 11 | -------------------------------------------------------------------------------- /orchestrate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sc -u $SAUCE_USERNAME -k $SAUCE_ACCESS_KEY --region us-west --tunnel-name mdonovan2010_tunnel_name &> sc-orchestrate.log & 4 | 5 | npm run start -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Single Page Apps for GitHub Pages 6 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/CNAME: -------------------------------------------------------------------------------- 1 | www.saucedemo.com 2 | -------------------------------------------------------------------------------- /public/SL-symbol-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/SL-symbol-color.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/favicon.ico -------------------------------------------------------------------------------- /public/frames/frame-content.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/frames/frame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Merchant Detail Page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | <body> 14 | <p>Your browser does not support frames.</p> 15 | </body> 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/frames/global.css: -------------------------------------------------------------------------------- 1 | iframe, frame { 2 | border: none; 3 | } 4 | 5 | body, html { 6 | height: 100%; 7 | margin: 0; 8 | padding: 0; 9 | } 10 | -------------------------------------------------------------------------------- /public/frames/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 51 | 52 | 53 |
54 | Logo 57 | 65 |
66 | 67 |
68 |
69 |

Welcome to My Website

70 |

Explore our services and learn more about us!

71 |
72 |
73 | 74 | 75 | -------------------------------------------------------------------------------- /public/frames/iframe-content.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 28 | 29 | 30 | 31 | 32 | 34 |
35 | 36 | 37 |
38 | 39 | 40 | -------------------------------------------------------------------------------- /public/frames/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Merchant Detail Page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /public/frames/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Visual test page 7 | 8 | 9 | 10 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /public/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/icon-192x192.png -------------------------------------------------------------------------------- /public/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/icon-256x256.png -------------------------------------------------------------------------------- /public/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/icon-384x384.png -------------------------------------------------------------------------------- /public/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/icon-512x512.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 24 | 25 | 26 | 48 | 49 | 50 | 51 | Swag Labs 52 | 53 | 54 | 55 |
56 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme_color": "#eefcf6", 3 | "background_color": "#132322", 4 | "display": "browser", 5 | "scope": "/", 6 | "start_url": "/.", 7 | "name": "Swag Labs", 8 | "short_name": "Swag Labs", 9 | "icons": [ 10 | { 11 | "src": "/icon-192x192.png", 12 | "sizes": "192x192", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "/icon-256x256.png", 17 | "sizes": "256x256", 18 | "type": "image/png" 19 | }, 20 | { 21 | "src": "/icon-384x384.png", 22 | "sizes": "384x384", 23 | "type": "image/png" 24 | }, 25 | { 26 | "src": "/icon-512x512.png", 27 | "sizes": "512x512", 28 | "type": "image/png" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /public/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "theme_color": "#eefcf6", 3 | "background_color": "#132322", 4 | "display": "browser", 5 | "scope": "/", 6 | "start_url": "/.", 7 | "name": "Swag Labs", 8 | "short_name": "Swag Labs", 9 | "icons": [ 10 | { 11 | "src": "/icon-192x192.png", 12 | "sizes": "192x192", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "/icon-256x256.png", 17 | "sizes": "256x256", 18 | "type": "image/png" 19 | }, 20 | { 21 | "src": "/icon-384x384.png", 22 | "sizes": "384x384", 23 | "type": "image/png" 24 | }, 25 | { 26 | "src": "/icon-512x512.png", 27 | "sizes": "512x512", 28 | "type": "image/png" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/v1/cart.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Swag Labs 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 |
19 |
20 |
Your Cart
21 |
22 |
23 |
24 |
25 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /public/v1/checkout-complete.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Swag Labs 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 |
19 |
20 |
Finish
21 |
22 |

THANK YOU FOR YOUR ORDER

23 |
Your order has been dispatched, and will arrive just as fast as the pony can get 24 | there! 25 |
26 | 27 |
28 |
29 |
30 |
31 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/v1/checkout-step-one.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Swag Labs 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 |
19 |
20 |
Checkout: Your Information
21 |
22 |
23 |
24 |
25 | 26 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /public/v1/checkout-step-two.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Swag Labs 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 |
19 |
20 |
Checkout: Overview
21 |
22 |
23 |
24 | 25 |
26 | 27 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/v1/img/HeaderBar_Bot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/v1/img/HeaderBar_Bot.png -------------------------------------------------------------------------------- /public/v1/img/Login_Bot_graphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/v1/img/Login_Bot_graphic.png -------------------------------------------------------------------------------- /public/v1/img/SwagBot_Footer_graphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/v1/img/SwagBot_Footer_graphic.png -------------------------------------------------------------------------------- /public/v1/img/SwagLabs_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/v1/img/SwagLabs_logo.png -------------------------------------------------------------------------------- /public/v1/img/bike-light-1200x1500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/v1/img/bike-light-1200x1500.jpg -------------------------------------------------------------------------------- /public/v1/img/bolt-shirt-1200x1500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/v1/img/bolt-shirt-1200x1500.jpg -------------------------------------------------------------------------------- /public/v1/img/cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/v1/img/cart.png -------------------------------------------------------------------------------- /public/v1/img/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/v1/img/facebook.png -------------------------------------------------------------------------------- /public/v1/img/linkedIn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/v1/img/linkedIn.png -------------------------------------------------------------------------------- /public/v1/img/peek.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/v1/img/peek.png -------------------------------------------------------------------------------- /public/v1/img/pony-express.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/v1/img/pony-express.png -------------------------------------------------------------------------------- /public/v1/img/red-onesie-1200x1500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/v1/img/red-onesie-1200x1500.jpg -------------------------------------------------------------------------------- /public/v1/img/red-tatt-1200x1500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/v1/img/red-tatt-1200x1500.jpg -------------------------------------------------------------------------------- /public/v1/img/sauce-backpack-1200x1500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/v1/img/sauce-backpack-1200x1500.jpg -------------------------------------------------------------------------------- /public/v1/img/sauce-pullover-1200x1500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/v1/img/sauce-pullover-1200x1500.jpg -------------------------------------------------------------------------------- /public/v1/img/sl-404.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/v1/img/sl-404.jpg -------------------------------------------------------------------------------- /public/v1/img/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/public/v1/img/twitter.png -------------------------------------------------------------------------------- /public/v1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Swag Labs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 |
17 | 18 |
19 | 20 | 21 | 22 |
23 |
24 | 38 |
39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /public/v1/inventory-item.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Swag Labs 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 |
15 |
Swag Labs
16 |
17 |
18 | 19 |
20 |
21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /public/v1/inventory.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Swag Labs 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /scripts/postbuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # copy the content of the storybook build folder to the main build folder 4 | cp -a ../storybook-static/. ../build/storybook/ 5 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/src/.DS_Store -------------------------------------------------------------------------------- /src/assets/img/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/src/assets/img/arrow.png -------------------------------------------------------------------------------- /src/assets/img/back-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/src/assets/img/back-arrow.png -------------------------------------------------------------------------------- /src/assets/img/bike-light-1200x1500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/src/assets/img/bike-light-1200x1500.jpg -------------------------------------------------------------------------------- /src/assets/img/bolt-shirt-1200x1500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/src/assets/img/bolt-shirt-1200x1500.jpg -------------------------------------------------------------------------------- /src/assets/img/cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/src/assets/img/cart.png -------------------------------------------------------------------------------- /src/assets/img/checkmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/src/assets/img/checkmark.png -------------------------------------------------------------------------------- /src/assets/img/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/src/assets/img/close.png -------------------------------------------------------------------------------- /src/assets/img/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/src/assets/img/facebook.png -------------------------------------------------------------------------------- /src/assets/img/filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/src/assets/img/filter.png -------------------------------------------------------------------------------- /src/assets/img/linkedIn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/src/assets/img/linkedIn.png -------------------------------------------------------------------------------- /src/assets/img/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/src/assets/img/menu.png -------------------------------------------------------------------------------- /src/assets/img/pony-express.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/src/assets/img/pony-express.png -------------------------------------------------------------------------------- /src/assets/img/red-onesie-1200x1500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/src/assets/img/red-onesie-1200x1500.jpg -------------------------------------------------------------------------------- /src/assets/img/red-tatt-1200x1500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/src/assets/img/red-tatt-1200x1500.jpg -------------------------------------------------------------------------------- /src/assets/img/sauce-backpack-1200x1500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/src/assets/img/sauce-backpack-1200x1500.jpg -------------------------------------------------------------------------------- /src/assets/img/sauce-pullover-1200x1500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/src/assets/img/sauce-pullover-1200x1500.jpg -------------------------------------------------------------------------------- /src/assets/img/sl-404.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/src/assets/img/sl-404.jpg -------------------------------------------------------------------------------- /src/assets/img/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/sample-app-web/9192e428a8b6e913219c617e10b0b7eb27e24a62/src/assets/img/twitter.png -------------------------------------------------------------------------------- /src/assets/svg/arrow3x.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /src/assets/svg/cart3x.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/svg/close@3x.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /src/assets/svg/filter3x.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /src/assets/svg/menu3x.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /src/components/BrokenComponent.jsx: -------------------------------------------------------------------------------- 1 | const BrokenComponent = () => { 2 | throw new Error("This component failed to render!"); 3 | }; 4 | 5 | export default BrokenComponent; 6 | -------------------------------------------------------------------------------- /src/components/Button.css: -------------------------------------------------------------------------------- 1 | .btn { 2 | border: none; 3 | box-sizing: border-box; 4 | cursor: pointer; 5 | outline: none; 6 | padding: 6px 20px; 7 | font-size: 16px; 8 | line-height: 20px; 9 | font-weight: 500; 10 | font-family: "DM Sans", sans-serif; 11 | display: inline-block; 12 | text-decoration: none; 13 | border-radius: 4px; 14 | } 15 | 16 | .btn_primary { 17 | border: 1px solid #132322; 18 | color: #132322; 19 | background-color: #fff; 20 | } 21 | 22 | .btn_secondary { 23 | border: 1px solid #e2231a; 24 | background-color: #fff; 25 | color: #e2231a; 26 | } 27 | 28 | .btn_action { 29 | background-color: #3ddc91; 30 | border-radius: 4px; 31 | color: #132322; 32 | } 33 | 34 | .btn_secondary.back { 35 | position: relative; 36 | color: #132322; 37 | border: 1px solid #132322; 38 | } 39 | 40 | .btn_secondary .back-image { 41 | position: absolute; 42 | left: 0; 43 | top: 10px; 44 | height: 11px; 45 | width: 11px; 46 | } 47 | 48 | .btn_small { 49 | width: 160px; 50 | } 51 | 52 | .btn_medium { 53 | width: 220px; 54 | } 55 | 56 | .btn_large { 57 | width: 100%; 58 | } 59 | 60 | .btn_inventory_misaligned { 61 | position: absolute; 62 | right: -20px; 63 | } 64 | 65 | .btn_visual_failure { 66 | position: absolute; 67 | right: 0; 68 | top: 0; 69 | } 70 | 71 | @media only screen and (max-width: 900px) { 72 | .btn_small, 73 | .btn_medium, 74 | .btn_large { 75 | width: 100%; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/components/Button.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import "./Button.css"; 4 | import backPng from "../assets/img/back-arrow.png"; 5 | 6 | export const BUTTON_TYPES = { 7 | ACTION: "action", 8 | BACK: "secondary back", 9 | PRIMARY: "primary", 10 | SECONDARY: "secondary", 11 | }; 12 | export const BUTTON_SIZES = { 13 | SMALL: "small", 14 | MEDIUM: "medium", 15 | LARGE: "large", 16 | }; 17 | const Button = ({ 18 | customClass, 19 | label, 20 | onClick, 21 | size, 22 | testId, 23 | type, 24 | ...props 25 | }) => { 26 | const buttonTypeClass = ` btn_${type}`; 27 | const extraClass = customClass ? ` ${customClass}` : ""; 28 | const buttonSize = ` btn_${size}`; 29 | /* istanbul ignore next */ 30 | const BackImage = () => ( 31 | Go back 32 | ); 33 | 34 | return ( 35 | 50 | ); 51 | }; 52 | 53 | Button.propTypes = { 54 | /** 55 | * A custom class 56 | */ 57 | customClass: PropTypes.string, 58 | /** 59 | * The label 60 | */ 61 | label: PropTypes.string.isRequired, 62 | /** 63 | * The on click handler 64 | */ 65 | onClick: PropTypes.func.isRequired, 66 | /** 67 | * Size of the button 68 | */ 69 | size: PropTypes.oneOf(Object.values(BUTTON_SIZES)), 70 | /** 71 | * The test id 72 | */ 73 | testId: PropTypes.string, 74 | /** 75 | * What type of field is it 76 | */ 77 | type: PropTypes.oneOf(Object.values(BUTTON_TYPES)), 78 | }; 79 | 80 | Button.defaultProps = { 81 | customClass: undefined, 82 | size: BUTTON_SIZES.LARGE, 83 | testId: undefined, 84 | type: BUTTON_TYPES.PRIMARY, 85 | }; 86 | 87 | export default Button; 88 | -------------------------------------------------------------------------------- /src/components/CartButton.css: -------------------------------------------------------------------------------- 1 | .shopping_cart_link { 2 | background: url("../assets/img/cart.png") no-repeat center center; 3 | background: url("../assets/svg/cart3x.svg") no-repeat center center, 4 | linear-gradient(transparent, transparent); 5 | height: 40px; 6 | width: 40px; 7 | display: block; 8 | position: relative; 9 | } 10 | 11 | .shopping_cart_badge { 12 | background-color: #e2231a; 13 | border-radius: 10px; 14 | box-sizing: border-box; 15 | display: flex; 16 | color: #ffffff; 17 | font-weight: 400; 18 | top: 0; 19 | right: 0; 20 | height: 20px; 21 | width: 20px; 22 | vertical-align: middle; 23 | justify-content: center; 24 | align-items: center; 25 | position: absolute; 26 | } 27 | -------------------------------------------------------------------------------- /src/components/CartButton.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { withRouter } from "react-router-dom"; 3 | import PropTypes from "prop-types"; 4 | import { ShoppingCart } from "../utils/shopping-cart"; 5 | import { ROUTES } from "../utils/Constants"; 6 | import "./CartButton.css"; 7 | 8 | const CartButton = (props) => { 9 | const { history } = props; 10 | let cartBadge = ""; 11 | const [cartContents, setCartContents] = useState( 12 | ShoppingCart.getCartContents() 13 | ); 14 | // Strangely enough this is being called, but not covered in the report 15 | /* istanbul ignore next */ 16 | const cartListener = { 17 | forceUpdate: () => setCartContents(ShoppingCart.getCartContents()), 18 | }; 19 | 20 | useEffect(() => { 21 | ShoppingCart.registerCartListener(cartListener); 22 | }, []); 23 | 24 | if (cartContents.length > 0) { 25 | cartBadge = ( 26 | 27 | {cartContents.length} 28 | 29 | ); 30 | } 31 | 32 | return ( 33 | history.push(ROUTES.CART)} 36 | data-test="shopping-cart-link" 37 | > 38 | {cartBadge} 39 | 40 | ); 41 | }; 42 | 43 | CartButton.propTypes = { 44 | /** 45 | * The history 46 | */ 47 | history: PropTypes.shape({ 48 | push: PropTypes.func.isRequired, 49 | }).isRequired, 50 | }; 51 | 52 | export default withRouter(CartButton); 53 | -------------------------------------------------------------------------------- /src/components/CartItem.css: -------------------------------------------------------------------------------- 1 | .cart_item { 2 | border: 1px solid #ededed; 3 | border-radius: 8px; 4 | background: #fff; 5 | display: flex; 6 | padding: 20px; 7 | margin-bottom: 20px; 8 | } 9 | 10 | .cart_quantity { 11 | border: 1px solid #ededed; 12 | box-sizing: border-box; 13 | text-align: center; 14 | font-size: 14px; 15 | line-height: 20px; 16 | font-weight: 400; 17 | padding: 6px 20px; 18 | height: 36px; 19 | width: 44px; 20 | } 21 | 22 | .cart_item_label > a { 23 | color: #4a4a4a; 24 | text-decoration: none; 25 | } 26 | 27 | .cart_item_label { 28 | padding: 0 0 0 10px; 29 | display: inline-block; 30 | vertical-align: top; 31 | position: relative; 32 | width: 100%; 33 | } 34 | 35 | .inventory_item_desc { 36 | width: 75%; 37 | } 38 | 39 | .item_pricebar { 40 | margin-top: 20px; 41 | display: flex; 42 | justify-content: space-between; 43 | align-items: flex-end; 44 | } 45 | 46 | @media only screen and (max-width: 900px) { 47 | .item_pricebar .btn { 48 | width: 160px; 49 | } 50 | } 51 | @media only screen and (max-width: 640px) { 52 | /* .cart_item { 53 | padding: 15px 0; 54 | } */ 55 | 56 | .cart_quantity { 57 | padding: 5px 10px; 58 | width: 40px; 59 | } 60 | 61 | .inventory_item_desc { 62 | width: 100%; 63 | } 64 | 65 | .cart_item .inventory_item_price { 66 | margin: 0 0 20px 0; 67 | padding: 20px 10px 0 0; 68 | } 69 | 70 | .cart_item .item_pricebar { 71 | align-items: flex-start; 72 | flex-direction: column; 73 | } 74 | 75 | .item_pricebar .btn { 76 | width: 100%; 77 | } 78 | 79 | .cart_item_label { 80 | width: calc(100% - 50px); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/components/DrawerMenu.css: -------------------------------------------------------------------------------- 1 | @media only screen and (max-width: 480px) { 2 | .bm-menu-wrap { 3 | width: 100% !important; 4 | } 5 | } 6 | .menu-item { 7 | border-bottom: 1px solid #ededed; 8 | color: #18583a; 9 | display: inline-block; 10 | font-family: "DM Mono", "sans-serif"; 11 | font-size: 16px; 12 | margin-bottom: 7px; 13 | text-decoration: none; 14 | cursor: pointer; 15 | padding: 10px 0; 16 | } 17 | 18 | .menu-item:hover { 19 | color: #3ddc91; 20 | } 21 | 22 | /* ===============Burger button styling==================== */ 23 | /* Position and sizing of burger button */ 24 | .bm-burger-button { 25 | position: absolute; 26 | width: 20px; 27 | height: 20px; 28 | left: 20px; 29 | top: 20px; 30 | } 31 | 32 | /* Color/shape of burger icon bars */ 33 | .bm-burger-bars { 34 | background: #777; 35 | } 36 | 37 | /* Position and sizing of clickable cross button */ 38 | .bm-cross-button { 39 | height: 20px !important; 40 | width: 20px !important; 41 | right: 16px !important; 42 | top: 16px !important; 43 | } 44 | 45 | /* Color/shape of close button cross */ 46 | .bm-cross { 47 | background: #ffffff; 48 | } 49 | 50 | /* General sidebar styles */ 51 | .bm-menu { 52 | background: #fff; 53 | padding: 2.5em 1.5em 0; 54 | font-size: 16px; 55 | box-shadow: none; 56 | } 57 | 58 | /* Morph shape necessary with bubble or elastic */ 59 | .bm-morph-shape { 60 | fill: #373a47; 61 | } 62 | 63 | /* Individual item */ 64 | .bm-item { 65 | } 66 | 67 | .visual_failure { 68 | transform: rotate(3deg); 69 | } 70 | -------------------------------------------------------------------------------- /src/components/ErrorMessage.css: -------------------------------------------------------------------------------- 1 | .error-message-container { 2 | background-color: #ffffff; 3 | height: 45px; 4 | margin-bottom: 5px; 5 | margin-top: -10px; 6 | position: relative; 7 | flex: 1; 8 | display: flex; 9 | align-items: center; 10 | padding-left: 10px; 11 | padding-right: 10px; 12 | justify-content: center; 13 | } 14 | 15 | .error-message-container.error { 16 | background-color: #e2231a; 17 | } 18 | 19 | .error-message-container h3 { 20 | color: #ffffff; 21 | font-size: 14px; 22 | text-align: center; 23 | font-family: Roboto, Arial, Helvetica, sans-serif; 24 | padding-left: 10px; 25 | padding-right: 10px; 26 | } 27 | 28 | .error-message-container .error-button { 29 | border: 0px; 30 | background-color: transparent; 31 | color: #ffffff; 32 | cursor: pointer; 33 | position: absolute; 34 | right: 5px; 35 | top: 5px; 36 | } 37 | 38 | .error-message-container .error-button:focus { 39 | outline: none; 40 | } 41 | -------------------------------------------------------------------------------- /src/components/ErrorMessage.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 4 | import { faTimes } from "@fortawesome/free-solid-svg-icons"; 5 | import "./ErrorMessage.css"; 6 | 7 | const ErrorMessage = ({ isError, errorMessage, onClick, ...props }) => { 8 | return ( 9 |
13 | {isError && ( 14 | // This component is not structured how it should, 15 | // But this is done to keep backwards compatibility 16 |

17 | 24 | {errorMessage} 25 |

26 | )} 27 |
28 | ); 29 | }; 30 | 31 | ErrorMessage.propTypes = { 32 | /** 33 | * If this is an isError field yes or no 34 | */ 35 | isError: PropTypes.bool.isRequired, 36 | /** 37 | * The value of the input 38 | */ 39 | errorMessage: PropTypes.string.isRequired, 40 | /** 41 | * The on change handler 42 | */ 43 | onClick: PropTypes.func.isRequired, 44 | }; 45 | 46 | export default ErrorMessage; 47 | -------------------------------------------------------------------------------- /src/components/Footer.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | background-color: #132322; 3 | min-height: 140px; 4 | margin-top: auto; 5 | position: relative; 6 | /*display: inline-table;*/ 7 | } 8 | /* .footer_container { 9 | max-width: 1440px; 10 | margin: 0 auto; 11 | position: relative; 12 | height: auto; 13 | width: 100%; 14 | 15 | } */ 16 | .footer_copy, 17 | .social { 18 | position: absolute; 19 | } 20 | .footer_copy { 21 | left: 50px; 22 | bottom: 40px; 23 | color: #fff; 24 | } 25 | .social { 26 | list-style-type: none; 27 | left: 10px; 28 | top: 20px; 29 | } 30 | .social li { 31 | float: left; 32 | margin-right: 20px; 33 | display: inline-block; 34 | width: 30px; 35 | height: 30px; 36 | } 37 | .social_twitter { 38 | background: url("../assets/img/twitter.png") no-repeat; 39 | } 40 | .social_facebook { 41 | background: url("../assets/img/facebook.png") no-repeat; 42 | } 43 | .social_linkedin { 44 | background: url("../assets/img/linkedIn.png") no-repeat; 45 | } 46 | 47 | .social a { 48 | width: 30px; 49 | height: 30px; 50 | display: block; 51 | font-size: 0; 52 | } 53 | 54 | @media only screen and (max-width: 960px) { 55 | .footer { 56 | height: auto; 57 | text-align: center; 58 | padding: 0 10px; 59 | display: inline-table; 60 | } 61 | .social, 62 | .footer_copy { 63 | position: relative; 64 | } 65 | .social { 66 | width: 100%; 67 | padding: 0; 68 | left: 0; 69 | } 70 | .social li { 71 | float: none; 72 | } 73 | 74 | .footer_copy { 75 | width: 100%; 76 | left: 0; 77 | bottom: 0; 78 | margin: 40px 0; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/components/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./Footer.css"; 3 | 4 | const SwagLabsFooter = () => { 5 | return ( 6 | 47 | ); 48 | }; 49 | 50 | export default SwagLabsFooter; 51 | -------------------------------------------------------------------------------- /src/components/HeaderContainer.css: -------------------------------------------------------------------------------- 1 | .header_container { 2 | border-bottom: 1px solid #ededed; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | .primary_header { 8 | border-bottom: 1px solid #ededed; 9 | height: 56px; 10 | } 11 | 12 | .primary_header .header_label { 13 | background: #fff; 14 | padding: 4px 0; 15 | text-align: center; 16 | font-size: 24px; 17 | position: relative; 18 | } 19 | 20 | .primary_header .app_logo { 21 | font-family: DM Mono, "sans-serif"; 22 | font-size: 24px; 23 | line-height: 48px; 24 | text-align: center; 25 | margin: 0 50px; 26 | } 27 | 28 | .primary_header .shopping_cart_container { 29 | height: 40px; 30 | width: 40px; 31 | position: absolute; 32 | top: 10px; 33 | right: 20px; 34 | } 35 | .primary_header .shopping_cart_container.visual_failure { 36 | height: 40px; 37 | width: 40px; 38 | position: absolute; 39 | top: 40px; 40 | right: 205px; 41 | } 42 | 43 | .header_secondary_container { 44 | background: #fff; 45 | width: 100%; 46 | height: 56px; 47 | display: flex; 48 | flex-direction: row; 49 | align-items: center; 50 | padding: 0 20px; 51 | box-sizing: border-box; 52 | } 53 | 54 | .header_secondary_container .title { 55 | color: #132322; 56 | font-family: "DM Sans", sans-serif; 57 | font-size: 18px; 58 | font-weight: 500; 59 | line-height: 48px; 60 | } 61 | 62 | /* .primary_header, .header_secondary_container{ 63 | max-width: 1440px; 64 | margin: 0 auto; 65 | position: relative; 66 | width: 100%; 67 | } */ 68 | 69 | .right_component { 70 | margin-left: auto; 71 | } 72 | 73 | @media only screen and (max-width: 640px) { 74 | .header_secondary_container { 75 | height: 60px; 76 | padding: 0 15px; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/components/InputError.css: -------------------------------------------------------------------------------- 1 | .form_group { 2 | margin-bottom: 15px; 3 | position: relative; 4 | } 5 | .input_error { 6 | font-family: DM Sans, Arial, Helvetica, sans-serif; 7 | font-size: 14px; 8 | width: 100%; 9 | border: 0px; 10 | border-bottom: 1px solid #ededed; 11 | outline: none; 12 | padding: 10px 0; 13 | } 14 | 15 | .input_error.error { 16 | border-bottom-color: #e2231a; 17 | } 18 | 19 | .error_icon { 20 | color: #e2231a; 21 | font-size: 18px; 22 | position: absolute; 23 | right: 0; 24 | top: 12px; 25 | } 26 | 27 | /* Chrome/Opera/Safari */ 28 | .form_input::-webkit-input-placeholder, 29 | /* Firefox 19+ */ 30 | .form_input::-moz-placeholder, 31 | /* IE 10+ */ 32 | .form_input:-ms-input-placeholder, 33 | .form_input::placeholder { 34 | color: #6d7584; 35 | } 36 | 37 | /* Chrome/Opera/Safari */ 38 | .form_input.error::-webkit-input-placeholder, 39 | /* Firefox 19+ */ 40 | .form_input.error::-moz-placeholder, 41 | /* IE 10+ */ 42 | .form_input.error:-ms-input-placeholder, 43 | .form_input.error::placeholder { 44 | color: #e2231a !important; 45 | } 46 | -------------------------------------------------------------------------------- /src/components/InputError.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 4 | import { faTimesCircle } from "@fortawesome/free-solid-svg-icons"; 5 | import "./InputError.css"; 6 | 7 | export const INPUT_TYPES = { 8 | TEXT: "text", 9 | PASSWORD: "password", 10 | }; 11 | const InputError = ({ 12 | isError, 13 | onChange, 14 | placeholder, 15 | testId, 16 | type, 17 | value, 18 | ...props 19 | }) => { 20 | return ( 21 |
22 | 39 | {isError && ( 40 | 41 | )} 42 |
43 | ); 44 | }; 45 | 46 | InputError.propTypes = { 47 | /** 48 | * If this is an isError field yes or no 49 | */ 50 | isError: PropTypes.bool.isRequired, 51 | /** 52 | * The on change handler 53 | */ 54 | onChange: PropTypes.func.isRequired, 55 | /** 56 | * The placeholder of the input 57 | */ 58 | placeholder: PropTypes.string, 59 | /** 60 | * The test id 61 | */ 62 | testId: PropTypes.string, 63 | /** 64 | * What type of field is it 65 | */ 66 | type: PropTypes.oneOf(["text", "password"]), 67 | /** 68 | * The value of the input 69 | */ 70 | value: PropTypes.string, 71 | }; 72 | 73 | InputError.defaultProps = { 74 | placeholder: "", 75 | testId: undefined, 76 | type: INPUT_TYPES.TEXT, 77 | value: "", 78 | }; 79 | 80 | export default InputError; 81 | -------------------------------------------------------------------------------- /src/components/InventoryListItem.css: -------------------------------------------------------------------------------- 1 | .inventory_item { 2 | border: 1px solid #ededed; 3 | background: #fff; 4 | position: relative; 5 | margin-bottom: 12px; 6 | display: flex; 7 | box-sizing: border-box; 8 | border-radius: 8px; 9 | } 10 | 11 | .inventory_item_img { 12 | flex: 1; 13 | overflow: hidden; 14 | } 15 | .inventory_item_img a { 16 | display: block; 17 | height: 100%; 18 | width: 100%; 19 | } 20 | .inventory_item_img img { 21 | height: 100%; 22 | border-bottom-left-radius: 4px; 23 | border-top-left-radius: 4px; 24 | } 25 | 26 | .inventory_item_description { 27 | flex: 2; 28 | flex-direction: column; 29 | padding: 20px 34px 20px 20px; 30 | display: flex; 31 | justify-content: space-between; 32 | } 33 | 34 | .inventory_item_label > a { 35 | color: #4a4a4a; 36 | text-decoration: none; 37 | } 38 | .inventory_item_name { 39 | font-family: "DM Mono", sans-serif; 40 | font-size: 20px; 41 | font-weight: 500; 42 | color: #18583a; 43 | } 44 | .align_right { 45 | text-align: right; 46 | } 47 | .inventory_item_desc { 48 | font-family: "DM Sans", sans-serif; 49 | font-size: 14px; 50 | line-height: 20px; 51 | margin-top: 10px; 52 | color: #132322; 53 | } 54 | .inventory_item .inventory_item_desc { 55 | width: inherit; 56 | } 57 | .pricebar { 58 | display: flex; 59 | justify-content: space-between; 60 | align-items: flex-end; 61 | } 62 | 63 | .inventory_item_price { 64 | border-top: 1px solid #ededed; 65 | color: #132322; 66 | font-family: "DM Mono", sans-serif; 67 | font-size: 20px; 68 | font-weight: 500; 69 | line-height: 36px; 70 | display: inline-block; 71 | padding-top: 5px; 72 | } 73 | 74 | @media only screen and (min-width: 1060px) { 75 | .inventory_item { 76 | height: 240px; 77 | width: 505px; 78 | } 79 | } 80 | 81 | @media only screen and (max-width: 1060px) { 82 | .inventory_item { 83 | height: 240px; 84 | width: 100%; 85 | } 86 | .pricebar .btn { 87 | width: 160px; 88 | } 89 | } 90 | 91 | @media only screen and (max-width: 480px) { 92 | .inventory_item { 93 | height: inherit; 94 | flex-direction: column; 95 | border: none; 96 | margin-bottom: 40px; 97 | } 98 | 99 | .inventory_item_img { 100 | box-sizing: border-box; 101 | padding: 0 10px; 102 | } 103 | .inventory_item_img img { 104 | border-radius: 0; 105 | width: 100%; 106 | } 107 | 108 | .inventory_item_description { 109 | padding-bottom: 0; 110 | } 111 | 112 | .pricebar { 113 | align-items: flex-start; 114 | border-bottom: 1px solid #ededed; 115 | flex-direction: column; 116 | padding-bottom: 40px; 117 | } 118 | 119 | .inventory_item_price { 120 | margin: 20px 0; 121 | padding: 20px 10px 0 0; 122 | } 123 | 124 | .pricebar .btn { 125 | width: 100%; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/components/PrivateRoute.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Redirect, Route } from "react-router-dom"; 3 | import PropTypes from "prop-types"; 4 | import { isLoggedIn } from "../utils/Credentials"; 5 | import { ROUTES } from "../utils/Constants"; 6 | 7 | /** 8 | * @TODO: This can't be tested yet because enzyme currently doesn't support ReactJS17, 9 | * see https://github.com/enzymejs/enzyme/issues/2429. 10 | * This means we can't fully mount the component and test all rendered components 11 | * and functions 12 | */ 13 | /* istanbul ignore next */ 14 | const PrivateRoute = ({ component: Component, ...rest }) => { 15 | return ( 16 | 19 | isLoggedIn() ? ( 20 | 21 | ) : ( 22 | 25 | ) 26 | } 27 | /> 28 | ); 29 | }; 30 | /* istanbul ignore next */ 31 | PrivateRoute.propTypes = { 32 | /** 33 | * A react component 34 | */ 35 | component: PropTypes.element, 36 | }; 37 | /* istanbul ignore next */ 38 | PrivateRoute.defaultProps = { 39 | customClass: undefined, 40 | secondaryHeaderBot: undefined, 41 | secondaryLeftComponent: undefined, 42 | secondaryRightComponent: undefined, 43 | secondaryTitle: undefined, 44 | }; 45 | 46 | export default PrivateRoute; 47 | -------------------------------------------------------------------------------- /src/components/Select.css: -------------------------------------------------------------------------------- 1 | .select_container { 2 | border: 1px solid #ededed; 3 | border-radius: 4px; 4 | box-sizing: border-box; 5 | background-color: #ffffff; 6 | overflow: hidden; 7 | padding: 0 0 0 40px; 8 | cursor: pointer; 9 | height: 32px; 10 | width: 32px; 11 | display: block; 12 | position: relative; 13 | } 14 | 15 | .active_option { 16 | display: none; 17 | } 18 | 19 | .select_container:before { 20 | content: ""; 21 | background: url("../assets/img/filter.png") no-repeat center center; 22 | background: url("../assets/svg/filter3x.svg") no-repeat center center, 23 | linear-gradient(transparent, transparent); 24 | height: 32px; 25 | width: 32px; 26 | position: absolute; 27 | left: 0; 28 | top: 0; 29 | } 30 | 31 | .product_sort_container { 32 | position: absolute; 33 | top: 0; 34 | left: 0; 35 | right: 0; 36 | bottom: 0; 37 | width: 100%; 38 | /* This needs to be like this because otherwise Safari can't click on an element that is not visible */ 39 | opacity: 0.001; 40 | } 41 | 42 | @media only screen and (min-width: 900px) { 43 | .select_container { 44 | padding: 0 32px; 45 | width: 225px; 46 | line-height: 32px; 47 | } 48 | 49 | .select_container:after { 50 | content: ""; 51 | background: url("../assets/img/arrow.png") no-repeat center center; 52 | background: url("../assets/svg/arrow3x.svg") no-repeat center center, 53 | linear-gradient(transparent, transparent); 54 | height: 32px; 55 | width: 32px; 56 | position: absolute; 57 | right: 0; 58 | top: 0; 59 | } 60 | 61 | .active_option { 62 | display: block; 63 | font-size: 14px; 64 | text-align: center; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/components/Select.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import "./Select.css"; 4 | 5 | const Select = ({ activeOption, onChange, options, testId }) => { 6 | return ( 7 | 8 | 9 | { 10 | options[options.findIndex((option) => option.key === activeOption)] 11 | .value 12 | } 13 | 14 | 30 | 31 | ); 32 | }; 33 | Select.propTypes = { 34 | /** 35 | * The active option key 36 | */ 37 | activeOption: PropTypes.string.isRequired, 38 | /** 39 | * The on change handler 40 | */ 41 | onChange: PropTypes.func.isRequired, 42 | /** 43 | * The options 44 | */ 45 | options: PropTypes.arrayOf( 46 | PropTypes.shape({ 47 | key: PropTypes.string.isRequired, 48 | value: PropTypes.string.isRequired, 49 | }) 50 | ).isRequired, 51 | /** 52 | * The test id 53 | */ 54 | testId: PropTypes.string, 55 | }; 56 | Select.defaultProps = { 57 | testId: undefined, 58 | }; 59 | 60 | export default Select; 61 | -------------------------------------------------------------------------------- /src/components/SubmitButton.css: -------------------------------------------------------------------------------- 1 | .submit-button { 2 | background-color: #3ddc91; 3 | border: 4px solid #3ddc91; 4 | border-radius: 4px; 5 | color: #132322; 6 | cursor: pointer; 7 | display: inline-block; 8 | outline: none; 9 | font-size: 16px; 10 | line-height: 20px; 11 | /* letter-spacing: 2px; */ 12 | font-family: DM Sans, Arial, Helvetica, sans-serif; 13 | line-height: initial; 14 | margin-bottom: 15px; 15 | padding: 10px 30px; 16 | text-decoration: none; 17 | width: 100%; 18 | } 19 | -------------------------------------------------------------------------------- /src/components/SubmitButton.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import "./SubmitButton.css"; 4 | 5 | const SubmitButton = ({ customClass, testId, value, ...props }) => { 6 | const extraClass = customClass ? ` ${customClass}` : ""; 7 | return ( 8 | 21 | ); 22 | }; 23 | 24 | SubmitButton.propTypes = { 25 | /** 26 | * A custom class 27 | */ 28 | customClass: PropTypes.string, 29 | /** 30 | * The test id 31 | */ 32 | testId: PropTypes.string, 33 | /** 34 | * The value of the input 35 | */ 36 | value: PropTypes.string.isRequired, 37 | }; 38 | 39 | SubmitButton.defaultProps = { 40 | customClass: undefined, 41 | testId: undefined, 42 | }; 43 | 44 | export default SubmitButton; 45 | -------------------------------------------------------------------------------- /src/components/__tests__/BrokenComponent.tests.js: -------------------------------------------------------------------------------- 1 | import { shallow } from "enzyme"; 2 | import BrokenComponent from "../BrokenComponent"; 3 | 4 | describe("BrokenComponent", () => { 5 | it("should throw an error on render", () => { 6 | expect(() => shallow()).toThrowError( 7 | "This component failed to render!" 8 | ); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/components/__tests__/Button.tests.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { shallow } from "enzyme"; 3 | import Button, { BUTTON_SIZES, BUTTON_TYPES } from "../Button"; 4 | 5 | let props; 6 | 7 | describe("Button", () => { 8 | beforeEach(() => { 9 | props = { 10 | label: "Default button", 11 | onClick: () => {}, 12 | }; 13 | }); 14 | 15 | it("should render correctly with the required options", () => { 16 | const component = shallow(