├── .dockerignore ├── .gitignore ├── .prettierrc ├── Dockerfile ├── LICENSE ├── README.md ├── app.json ├── app.yml ├── package-lock.json ├── package.json ├── public ├── img │ └── favicons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── manifest.json │ │ ├── mstile-144x144.png │ │ ├── mstile-150x150.png │ │ ├── mstile-310x150.png │ │ ├── mstile-310x310.png │ │ ├── mstile-70x70.png │ │ └── safari-pinned-tab.svg ├── index.html ├── js │ └── production.min.js ├── manifest.json └── style.min.css ├── src ├── assets │ ├── header.png │ └── img │ │ ├── bright.png │ │ ├── footer │ │ ├── at-love.svg │ │ ├── i-love.svg │ │ └── we-love.svg │ │ ├── headers │ │ └── header.png │ │ ├── logo │ │ ├── ILOVELAMP.png │ │ ├── ill-dark.svg │ │ ├── ill-short-dark.svg │ │ ├── ill-short-white.svg │ │ └── ill-white.svg │ │ ├── modern.png │ │ ├── products │ │ ├── lamp1-trans.png │ │ ├── lamp10-trans.png │ │ ├── lamp2-trans.png │ │ ├── lamp3-trans.png │ │ ├── lamp4-trans.png │ │ ├── lamp5-trans.png │ │ ├── lamp6-trans.png │ │ ├── lamp7-trans.png │ │ ├── lamp8-trans.png │ │ └── lamp9-trans.png │ │ ├── silver.png │ │ ├── style.min.css │ │ ├── unique.png │ │ └── weloveyou.svg ├── components │ ├── App.js │ ├── Cart │ │ ├── Cart.js │ │ ├── CartCounter.js │ │ ├── CartHeader.js │ │ └── CartItems.js │ ├── Categories │ │ ├── Categories.js │ │ └── CategoriesContainer.js │ ├── Checkout │ │ ├── CheckoutContainer.js │ │ ├── CheckoutForm.js │ │ ├── CheckoutItems.js │ │ ├── CheckoutSummary.js │ │ └── OneClickCheckout.js │ ├── Home │ │ ├── Home.js │ │ ├── HomeHeader.js │ │ ├── HomeMainSection.js │ │ ├── TopPicks.js │ │ └── TopPicksContainer.js │ ├── Orders │ │ ├── OrderConfirmation.js │ │ └── OrderConfirmationContainer.js │ ├── Products │ │ ├── AllProducts.js │ │ ├── ProductHeader.js │ │ ├── ProductImage.js │ │ ├── ProductsContainer.js │ │ ├── ProductsHeader.js │ │ ├── SingleProduct.js │ │ └── SingleProductContainer.js │ ├── Styles │ │ ├── StyleProducts.js │ │ ├── StylesContainer.js │ │ ├── StylesHeader.js │ │ ├── StylesHeading.js │ │ └── StylesMenu.js │ └── global │ │ ├── Footer.js │ │ ├── HeaderNav.js │ │ ├── Loading.js │ │ ├── MailingList.js │ │ ├── Mobile │ │ ├── MenuButton.js │ │ ├── MobileNav.js │ │ └── MobileNavMenu.js │ │ └── NotFound.js ├── ducks │ ├── cart.js │ ├── categories.js │ ├── checkout.js │ ├── collections.js │ ├── index.js │ ├── payments.js │ ├── product.js │ ├── products.js │ └── styles.js ├── index.css ├── index.js ├── moltin.js └── store.js ├── static.json └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | # Items that don't need to be in a Docker image. 2 | # Anything not used by the build system should go here. 3 | Dockerfile 4 | .dockerignore 5 | .gitignore 6 | README.md 7 | 8 | # Artifacts that will be built during image creation. 9 | # This should contain all files created during `npm run build`. 10 | build 11 | node_modules 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | config.js 19 | package-lock.json 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | /localStorage 26 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "bracketSpacing": true, 4 | "singleQuote": true, 5 | "trailingComma": "none", 6 | "semi": true, 7 | "arrowParens": "avoid", 8 | "jsxBracketSameLine": true 9 | } 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # You should always specify a full version here to ensure all of your developers 2 | # are running the same version of Node. 3 | FROM node:7.8.0 4 | 5 | # The base node image sets a very verbose log level. 6 | ENV NPM_CONFIG_LOGLEVEL error 7 | ENV NPM_ENV production 8 | 9 | # Copy all local files into the image. 10 | COPY . . 11 | 12 | # Build for production. 13 | RUN set -x \ 14 | && npm install \ 15 | && npm run build \ 16 | && npm install -g serve 17 | 18 | CMD [ "serve", "-s", "build" ] 19 | 20 | # Tell Docker about the port we'll run on. 21 | EXPOSE 5000 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 - Present moltin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Moltin React Demo Store 4 | 5 |

6 | 7 | # Moltin — React Demo Store 8 | 9 | An example store built using [React](https://reactjs.org/), [Redux](https://redux.js.org/) and [moltin](https://moltin.com). This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). 10 | 11 | * [Demo](https://ilovelamp.now.sh) 12 | * [API Reference](https://docs.moltin.com) 13 | 14 | ## Development 15 | 16 | ```bash 17 | git clone https://github.com/moltin/react-demo-store.git 18 | cd react-demo-store 19 | yarn # or npm install 20 | yarn start # or npm start 21 | ``` 22 | 23 | Note: You will want to change the `client_id` inside `src/moltin.js` with your own moltin store credentials. 24 | 25 | This demo store uses the Redux "[ducks](https://github.com/erikras/ducks-modular-redux)" approach to bundling reducers and actions. 26 | 27 | ## Deployment 28 | 29 | ### Heroku 30 | 31 | [![Deploy](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy) 32 | 33 | ### Docker 34 | 35 | 1. [Download and install docker](https://docs.docker.com/engine/installation/) 36 | 2. Make sure docker is running locally 37 | 3. Run `docker build -t lamp .` at command line 38 | 4. Run the docker image with the command `docker run -p 5000 IMAGE_ID` where `IMAGE_ID` is the image ID shown in the result of step 3. 39 | 5. Access your app on port 5000 40 | 41 | ## Using this app with your own moltin store 42 | 43 | The app expects a certain inventory setup to correctly function as an ILoveLamp store, if you'd like to build it from the ground up, here's what to do: 44 | 45 | 1. [Create a collection](https://docs.moltin.com/collection#create-a-collection) with the slug `top_picks` 46 | 2. [Create at least one category](https://docs.moltin.com/collection#create-a-category) 47 | 3. [Create at least one product](https://docs.moltin.com/collection#create-a-product) 48 | 4. [Create at least one file i.e. an image for your product](https://docs.moltin.com/collection#create-a-file) 49 | 5. [Attach the product/s to the category and collection](https://docs.moltin.com/collection#create-category-relationship-s-) 50 | 6. [Attach the file to the product as a main image](https://docs.moltin.com/collection#create-file-relationship-s-) 51 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "React Demo Store", 3 | "description": "React demo store built with the moltin JavaScript SDK", 4 | "logo": "https://www.moltin.com/img/icons/favicons/apple-touch-icon.png", 5 | "repository": "https://github.com/moltin/react-demo-store", 6 | "keywords": ["moltin", "react", "ecommerce"], 7 | "buildpacks": [ 8 | { 9 | "url": "https://github.com/mars/create-react-app-buildpack.git" 10 | } 11 | ], 12 | "success_url": "/", 13 | "env": { 14 | "REACT_APP_MOLTIN_CLIENT_ID": { 15 | "description": 16 | "The client_id is the unique ID associated with your store. You'll find this on the welcome screen of your moltin Dashboard.", 17 | "value": "j6hSilXRQfxKohTndUuVrErLcSJWP15P347L6Im0M4" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app.yml: -------------------------------------------------------------------------------- 1 | name: Lamp 2 | image: ubuntu-14-04-x64 3 | min_size: 1gb 4 | config: 5 | #cloud-config 6 | users: 7 | - name: brick 8 | groups: sudo 9 | shell: /bin/bash 10 | sudo: ['ALL=(ALL) NOPASSWD:ALL'] 11 | packages: 12 | - git 13 | runcmd: 14 | - cd /home/brick && git clone git://github.com/matthew1809/ILoveLamp-React.git && cd ILoveLamp-React && npm install && bash build/ubuntu/14.04/provision.sh 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moltin-react-demo-store", 3 | "version": "0.2.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "react-scripts start", 7 | "build": "react-scripts build", 8 | "test": "react-scripts test --env=jsdom", 9 | "eject": "react-scripts eject", 10 | "precommit": "lint-staged" 11 | }, 12 | "dependencies": { 13 | "@moltin/sdk": "^3.18.1", 14 | "history": "^4.7.2", 15 | "react": "^16.2.0", 16 | "react-dom": "^16.2.0", 17 | "react-redux": "^5.0.5", 18 | "react-router": "^4.1.1", 19 | "react-router-dom": "^4.1.1", 20 | "react-router-redux": "next", 21 | "react-scripts": "1.1.0", 22 | "redux": "^3.7.1", 23 | "redux-form": "7.2.3", 24 | "redux-thunk": "2.2.0" 25 | }, 26 | "devDependencies": { 27 | "husky": "^0.14.3", 28 | "lint-staged": "^7.0.0", 29 | "prettier": "1.10.2", 30 | "redux-logger": "^3.0.6" 31 | }, 32 | "lint-staged": { 33 | "*.{js,json,css,md}": ["prettier --write", "git add"] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /public/img/favicons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/public/img/favicons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/favicons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/public/img/favicons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/public/img/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/img/favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #50505b 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/img/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/public/img/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /public/img/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/public/img/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /public/img/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/public/img/favicons/favicon.ico -------------------------------------------------------------------------------- /public/img/favicons/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ILoveLamp", 3 | "icons": [ 4 | { 5 | "src": "/img/favicons/android-chrome-192x192.png", 6 | "sizes": "192x192", 7 | "type": "image/png" 8 | }, 9 | { 10 | "src": "/img/favicons/android-chrome-512x512.png", 11 | "sizes": "512x512", 12 | "type": "image/png" 13 | } 14 | ], 15 | "theme_color": "#ff0000", 16 | "background_color": "#ff0000", 17 | "display": "standalone" 18 | } -------------------------------------------------------------------------------- /public/img/favicons/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/public/img/favicons/mstile-144x144.png -------------------------------------------------------------------------------- /public/img/favicons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/public/img/favicons/mstile-150x150.png -------------------------------------------------------------------------------- /public/img/favicons/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/public/img/favicons/mstile-310x150.png -------------------------------------------------------------------------------- /public/img/favicons/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/public/img/favicons/mstile-310x310.png -------------------------------------------------------------------------------- /public/img/favicons/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/public/img/favicons/mstile-70x70.png -------------------------------------------------------------------------------- /public/img/favicons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 31 | I Love Lamp 32 | 33 | 34 | 37 |
38 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "ILoveLamp", 3 | "name": "I Love Lamp store", 4 | "icons": [ 5 | { 6 | "src": "img/favicons/favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "img/favicons/android-chrome-192x192.png", 12 | "sizes": "192x192", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "img/favicons/android-chrome-512x512.png", 17 | "sizes": "512x512", 18 | "type": "image/png" 19 | } 20 | ], 21 | "start_url": "./index.html", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/assets/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/src/assets/header.png -------------------------------------------------------------------------------- /src/assets/img/bright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/src/assets/img/bright.png -------------------------------------------------------------------------------- /src/assets/img/footer/at-love.svg: -------------------------------------------------------------------------------- 1 | ILOVELAMP -------------------------------------------------------------------------------- /src/assets/img/footer/i-love.svg: -------------------------------------------------------------------------------- 1 | ILOVELAMP -------------------------------------------------------------------------------- /src/assets/img/footer/we-love.svg: -------------------------------------------------------------------------------- 1 | ILOVELAMP -------------------------------------------------------------------------------- /src/assets/img/headers/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/src/assets/img/headers/header.png -------------------------------------------------------------------------------- /src/assets/img/logo/ILOVELAMP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/src/assets/img/logo/ILOVELAMP.png -------------------------------------------------------------------------------- /src/assets/img/logo/ill-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /src/assets/img/logo/ill-short-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | ILOVELAMP-white-01 6 | 7 | 8 | 9 | 11 | 12 | 15 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/assets/img/logo/ill-short-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | ILOVELAMP-white-01 6 | 7 | 8 | 9 | 11 | 12 | 15 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/assets/img/logo/ill-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | ILOVELAMP-white-01 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | -------------------------------------------------------------------------------- /src/assets/img/modern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/src/assets/img/modern.png -------------------------------------------------------------------------------- /src/assets/img/products/lamp1-trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/src/assets/img/products/lamp1-trans.png -------------------------------------------------------------------------------- /src/assets/img/products/lamp10-trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/src/assets/img/products/lamp10-trans.png -------------------------------------------------------------------------------- /src/assets/img/products/lamp2-trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/src/assets/img/products/lamp2-trans.png -------------------------------------------------------------------------------- /src/assets/img/products/lamp3-trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/src/assets/img/products/lamp3-trans.png -------------------------------------------------------------------------------- /src/assets/img/products/lamp4-trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/src/assets/img/products/lamp4-trans.png -------------------------------------------------------------------------------- /src/assets/img/products/lamp5-trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/src/assets/img/products/lamp5-trans.png -------------------------------------------------------------------------------- /src/assets/img/products/lamp6-trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/src/assets/img/products/lamp6-trans.png -------------------------------------------------------------------------------- /src/assets/img/products/lamp7-trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/src/assets/img/products/lamp7-trans.png -------------------------------------------------------------------------------- /src/assets/img/products/lamp8-trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/src/assets/img/products/lamp8-trans.png -------------------------------------------------------------------------------- /src/assets/img/products/lamp9-trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/src/assets/img/products/lamp9-trans.png -------------------------------------------------------------------------------- /src/assets/img/silver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/src/assets/img/silver.png -------------------------------------------------------------------------------- /src/assets/img/style.min.css: -------------------------------------------------------------------------------- 1 | .select2-container--classic .select2-results>.select2-results__options,.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{position:relative}.select2-container[dir=rtl] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-search--inline{float:left}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;padding:0}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:#fff;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option[aria-selected]{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear,.select2-search--dropdown.select2-search--hide{display:none}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.behind .content,body{z-index:0}.select2-hidden-accessible{border:0!important;clip:rect(0 0 0 0)!important;height:1px!important;margin:-1px!important;overflow:hidden!important;padding:0!important;position:absolute!important;width:1px!important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:700}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent;border-style:solid;border-width:5px 4px 0;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir=rtl] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir=rtl] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888;border-width:0 4px 5px}.select2-container--default .select2-selection--multiple{background-color:#fff;border:1px solid #aaa;border-radius:4px;cursor:text}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--default .select2-selection--multiple .select2-selection__rendered li{list-style:none}.select2-container--default .select2-selection--multiple .select2-selection__placeholder{color:#999;margin-top:5px;float:left}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:700;margin-top:5px;margin-right:10px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:700;margin-right:2px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--default[dir=rtl] .select2-selection--multiple .select2-search--inline,.select2-container--default[dir=rtl] .select2-selection--multiple .select2-selection__choice,.select2-container--default[dir=rtl] .select2-selection--multiple .select2-selection__placeholder{float:right}.select2-container--default[dir=rtl] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir=rtl] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:1px solid #000;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple,.select2-container--default.select2-container--open.select2-container--above .select2-selection--single{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple,.select2-container--default.select2-container--open.select2-container--below .select2-selection--single{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:0 0;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results__option[role=group]{padding:0}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#5897fb;color:#fff}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top,#fff 50%,#eee 100%);background-image:-o-linear-gradient(top,#fff 50%,#eee 100%);background-image:linear-gradient(to bottom,#fff 50%,#eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:700;margin-right:10px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top,#eee 50%,#ccc 100%);background-image:-o-linear-gradient(top,#eee 50%,#ccc 100%);background-image:linear-gradient(to bottom,#eee 50%,#ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent;border-style:solid;border-width:5px 4px 0;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}body,html{height:100%}.select2-container--classic[dir=rtl] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir=rtl] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:4px 0 0 4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:0 0;border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888;border-width:0 4px 5px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top,#fff 0,#eee 50%);background-image:-o-linear-gradient(top,#fff 0,#eee 50%);background-image:linear-gradient(to bottom,#fff 0,#eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top,#eee 50%,#fff 100%);background-image:-o-linear-gradient(top,#eee 50%,#fff 100%);background-image:linear-gradient(to bottom,#eee 50%,#fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:#fff;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__rendered{list-style:none;margin:0;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:700;margin-right:2px}address,body{font-weight:400}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555}.select2-container--classic[dir=rtl] .select2-selection--multiple .select2-selection__choice{float:right;margin-left:5px;margin-right:auto}.select2-container--classic[dir=rtl] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results__option[role=group]{padding:0}.select2-container--classic .select2-results__option[aria-disabled=true]{color:grey}.select2-container--classic .select2-results__option--highlighted[aria-selected]{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb}.pushy ul,.select-country-restyle .select2-container--default .select2-dropdown ul,.select-restyle .select2-container--default .select2-dropdown ul{padding-left:0;position:static}.content,body{position:relative}.pushy ul li,.select-country-restyle .select2-container--default .select2-dropdown ul li,.select-restyle .select2-container--default .select2-dropdown ul li{padding-left:0}.pushy ul li::before,.select-country-restyle .select2-container--default .select2-dropdown ul li::before,.select-restyle .select2-container--default .select2-dropdown ul li::before{content:''}.cart .cart-delete,.cart .cart-delete .remove,.cart .cart-details,.cart .cart-list-headings .cart-header-group,.cart .total-price,.checkout .checkout-product,.checkout .checkout-product .product-image,.checkout .checkout-product .remove,.checkout .price-item,.checkout .total-price,.down-arrow,.logo,.nav-container,.primary-nav,.replace-checkbox,.secondary-nav,.select-country-restyle .select2-container .select2-selection .select2-selection__rendered,.select-country-restyle .select2-container--default .select2-dropdown .select2-results__option,.select-country-restyle .select2-container--default .select2-dropdown .select2-results__option[aria-selected=true],.select-country-restyle .select2-container--default .select2-dropdown .select2-results__option[aria-selected],.select-country-restyle .select2-container--default .select2-dropdown li,.select-restyle .select2-container .select2-selection .select2-selection__rendered,.select-restyle .select2-container--default .select2-dropdown .select2-results__option,.select-restyle .select2-container--default .select2-dropdown .select2-results__option[aria-selected=true],.select-restyle .select2-container--default .select2-dropdown .select2-results__option[aria-selected],.select-restyle .select2-container--default .select2-dropdown li,form .error,form .submit-error,input,select,textarea{align-items:center;display:flex;flex-direction:row}.cart .cart-listing.empty,.order-confirmation .confirmation{display:flex;flex-direction:column;justify-content:center}.btn,.header-container,.style-links .style-link,.styles-list .styles-item .fake-btn,.styles-list .styles-item h3,[type=submit],button{align-items:center;display:flex;justify-content:center}@keyframes shakeAnimation{12.5%{transform:translateX(-6px) rotateY(-5deg)}37.5%{transform:translateX(5px) rotateY(4deg)}62.5%{transform:translateX(-3px) rotateY(-2deg)}87.5%{transform:translateX(2px) rotateY(1deg)}100%{transform:translateX(0)}}@keyframes pulse{0%,100%,50%{color:#2d2d40;transform:scale(1)}25%,75%{color:#edadad;transform:scale(1.2)}}@keyframes bob{0%,100%,20%,50%,80%{transform:translateY(0) rotate(90deg)}40%{transform:translateY(-15px) rotate(90deg)}60%{transform:translateY(-5px) rotate(90deg)}}@keyframes colourChange{0%,25%{color:#50505b}100%{color:#fff}}@keyframes cartBackgroundChange{0%,25%{background-color:#50505b;color:#fff}100%{background-color:#fff;color:#50505b}}@keyframes backgroundChange{0%,25%{background-color:#fff}100%{background-color:#2d2d40}}@keyframes colourAndBackgroundChange{0%,25%{background-color:#fff;color:#50505b}100%{background-color:#2d2d40;color:#fff}}@keyframes lineChange{0%,25%{border-color:#e7e7e7}100%{border-color:#50505b}}@keyframes fadeColour{0%{opacity:1}50%{opacity:0}}@keyframes fadeIn{0%,50%{opacity:0}100%{opacity:1}}html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}body{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}address{font-style:normal}figure{margin:0}img{height:auto;max-width:100%;margin-bottom:1em;margin-top:1em}blockquote>:last-child,section{margin-bottom:0}fieldset,form{margin:0;padding:0}a img,fieldset,iframe,img{border:0}legend{padding:0}button{background-image:none;cursor:pointer}a:focus,input:focus,textarea:focus{outline:0}::-moz-focus-inner{border:0}[type=submit]{cursor:pointer;-webkit-appearance:none}[type=search]{box-shadow:none;-webkit-appearance:none}textarea{overflow-y:auto;resize:none}main{display:block}.content{margin-left:auto;margin-right:auto;max-width:1230px;padding-left:1.875rem;padding-right:1.875rem}body,dl dd{margin:0}[role=contentinfo] section{margin-bottom:0;margin-top:0}::selection{background:rgba(0,0,0,.25);color:#fff}section{background:#fff;margin-top:4.625rem}.hidden{display:none}@media (max-width:34.8125em){[role=main]{margin-bottom:0}[role=contentinfo]{position:relative}}.small,footer dd,footer p,form .error,form .submit-error{font-size:.875rem;line-height:1.625rem}.product h3,body{font-size:1rem;line-height:1.625rem}.cart .cart-item h3,.cart .cart-price .price,.checkout .price-item,.checkout h2,.loading p,.mailing-list p,.primary-nav a,.product-list .product-item .title,.product-listing .description,.product-listing .manufacturer,.secondary-nav a,.select-country-restyle,.select-country-restyle::before,.select-restyle,.select-restyle::before,header:not(.large-header):not(.medium-header):not(.broken) h1,input,label,select,textarea{font-size:1.125rem;line-height:1.75rem}.cart .total-price{font-size:1.5rem;line-height:2.125rem}.cart .cart-listing.empty p,.price{font-size:1.75rem;line-height:2.5rem}.select-country-restyle,.select-country-restyle .select2-container--default .select2-selection--single .select2-selection__placeholder,.select-restyle,.select-restyle .select2-container--default .select2-selection--single .select2-selection__placeholder,body,select{color:#50505b}.price,body{font-family:Arial,Arial,sans-serif}.cart .cart-item h3,.checkout h2,.loading p,.product h2,.product h3,.product-list .product-item .title,.product-listing .manufacturer,.styles-list .styles-item h3,.word-mark,footer dd,header:not(.large-header) h1{text-transform:uppercase}.cart .cart-item h3,.loading p,.product h2,.product-list .product-item .title,.product-listing .manufacturer,header:not(.large-header):not(.medium-header):not(.broken) h1{letter-spacing:.2rem}a:link,a:visited{color:#2d2d40}a:active,a:focus,a:hover{color:#edadad;text-decoration:none}.broken-body footer a:link,.broken-body footer a:visited,.logo.light .logo-link:link,.logo.light .logo-link:visited,.primary-nav.light a:link,.primary-nav.light a:visited,.secondary-nav.light a:link,.secondary-nav.light a:visited{color:#fff}.broken-body footer a:focus,.logo.light .logo-link:focus,.primary-nav.light a:focus,.secondary-nav.light a:focus{color:rgba(255,255,255,.9);outline:0;text-decoration:underline}.broken-body footer a:active,.broken-body footer a:hover,.logo.light .logo-link:active,.logo.light .logo-link:hover,.primary-nav.light a:active,.primary-nav.light a:hover,.secondary-nav.light a:active,.secondary-nav.light a:hover{color:rgba(255,255,255,.75)}.logo .logo-link:link,.logo .logo-link:visited,nav a:link,nav a:visited{color:#50505b}.logo .logo-link:focus,nav a:focus{color:#9f9fae;outline:0;text-decoration:underline}.logo .logo-link:active,.logo .logo-link:hover,nav a:active,nav a:hover{color:#9f9fae}body{-webkit-overflow-scrolling:touch;overflow-x:hidden;-webkit-font-smoothing:antialiased;line-height:1.875}p{max-width:33.75rem}.hide-content{clip:rect(1px,1px,1px,1px);clip-path:polygon(0 0,0 0,0 0,0 0);height:1px;margin:0;overflow:hidden;padding:0;position:absolute;width:1px}.love{font-weight:500}.price{font-weight:600}blockquote{background-color:#f9f9f9;border-left:.5em solid #2d2d40;font-size:ms(0);margin-left:1em;margin-right:1em;padding:1em 1.5em}blockquote>:first-child{margin-top:0}ol,ul{list-style-position:inside;list-style-type:none;padding-left:1em}ol li,ul li{padding-left:2em;position:relative}ol li::before,ul li::before{color:#2d2d40;content:'\25CF';font-size:.875em;left:0;line-height:.5;position:absolute;top:.8125em}ol{counter-reset:item}ol>li{counter-increment:item;display:table}ol>li::before{content:counter(item) ". ";display:table-cell;font-weight:200}ol ol>li::before{content:counters(item,".") " "}ol ul>li::before{content:'\25CF'}h1,h2,li a,p a{white-space:pre;white-space:pre-wrap;white-space:pre-line;white-space:-pre-wrap;white-space:-o-pre-wrap;white-space:-moz-pre-wrap;white-space:-hp-pre-wrap;word-wrap:break-word}.broken-body footer a,.btn,.cart .cart-list-headings,.cart .cart-listing.empty p,.down-arrow,.logo .logo-link,.logo.light .logo-link,.mailing-list h2,.order-confirmation h2,.primary-nav.light a,.product h2,.product-list .product-item .title,.product-list .product-item.new::after,.product-list .product-item.sale::after,.pushy li,.secondary-nav.light a,.select-country-restyle,.select-country-restyle::before,.select-restyle,.select-restyle::before,.style-links .style-link,.styles-list .styles-item .fake-btn,.styles-list .styles-item h3,.top-picks h2,.word-mark,[type=submit],button,footer dd,footer p,form .error,form .submit-error,h1,h2,h3,h4,h5,h6,header.medium-header h1,input,nav a,select,textarea{font-family:Montserrat,sans-serif}.btn,.down-arrow,.mailing-list h2,.order-confirmation h2,.product h2,.product-list .product-item.new::after,.product-list .product-item.sale::after,.pushy li,.style-links .style-link,.styles-list .styles-item .fake-btn,.styles-list .styles-item h3,.top-picks h2,[type=submit],button,h1,h2,h3,h4,h5,h6,header.medium-header h1{font-weight:200;line-height:.9;margin-bottom:.25em;margin-top:.25em}h1,h2,h3,h4,h5,h6{color:#2d2d40}.styles-list .styles-item h3,h1,h2,header.medium-header h1{font-size:4rem;line-height:4.625rem}@media (min-width:52.875em){h1{font-size:5.875rem;line-height:6.5rem}.styles-list .styles-item h3,h2,header.medium-header h1{font-size:4.625rem;line-height:5.25rem}}h3{font-size:3.75rem;line-height:4.375rem}.down-arrow,.mailing-list h2,.product h2,.top-picks h2,h4{font-size:3rem;line-height:3.625rem}.order-confirmation h2,h5{font-size:2.25rem;line-height:3rem}.btn,.product-list .product-item.new::after,.product-list .product-item.sale::after,.pushy li,.style-links .style-link,.styles-list .styles-item .fake-btn,[type=submit],button,h6{font-size:1.5rem;font-weight:300;line-height:2.125rem}.checkout fieldset button,.style-links .style-link,.styles-list .styles-item .fake-btn{max-width:12.5rem;min-height:3.75rem}.broken-body .broken-container .btn,.cart .cart-listing.empty .btn,.cart .submit,.mailing-list .submit,.product-listing [type=submit]{max-width:15rem;min-height:5rem}.header-container .btn{max-width:18.75rem;min-height:5rem}@media (min-width:28.125em){.checkout fieldset button,.style-links .style-link,.styles-list .styles-item .fake-btn{width:12.5rem}.broken-body .broken-container .btn,.cart .cart-listing.empty .btn,.cart .submit,.mailing-list .submit,.product-listing [type=submit]{width:15rem}.header-container .btn{width:18.75rem}}.header-container .btn:link,.header-container .btn:visited{background:#ffefef;border:0;color:#2d2d40}.header-container .btn:active,.header-container .btn:focus,.header-container .btn:hover{background:#2d2d40;border:0;color:#ffefef}.cart .cart-listing.empty .btn:link,.cart .cart-listing.empty .btn:visited{background:#d7f0ea;border:0;color:#2d2d40}.cart .cart-listing.empty .btn:active,.cart .cart-listing.empty .btn:focus,.cart .cart-listing.empty .btn:hover{background:#2d2d40;border:0;color:#d7f0ea}.cart .submit,.cart .submit:link,.cart .submit:visited,.product-listing [type=submit],.product-listing [type=submit]:link,.product-listing [type=submit]:visited{background:#2d2d40;border-color:#2d2d40;color:#fff}.cart .submit:focus,.product-listing [type=submit]:focus{background:#f0f0f0;border-color:#2d2d40;color:#2d2d40}.cart .submit:active,.cart .submit:hover,.product-listing [type=submit]:active,.product-listing [type=submit]:hover{background:0 0;border-color:#2d2d40;color:#2d2d40}.btn,[type=submit],button{background:#9f9fae;border:3px solid #9f9fae;color:#fff}.btn:link,.btn:visited,[type=submit]:link,[type=submit]:visited,button:link,button:visited{background:#9f9fae;border-color:#9f9fae;color:#fff}.btn:active,.btn:focus,.btn:hover,[type=submit]:active,[type=submit]:focus,[type=submit]:hover,button:active,button:focus,button:hover{background:0 0;color:#9f9fae}.style-links .style-link,.style-links .style-link:link,.style-links .style-link:visited{background:0 0;border-color:#9f9fae;color:#9f9fae}.style-links .style-link:active,.style-links .style-link:focus,.style-links .style-link:hover{background:0 0;border-color:#2d2d40;color:#2d2d40}.broken-body .broken-container .btn,.broken-body .broken-container .btn:link,.broken-body .broken-container .btn:visited{background:0 0;border-color:#fff;color:#fff}.broken-body .broken-container .btn:active,.broken-body .broken-container .btn:focus,.broken-body .broken-container .btn:hover{background:0 0;border-color:#edadad;color:#edadad}.btn,.style-links .style-link,.styles-list .styles-item .fake-btn,[type=submit],button{border-style:solid;border-width:3px;font-weight:500;padding:.9375rem 1.875rem;text-align:center;text-decoration:none}.btn::after,.btn::before,.style-links .style-link::after,.style-links .style-link::before,.styles-list .styles-item .fake-btn::after,.styles-list .styles-item .fake-btn::before,[type=submit]::after,[type=submit]::before,button::after,button::before{content:'';flex:1 0 auto}form .error,form .submit-error,input,select,textarea{border:2px solid #e7e7e7;border-radius:0;font-weight:300;line-height:1.5;margin-top:1.25rem;min-height:3.75rem;padding:.6875rem .9375rem}form{margin-bottom:1em;margin-top:1em;max-width:33.75rem;padding:1em}form .error,form .submit-error{background:#ffefef;border-width:0;min-height:3.75rem;padding:.6875rem .9375rem;width:100%}form .error:first-child,form .submit-error:first-child{margin-top:0}input,label,textarea{display:block;font-weight:400}input,select,textarea{-webkit-appearance:none;border-color:#e7e7e7;color:#50505b;display:block;position:relative;width:100%}input.invalid,select.invalid,textarea.invalid{border-color:#edadad}input:active,input:focus,select:active,select:focus,textarea:active,textarea:focus{background:#fff;border-color:#9f9fae}input:-webkit-autofill,input:-webkit-autofill:active,input:-webkit-autofill:focus,input:-webkit-autofill:hover,select:-webkit-autofill,select:-webkit-autofill:active,select:-webkit-autofill:focus,select:-webkit-autofill:hover,textarea:-webkit-autofill,textarea:-webkit-autofill:active,textarea:-webkit-autofill:focus,textarea:-webkit-autofill:hover{background:#fff;box-shadow:0 0 0 50px #fff inset}input,select{height:3.75rem}input::-moz-focus-inner{border:0;padding:0}.input-wrap{border-radius:0;margin-top:1.25rem}.input-wrap.invalid input,.input-wrap.invalid select,.input-wrap.invalid textarea{border-color:#edadad;border-radius:0}.input-wrap.invalid input:active,.input-wrap.invalid input:focus,.input-wrap.invalid select:active,.input-wrap.invalid select:focus,.input-wrap.invalid textarea:active,.input-wrap.invalid textarea:focus{background:#ffefef;border-color:#9f9fae}.input-wrap input,.input-wrap select,.input-wrap textarea{margin-top:0}textarea{height:10em}select{background:#fff;box-shadow:0;font-weight:400;padding-left:.9375rem;padding-right:.9375rem}.loading p,header:not(.large-header):not(.medium-header):not(.broken) h1{font-weight:600}select>option{background-color:#fff;box-shadow:none;padding:.3125rem .9375rem}.quantity-input{display:flex;min-width:9.0625rem}.quantity-input .quantity{-moz-appearance:textfield;background:#f0f0f0;border:3px solid #f0f0f0;margin:0;max-height:2.5rem;max-width:4rem;min-height:0;padding:.25rem;text-align:center}.quantity-input .quantity:focus{background:#fff;border-color:#9f9fae}.quantity-input .quantity::-webkit-inner-spin-button,.quantity-input .quantity::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.quantity-input .number-button{font-size:1.875rem;font-weight:600;margin:0;max-height:2.5rem;padding:0;width:2.5rem}.replace-checkbox{margin-top:1.25rem;width:100%}.replace-checkbox [type=checkbox]{-moz-appearance:none;-webkit-appearance:none;appearance:none;border:2px solid #e7e7e7;flex:0 0 1.875rem;height:1.875rem;margin:0 .9375rem 0 0;min-height:1.875rem;padding:0;position:relative;width:1.875rem}.replace-checkbox [type=checkbox]:focus{background:#f0f0f0;border-color:#9f9fae}.replace-checkbox [type=checkbox]:checked{background:#9f9fae;border-color:#9f9fae}.replace-checkbox [type=checkbox]:checked:focus{border-color:#2d2d40}.replace-checkbox [type=checkbox]:checked::after{border:.25rem solid #fff;border-width:0 0 .25rem .25rem;content:'';display:block;height:.75rem;left:.25rem;position:absolute;top:.25rem;transform:rotate(-45deg);width:1.25rem}button:focus{outline:0}button.disabled,button:disabled{cursor:default}.invalid-form{animation:shakeAnimation .4s ease-in-out .1s normal forwards 1 running}.select-country-restyle,.select-restyle{height:3.75rem;position:relative;text-align:left;width:100%}.header-container .content,.loading,.logo,.mailing-list .mailing-list-content,.pushy,.pushy ul,.styles-list .styles-item h3,.top-picks{text-align:center}.select-country-restyle::before,.select-restyle::before{color:#9f9fae;content:'\276f';display:block;pointer-events:none;position:absolute;right:1rem;top:1.125rem;transform:rotate(90deg);z-index:1}.select-country-restyle select,.select-restyle select{-moz-appearance:none;-ms-appearance:none;-o-appearance:none;-webkit-appearance:none;appearance:none}.select-country-restyle .select2-container,.select-restyle .select2-container{height:inherit;vertical-align:inherit}.select-country-restyle .select2-container .selection,.select-restyle .select2-container .selection{height:inherit}.select-country-restyle .select2-container .select2-selection,.select-restyle .select2-container .select2-selection{border:2px solid #e7e7e7;border-radius:0;display:block;height:inherit}.select-country-restyle .select2-container .select2-selection .select2-selection__rendered,.select-restyle .select2-container .select2-selection .select2-selection__rendered{color:inherit;height:inherit;padding:.6875rem .9375rem}.select-country-restyle .select2-container .select2-selection .select2-selection__arrow b,.select-restyle .select2-container .select2-selection .select2-selection__arrow b{display:none}.select-country-restyle .select2-container .select2-selection.invalid,.select-restyle .select2-container .select2-selection.invalid{border-color:#edadad}.select-country-restyle .select2-container--default,.select-restyle .select2-container--default{color:inherit}.select-country-restyle .select2-container--default.select2-container--focus .select2-selection--single,.select-country-restyle .select2-container--default:hover .select2-selection--single,.select-restyle .select2-container--default.select2-container--focus .select2-selection--single,.select-restyle .select2-container--default:hover .select2-selection--single{background:#fff;border-color:#9f9fae;border-radius:0;outline:0}.select-country-restyle .select2-container--default .select2-dropdown,.select-restyle .select2-container--default .select2-dropdown{border:2px solid #9f9fae;border-radius:0;overflow:hidden}.select-country-restyle .select2-container--default .select2-dropdown .select2-search--dropdown,.select-restyle .select2-container--default .select2-dropdown .select2-search--dropdown{padding:.6875rem}.select-country-restyle .select2-container--default .select2-dropdown input,.select-restyle .select2-container--default .select2-dropdown input{margin:0;padding:.6875rem .46875rem}.select-country-restyle .select2-container--default .select2-dropdown .select2-results__option,.select-country-restyle .select2-container--default .select2-dropdown .select2-results__option[aria-selected=true],.select-country-restyle .select2-container--default .select2-dropdown .select2-results__option[aria-selected],.select-country-restyle .select2-container--default .select2-dropdown li,.select-restyle .select2-container--default .select2-dropdown .select2-results__option,.select-restyle .select2-container--default .select2-dropdown .select2-results__option[aria-selected=true],.select-restyle .select2-container--default .select2-dropdown .select2-results__option[aria-selected],.select-restyle .select2-container--default .select2-dropdown li{background-color:transparent;color:inherit;height:3.75rem;line-height:normal;padding:.6875rem .9375rem}.select-country-restyle .select2-container--default .select2-dropdown .select2-results__option[aria-disabled=true],.select-restyle .select2-container--default .select2-dropdown .select2-results__option[aria-disabled=true]{color:#9f9fae}.select-country-restyle .select2-container--default .select2-dropdown .select2-results__option.select2-results__option--highlighted,.select-country-restyle .select2-container--default .select2-dropdown .select2-results__option[aria-selected]:hover,.select-restyle .select2-container--default .select2-dropdown .select2-results__option.select2-results__option--highlighted,.select-restyle .select2-container--default .select2-dropdown .select2-results__option[aria-selected]:hover{background:#9f9fae;color:#fff}.select-country-restyle.input-wrap .select2-container--default .select2-dropdown,.select-restyle.input-wrap .select2-container--default .select2-dropdown{margin-left:-4px;margin-top:-4px}.loading{padding:1.875rem}.loading p{margin-left:auto;margin-right:auto}.loading .loading-icon{animation:pulse 2s ease-in-out infinite;color:#2d2d40;margin:0 auto;width:5.75rem}.header-container.light h1,.primary-nav.light,.secondary-nav.light{color:#fff}header{position:relative}footer dd,footer p,header.medium-header h1{font-weight:300}header.large-header,header.medium-header{display:flex;flex-direction:column}header.large-header{height:50rem}header.medium-header{height:31.25rem}.header-container{flex-grow:1;padding-bottom:5rem}.header-container .content{max-width:none}.header-container.smaller{padding-bottom:0;padding-top:5rem}.header-container .btn{box-shadow:0 30px 30px -15px rgba(0,0,0,.5);display:block;margin:3.75rem auto;padding-top:1.5rem}.down-arrow{bottom:0;color:#fff;left:0;position:absolute;right:0}.down-arrow .arrow{animation:bob 3s ease-in-out infinite;display:block;margin:0 auto;transform:rotate(90deg)}footer{margin-top:4.625rem}footer .footer-content{border-top:5px solid #f0f0f0;display:flex;flex-direction:column;padding-bottom:4.625rem;padding-top:4.625rem}footer .footer-header{margin-top:0}footer .footer-header svg{height:1.125rem;margin-top:0;width:auto}@media (max-width:28.0625em){footer .footer-header svg{max-width:100%}footer .footer-about,footer .footer-links,footer .footer-social{margin:1.875rem 0 0}}@media (max-width:52.8125em) and (min-width:28.125em){footer .footer-about,footer .footer-links,footer .footer-social{margin:1.875rem auto 0;min-width:17.3125rem}}@media (min-width:52.875em){footer .footer-content{align-items:flex-start;display:flex;flex-direction:row}footer .footer-about,footer .footer-links,footer .footer-social{float:left;margin:0 1.875rem 0 0;width:calc((100% - 3.75rem)/ 3)}footer .footer-social{margin-right:0}}footer p{margin:1rem 0;max-width:13rem}footer dl{margin-top:0}footer dt{margin-bottom:1rem}footer dd{margin:0 0 .5rem}nav a{text-decoration:none}.nav-container{padding:1.875rem;position:relative}.logo,.menu-btn,.menu-btn.active{position:absolute}.primary-nav,.secondary-nav{margin-left:auto;margin-right:auto;max-width:none;width:auto;z-index:1}.primary-nav a,.secondary-nav a{margin-right:1.875rem}.primary-nav a:last-child,.secondary-nav,.secondary-nav a:last-child{margin-right:0}.primary-nav{margin-left:0}@media (max-width:52.8125em){.primary-nav{display:none}.secondary-nav .cart{border-radius:50%;height:2.5rem;margin-top:-.1875rem;padding:.4rem;text-align:center;width:2.5rem}.secondary-nav .cart:link,.secondary-nav .cart:visited{background:#50505b;color:#fff}.secondary-nav .cart:active,.secondary-nav .cart:focus,.secondary-nav .cart:hover{background:#9f9fae;color:#fff}.secondary-nav.light .cart:link,.secondary-nav.light .cart:visited{background:#fff;color:#50505b}.secondary-nav.light .cart:focus{background:#f0f0f0;color:#50505b}.secondary-nav.light .cart:active,.secondary-nav.light .cart:hover{background:rgba(255,255,255,.75);color:#50505b}.secondary-nav .cart-name{clip:rect(1px,1px,1px,1px);clip-path:polygon(0 0,0 0,0 0,0 0);height:1px;margin:0;overflow:hidden;padding:0;position:absolute;width:1px}}.logo{left:0;margin:0 auto;padding:1.875rem;right:0;top:0}.logo .logo-link{display:inline-block;height:2rem;line-height:0;margin:0 auto}.logo .logo-link:link,.logo .logo-link:visited{opacity:1}.logo .logo-link:focus{color:#50505b;opacity:.8}.logo .logo-link:active,.logo .logo-link:hover{color:#50505b;opacity:.65}@media (max-width:28.0625em){.logo .logo-link{margin:0 4.5rem}}.logo.light .logo-link:active,.logo.light .logo-link:hover,.logo.light .logo-link:link,.logo.light .logo-link:visited{opacity:1}.logo svg{height:2rem;margin:0 auto}@media (max-width:28.125em){.logo .logo-link{align-items:center;display:flex}.logo svg{height:auto;width:100%}}@media (max-width:56.25rem){.logo .big-logo{display:none}}@media (min-width:56.25rem){.logo .small-logo{display:none}}.menu-btn{background-color:transparent;border:0;color:#2d2d40;display:block;float:left;height:1.875rem;left:1.875rem;margin:0;min-height:1.875rem;outline:0;padding:0;-webkit-tap-highlight-color:transparent;top:1.875rem;-webkit-touch-callout:none;user-select:none;width:1.875rem;z-index:10000}.menu-btn:not(.disabled):not(:disabled):active,.menu-btn:not(.disabled):not(:disabled):focus,.menu-btn:not(.disabled):not(:disabled):hover{opacity:.75}.menu-btn::after,.menu-btn::before{display:none}.menu-btn rect{transition:all .5s ease}.menu-btn.light{color:#fff}.menu-btn.active{color:#2d2d40}.menu-btn.active .top{transform:rotate(45deg) translate(4px,-12px)}.menu-btn.active .middle{opacity:0}.menu-btn.active .bottom{transform:rotate(-45deg) translate(-11px,-3px)}@media (min-width:52.875em){.menu-btn{display:none}}.pushy{background:#ffefef;color:#2d2d40;height:100%;min-height:100vh;-webkit-overflow-scrolling:touch;overflow-x:auto;overflow-y:scroll;padding:3.75rem 2.8125rem;position:fixed;top:0;width:250px;z-index:9999}.pushy ul{margin-bottom:auto}.pushy ul:first-child{margin-top:4rem}.pushy li{display:block;margin:1.875rem 0;width:100%}.pushy.pushy-left{left:0}.pushy.pushy-right{right:0}.pushy-left{transform:translate3d(-250px,0,0)}.pushy-open-left .main-container,.pushy-open-left .push,.pushy-right{transform:translate3d(250px,0,0)}.pushy-open-right .main-container,.pushy-open-right .push{transform:translate3d(-250px,0,0)}.pushy-open-left .pushy,.pushy-open-right .pushy{transform:translate3d(0,0,0)}.main-container,.push,.pushy{transition:transform .2s cubic-bezier(.16,.68,.43,.99)}.site-overlay{display:none}.pushy-open-left .site-overlay,.pushy-open-right .site-overlay{animation:fade .5s;background:0 0;background-color:rgba(0,0,0,.5);bottom:0;display:block;left:0;opacity:1;position:fixed;right:0;top:0;z-index:9998}@keyframes fade{0%{opacity:0}100%{opacity:1}}.no-csstransforms3d .pushy-submenu-closed ul{display:none;max-height:none}.mailing-list .mailing-list-content{background:#f9f9f9;padding:4.625rem 1.875rem}.mailing-list p{margin-left:auto;margin-right:auto;max-width:29rem}.mailing-list .newsletter-form{display:flex;margin:.9375rem auto 0;max-width:48.125rem}@media (max-width:52.8125em){.mailing-list .newsletter-form{flex-direction:column}}.mailing-list input{border:3px solid #fff;flex-grow:1;margin:0;min-height:5rem;padding:.9375rem 1.875rem;width:inherit}.mailing-list input:focus{border-color:#9f9fae}.mailing-list .submit{margin:1.875rem auto 0}@media (min-width:52.875em){.mailing-list .submit{flex:0 0 15rem;margin:0}}.style-links{margin:1.875rem auto -2.75rem}.style-links .style-links-container{align-items:center;display:flex}@media (max-width:52.8125em){.style-links .style-links-container{flex-wrap:wrap}}@media (min-width:28.125em){.style-links .style-link{margin:.9375rem 1.875rem .9375rem 0;max-width:none;width:calc((100% - (1.875rem * 3)/ 4))}.style-links .style-link:last-child{margin-right:0}}@media (max-width:52.8125em) and (min-width:28.125em){.style-links .style-link{width:calc((100% - 1.875rem)/ 2)}.style-links .style-link:nth-child(even){margin-right:0}}@media (max-width:28.0625em){.style-links .style-links-container{flex-direction:column}.style-links .style-link{margin:.9375rem auto;width:100%}.styles-list .styles-item h3{font-size:3rem;line-height:3.625rem}}.styles .content{max-width:none;padding:0}.styles-list{display:flex}@media (max-width:74rem){.styles-list{flex-wrap:wrap}}.styles-list .styles-item{position:relative}.styles-list .styles-item:active,.styles-list .styles-item:focus,.styles-list .styles-item:hover,.styles-list .styles-item:link,.styles-list .styles-item:visited{background:0 0;color:#fff;text-decoration:none}@media (min-width:28.125em){.styles-list .styles-item{width:50%}}@media (min-width:74rem){.styles-list .styles-item{width:25%}}.styles-list .styles-item .overlay{display:block}.styles-list .styles-item:link h3,.styles-list .styles-item:visited h3{opacity:1;z-index:10}.styles-list .styles-item:link img,.styles-list .styles-item:visited img{z-index:0}@media (min-width:52.875em){.styles-list .styles-item .overlay{display:none}.styles-list .styles-item:link h3,.styles-list .styles-item:visited h3{opacity:.5;z-index:0}.styles-list .styles-item:link img,.styles-list .styles-item:visited img{z-index:10}}.styles-list .styles-item:active::before,.styles-list .styles-item:focus::before,.styles-list .styles-item:hover::before{background:rgba(0,0,0,.2);bottom:0;content:'';display:block;left:0;position:absolute;right:0;top:0;z-index:1}.styles-list .styles-item:active h3,.styles-list .styles-item:focus h3,.styles-list .styles-item:hover h3{opacity:1;z-index:10}.styles-list .styles-item:active img,.styles-list .styles-item:focus img,.styles-list .styles-item:hover img{z-index:0}.styles-list .styles-item:active .overlay,.styles-list .styles-item:focus .overlay,.styles-list .styles-item:hover .overlay{display:block;z-index:20}.styles-list .styles-item h3{bottom:0;color:#fff;font-weight:300;left:0;position:absolute;right:0;top:0}@media (min-width:28.125em) and (max-width:34.8125em){.styles-list .styles-item h3{font-size:1.875rem;font-weight:400;line-height:2.5rem}}@media (min-width:34.875em) and (max-width:52.8125em){.styles-list .styles-item h3{font-size:3.125rem;line-height:3.75rem}}@media (min-width:74rem) and (max-width:102rem){.styles-list .styles-item h3{font-size:3.5rem;line-height:4.125rem}}.styles-list .styles-item img{display:block;height:auto;margin:auto;position:relative;width:100%}.styles-list .styles-item .fake-btn{border:0;bottom:0;color:#fff;font-weight:400;left:0;margin:0 auto;position:absolute;right:0;z-index:20}.product-list{display:flex;flex-wrap:wrap;margin-top:3.75rem}.product-list .product-item{display:flex;flex-direction:column;margin:0 auto 1.875rem;overflow:hidden;position:relative}.product-list .product-item:active,.product-list .product-item:focus,.product-list .product-item:hover,.product-list .product-item:link,.product-list .product-item:visited{background:0 0;color:#fff;text-decoration:none}@media (min-width:34.875em){.product-list .product-item{margin:0 1.875rem 1.875rem 0}}@media (min-width:34.875em) and (max-width:52.8125em){.product-list .product-item{float:left;width:calc((100% - 1.875rem)/ 2)}.product-list .product-item:nth-of-type(2n+2){margin-right:0}}@media (min-width:52.875em){.product-list .product-item{float:left;width:calc((100% - 3.75rem)/ 3)}.product-list .product-item:nth-of-type(3n+3){margin-right:0}.product-list .product-item:link .overlay,.product-list .product-item:visited .overlay{display:none}}.product-list .product-item:active .overlay,.product-list .product-item:focus .overlay,.product-list .product-item:hover .overlay{display:block}@media (max-width:52.8125em){.product-list .product-item:active .overlay-background,.product-list .product-item:focus .overlay-background,.product-list .product-item:hover .overlay-background{opacity:1}}.product-list .product-item.new::after,.product-list .product-item.sale::after{display:block;font-weight:600;margin:0;padding:.5rem 1.5rem;position:absolute;right:-2.75rem;text-align:center;top:.625rem;transform:rotate(45deg);width:10rem}.product-list .product-item.new::after{background:#adedc4;content:'New'}.product-list .product-item.sale::after{background:#edadad;content:'Sale'}.product-list .product-item img{display:block;height:auto;margin:auto;width:100%}.product-list .product-item .overlay-background{bottom:0;left:0;opacity:.6;padding:1.875rem;position:absolute;right:0;top:0}.product-list .product-item .overlay-content{bottom:0;color:#fff;left:0;padding:.9375rem 1.875rem;position:relative;text-align:left}.product-list .product-item .overlay{flex-grow:1;position:relative}.product h2{margin:1.875rem 0 .3125rem}.product h3{font-weight:600;margin:0}.product-listing{display:flex;flex-direction:row;margin-bottom:4.625rem}@media (max-width:52.8125em){.product-listing{flex-direction:column}}.product-listing .product-image{display:block;margin:2.3125rem auto 0;width:100%}@media (min-width:52.875em){.product-list .product-item .overlay{bottom:0;left:0;position:absolute;right:0;top:0;z-index:10}.product-list .product-item .overlay-content{position:absolute}.product-listing .product-image{margin:0;width:60%}}.product-listing .product-image img{display:block;height:auto;margin:0;width:100%}@media (max-width:52.8125em) and (min-width:34.875em){.product-listing .product-image img{margin:0 auto;width:75%}}.product-listing .product-description{margin:1.875rem auto 0;width:100%}@media (max-width:52.8125em) and (min-width:34.875em){.product-listing .product-description{width:75%}}@media (min-width:52.875em){.product-listing .product-description{margin:0 0 0 4.625rem;width:auto}}.product-listing .manufacturer{color:rgba(45,45,64,.5);margin-top:0}.product-listing form{margin-top:3.75rem;padding:0}.product-listing [type=submit]{margin:1.875rem auto 0 0}.product-info{display:flex;flex-direction:row}@media (max-width:67.4375em){.product-info{flex-direction:column}}.product-info .product-details{border:2px solid #f0f0f0;display:flex;flex-direction:column;flex-grow:1;margin:0 auto 1.875rem}@media (max-width:34.8125em){.product-info .product-details{width:100%}}@media (max-width:52.8125em) and (min-width:34.875em){.product-info .product-details{width:75%}}@media (max-width:67.4375em) and (min-width:52.875em){.product-info .product-details{width:50%}}@media (min-width:67.5em){.product-info .product-details{margin:0 1.875rem 1.875rem 0;width:calc((100% - 3.75rem)/ 3)}.product-info .label{max-width:45%}}.product-info .header{background:#f0f0f0;padding:1.40625rem 1.875rem}@media (max-width:28.0625em){.product-info .header{padding:.9375rem}}.product-info .details-body{display:flex;flex-direction:column;flex-grow:1;padding:.9375rem 1.875rem}@media (max-width:28.0625em){.product-info .details-body{padding:.9375rem}}.product-info .footer{padding:1.40625rem 1.875rem 1.875rem}@media (max-width:28.0625em){.product-info .footer{padding:.9375rem}}.product-info .footer p{margin:0}.product-info .row{display:flex;flex-direction:row;padding:.3125rem 0}.product-info .label{margin-right:1.875rem;width:7.875rem}@media (max-width:28.0625em){.product-info .label{width:4.75rem}}.product-info .sku{word-break:break-all}.cart{margin-top:0}.cart .cart-listing{max-width:none;padding:0}.cart .cart-listing.empty{border-bottom:5px solid #f0f0f0;border-top:5px solid #f0f0f0;margin-top:6.5rem;padding:1.875rem}.cart .cart-listing.empty .btn{margin:3.75rem auto 1.875rem}.cart .cart-listing.empty p{font-weight:500;margin:1.875rem auto 0;text-align:center}.cart .cart-list-headings{border-bottom:5px solid #f0f0f0;color:#9f9fae;display:flex;flex-direction:row;padding:1.875rem}.cart .cart-list-headings .cart-product-header{padding-right:1.875rem;width:10.625rem}.cart .cart-list-headings .cart-header-group{flex:1}.cart .cart-list-headings .cart-empty-header,.cart .cart-list-headings .cart-price-header,.cart .cart-list-headings .cart-quantity-header{padding-right:1.875rem;width:33.33%}@media (max-width:52.8125em){.cart .cart-list-headings .cart-header-group,.cart .cart-list-headings .cart-product-header{display:none}}.cart .cart-item{display:flex;flex-direction:row;padding:1.875rem;position:relative}@media (max-width:34.8125em){.cart .cart-item{flex-direction:column}}.cart .cart-item h3{font-weight:300;margin:0}.cart .product-image{flex-shrink:0;height:8.75rem;margin:0 1.875rem 0 0;width:8.75rem}@media (max-width:34.8125em){.cart .product-image{height:auto;margin:0 auto 1.875rem;max-height:8.75rem;max-width:8.75rem;width:100%}}.cart .product-image img{height:inherit;margin:0;width:inherit}.cart .cart-details{flex-grow:1}@media (max-width:52.8125em){.cart .cart-details{flex-direction:column;padding-right:2.5rem}}@media (max-width:34.8125em){.cart .cart-details{padding-right:0;text-align:center}.cart .cart-quantity .quantity-input{justify-content:center}}.cart .cart-price,.cart .cart-quantity,.cart .cart-title{padding:0 0 1.25rem;width:100%}@media (min-width:52.875em){.cart .cart-price,.cart .cart-quantity,.cart .cart-title{padding:0 1.875rem 0 0;width:33.33%}}.cart .cart-quantity{min-width:9.0625rem}.cart .cart-quantity .quantity-input{width:100%}.cart .cart-price .price{font-weight:400;margin:0}.cart .cart-delete{position:absolute;right:.9375rem;top:.9375rem}@media (min-width:52.875em){.cart .cart-delete{height:100%;top:0}.cart .total-price{justify-content:flex-end}}.cart .cart-delete .remove{background:0 0;border:0;color:#9f9fae;margin:0;min-height:none;min-width:0;padding:.9375rem}.cart .cart-delete .remove:active,.cart .cart-delete .remove:focus,.cart .cart-delete .remove:hover{color:#2d2d40}.cart .cart-delete svg{height:1.5rem;width:1.5rem}.cart .total-price{border-bottom:5px solid #f0f0f0;border-top:5px solid #f0f0f0;padding:1.875rem;text-align:right}.cart .total-price .price{font-size:inherit;font-weight:300;padding-left:1.875rem}@media (max-width:52.8125em){.cart .total-price .price{margin-left:auto}}.cart .submit{margin:1.875rem 1.875rem 1.875rem auto}@media (max-width:52.8125em){.cart .submit{margin-right:auto}}.checkout{margin-top:1.875rem}.checkout h2{color:inherit;font-weight:400;margin:0}.checkout .form-header{background:#e7e7e7;border:2px solid #e7e7e7;color:#9f9fae;padding:1rem 1.875rem}.checkout .form-header.inactive{background:#f7f7f7;border-color:#f7f7f7;color:#cfcfd5}.checkout .form-header.completed{background:#e8f7ed;border-color:#e8f7ed;color:#95cca9;cursor:pointer}.checkout .form-header.completed:active,.checkout .form-header.completed:focus,.checkout .form-header.completed:hover{border-color:#95cca9;outline:0}.checkout .form-header.incomplete{background:#edadad;border-color:#edadad;color:#fff}.checkout .form-header.incomplete:active,.checkout .form-header.incomplete:focus,.checkout .form-header.incomplete:hover{border-color:#9f9fae}.checkout .checkout-form,.checkout .checkout-summary{max-width:none;padding:0;width:100%}.checkout .checkout-summary{margin:0 0 4.625rem}.checkout .checkout-form{margin:0}@media (min-width:52.875em){.checkout .content{display:flex}.checkout .checkout-summary{margin:0;order:2;width:40%}.checkout .checkout-form{order:1;padding-right:3.75rem;width:60%}}.checkout .checkout-items,.checkout .price-calculations,.checkout .total-price{border-bottom:5px solid #f0f0f0;padding:1.875rem}.checkout .checkout-product:not(:last-child){margin-bottom:1.875rem}.checkout .checkout-product .product-image{width:5rem}.checkout .checkout-product img{margin:0}.checkout .checkout-product .product-info{display:flex;flex-direction:column;line-height:1.625rem;margin:0 1.875rem}.checkout .checkout-product .product-title{font-weight:600}.checkout .checkout-product .remove{background:0 0;border:0;color:#9f9fae;margin:0 -.9375rem 0 auto;min-height:none;min-width:0;padding:.9375rem}.checkout .checkout-product .remove:active,.checkout .checkout-product .remove:focus,.checkout .checkout-product .remove:hover{color:#2d2d40}.checkout .checkout-product svg{height:1.5rem;width:1.5rem}.checkout .checkout-product .price,.checkout .price-item .price{font-size:inherit;font-weight:300}.checkout .price-item .price,.checkout .total-price .price{margin-left:auto;margin-right:0;padding-left:1.875rem}.checkout fieldset{margin-bottom:1.875rem}.checkout fieldset button{margin:1.25rem 0 0 1.875rem}@media (max-width:34.8125em){.cart .cart-delete{right:-.9375rem}.checkout fieldset button{margin:1.25rem auto 0}}.checkout .form-content{height:auto}.checkout .collapsed .form-content{height:0;overflow:hidden}.checkout .collapsed button,.checkout .collapsed input,.checkout .collapsed label{display:none}.checkout .form-fields{display:flex;flex-wrap:wrap;padding:.9375rem 1.875rem 0}@media (max-width:28.0625em){.checkout .form-fields{padding:.9375rem 0 0}}.checkout .input-wrap{width:100%}.checkout .expiry-year{margin-right:1.25rem}.checkout .cvc,.checkout .expiry-year{width:calc((100% - 1.25rem)/ 2)}@media (min-width:28.125em){.checkout .expiry-year{margin-right:0}.checkout .expiry-month{margin-right:1.25rem}.checkout .expiry-month,.checkout .expiry-year{width:calc((100% - 1.25rem)/ 2)}}@media (min-width:34.875em){.checkout .country,.checkout .expiry-year,.checkout .firstname,.checkout .state{margin-right:auto}.checkout .country,.checkout .firstname,.checkout .lastname,.checkout .postcode,.checkout .state{width:calc((100% - 1.25rem)/ 2)}.checkout .expiry-month{width:calc((50% - 2.5rem))}.checkout .cvc,.checkout .expiry-year{width:25%}}.order-confirmation .confirmation{border-bottom:5px solid #f0f0f0;border-top:5px solid #f0f0f0;margin-top:6.5rem;padding:1.875rem}.order-confirmation h2{font-weight:500;margin:1.875rem auto 0;text-align:center}.order-confirmation p{margin-left:auto;margin-right:auto}.order-confirmation img{margin:1.875rem auto 4.625rem;width:14.375rem}@media (max-width:28.0625em){.order-confirmation img{width:100%}}.broken-body,.broken-body header{animation:backgroundChange 2s ease-in-out;background:#2d2d40}.broken-body header h1{animation:colourChange 2s ease-in-out}.broken-body .menu-btn:not(.active){animation:colourChange 1s ease-in-out}.broken-body .logo .logo-link,.broken-body .primary-nav a{animation:colourChange 2s ease-in-out}@media (max-width:52.8125em){.broken-body .secondary-nav .cart:link,.broken-body .secondary-nav .cart:visited{animation:cartBackgroundChange 2s ease-in-out}}@media (min-width:52.875em){.broken-body .secondary-nav .cart{animation:colourChange 2s ease-in-out}}.broken-body footer{animation:colourAndBackgroundChange 2s ease-in-out;background:#2d2d40;color:#fff;margin-top:0}.broken-body footer a{animation:colourChange 2s ease-in-out}.broken-body footer .footer-content{animation:lineChange 2s ease-in-out;border-color:#50505b}.broken-body .broken-container{animation:backgroundChange 2s ease-in-out;background:#2d2d40;padding:0 0 13.875rem}.broken-body .broken-container .content,.broken-body .broken-container h2{animation:colourChange 2s ease-in-out;color:#fff}.broken-body .broken-container .content{text-align:center}.broken-body .broken-container p{margin-left:auto;margin-right:auto}.broken-body .broken-container .btn{animation:fadeIn 3s ease-in-out;margin:4.625rem auto 0} -------------------------------------------------------------------------------- /src/assets/img/unique.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moltin/react-demo-store/14e339c9f6239a8c129821ba993af4dd6e960260/src/assets/img/unique.png -------------------------------------------------------------------------------- /src/assets/img/weloveyou.svg: -------------------------------------------------------------------------------- 1 | weloveyou -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Switch, Route } from 'react-router-dom'; 3 | 4 | import Home from './Home/Home'; 5 | import Cart from './Cart/Cart'; 6 | import CheckoutContainer from './Checkout/CheckoutContainer'; 7 | import StylesContainer from './Styles/StylesContainer'; 8 | import ProductsContainer from './Products/ProductsContainer'; 9 | import SingleProductContainer from './Products/SingleProductContainer'; 10 | import OneClickCheckout from './Checkout/OneClickCheckout'; 11 | import OrderConfirmationContainer from './Orders/OrderConfirmationContainer'; 12 | import NotFound from './global/NotFound'; 13 | // import MobileNav from './global/Mobile/MobileNav'; 14 | import Footer from './global/Footer'; 15 | 16 | const App = props => ( 17 |
18 | {/* */} 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | 35 | 36 | 37 | 38 |
40 | ); 41 | 42 | export default App; 43 | -------------------------------------------------------------------------------- /src/components/Cart/Cart.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { bindActionCreators } from 'redux'; 3 | import { connect } from 'react-redux'; 4 | import { Link } from 'react-router-dom'; 5 | 6 | import MobileNav from '../global/Mobile/MobileNav'; 7 | import CartHeader from './CartHeader'; 8 | import CartItems from './CartItems'; 9 | 10 | import { GetProducts } from '../../ducks/products'; 11 | import { GetCartItems } from '../../ducks/cart'; 12 | 13 | class Cart extends Component { 14 | componentWillMount() { 15 | const script = document.createElement('script'); 16 | 17 | script.src = '../../js/production.min.js'; 18 | script.async = false; 19 | 20 | document.body.appendChild(script); 21 | } 22 | 23 | componentDidMount() { 24 | this.props.GetProducts(); 25 | this.props.GetCartItems(); 26 | } 27 | 28 | render() { 29 | const { cart, products } = this.props; 30 | 31 | if ( 32 | cart.fetched === true && 33 | cart.fetching === false && 34 | products.fetched === true 35 | ) { 36 | if (cart.cart.data[0]) { 37 | var subtotal = '$' + cart.cart.meta.display_price.with_tax.amount / 100; 38 | return ( 39 |
40 | 41 | 42 |
43 |
44 |
45 |
46 |
47 |
Product
48 |
49 |
50 |
Quantity
51 |
Price
52 |
53 |
54 | 55 |
56 | Subtotal 57 | {' '} 58 | of all products 59 | {' '} 60 | {subtotal} 61 |
62 | 63 | Checkout 64 | 65 | 66 |
67 |
68 |
69 |
70 | ); 71 | } else { 72 | return ( 73 |
74 | 75 | 76 |
77 |
78 |
79 |
80 |

81 | Oh no, looks like you don't love lamp, as your cart is 82 | empty. 83 |

84 | 85 | Start shopping 86 | 87 |
88 |
89 |
90 |
91 |
92 | ); 93 | } 94 | } else { 95 | return ( 96 |
97 | 98 | 99 |
100 |
101 |
102 |
103 | 114 |

Loading

115 |
116 |
117 |
118 |
119 |
120 | ); 121 | } 122 | } 123 | } 124 | 125 | const mapStateToProps = ({ products, cart }) => ({ 126 | products, 127 | cart 128 | }); 129 | 130 | const mapDispatchToProps = dispatch => 131 | bindActionCreators( 132 | { 133 | GetProducts, 134 | GetCartItems 135 | }, 136 | dispatch 137 | ); 138 | 139 | export default connect(mapStateToProps, mapDispatchToProps)(Cart); 140 | -------------------------------------------------------------------------------- /src/components/Cart/CartCounter.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { bindActionCreators } from 'redux'; 3 | import { connect } from 'react-redux'; 4 | import { Link } from 'react-router-dom'; 5 | 6 | import { GetCartItems } from '../../ducks/cart'; 7 | 8 | class CartCounter extends Component { 9 | componentDidMount() { 10 | this.props.GetCartItems(); 11 | } 12 | 13 | render() { 14 | let quantity = 0; 15 | 16 | if (this.props.cart.fetched === true) { 17 | var items = this.props.cart.cart.data; 18 | 19 | quantity = items.reduce((sum, item) => sum + item.quantity, 0); 20 | } 21 | 22 | return ( 23 | 24 | 27 | The cart contains 28 | {quantity} 29 | items. 30 | 33 | 34 | ); 35 | } 36 | } 37 | 38 | const mapStateToProps = ({ cart }) => ({ 39 | cart 40 | }); 41 | 42 | const mapDispatchToProps = dispatch => 43 | bindActionCreators( 44 | { 45 | GetCartItems 46 | }, 47 | dispatch 48 | ); 49 | 50 | export default connect(mapStateToProps, mapDispatchToProps)(CartCounter); 51 | -------------------------------------------------------------------------------- /src/components/Cart/CartHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link, withRouter } from 'react-router-dom'; 3 | 4 | import CartCounter from './CartCounter'; 5 | 6 | const CartHeader = props => { 7 | let headerText; 8 | 9 | if (props.location.pathname.includes('cart')) { 10 | headerText = 'Shopping Cart'; 11 | } else if (props.location.pathname.includes('checkout')) { 12 | headerText = 'Checkout'; 13 | } else if (props.location.pathname.includes('order-confirmation')) { 14 | headerText = 'Order Confirmation'; 15 | } 16 | 17 | return ( 18 |
19 |
20 | 24 |
25 | 26 | I love lamp 27 | 69 | 98 | 99 |
100 | 103 |
104 |
105 |
106 |

{headerText}

107 |
108 |
109 |
110 | ); 111 | }; 112 | 113 | export default withRouter(CartHeader); 114 | -------------------------------------------------------------------------------- /src/components/Cart/CartItems.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import ProductImage from '../Products/ProductImage'; 4 | import { FETCH_CART_START, FETCH_CART_END } from '../../ducks/cart'; 5 | 6 | var api = require('../../moltin.js'); 7 | 8 | function mapStateToProps(state) { 9 | return state; 10 | } 11 | 12 | class CartItems extends Component { 13 | render() { 14 | var cart_decrement = (ID, quantity) => { 15 | this.props.dispatch(dispatch => { 16 | dispatch({ type: FETCH_CART_START }); 17 | 18 | api 19 | .UpdateCartMinus(ID, quantity) 20 | 21 | .then(cart => { 22 | console.log('cart quantity updated'); 23 | dispatch({ type: FETCH_CART_END, payload: cart, gotNew: true }); 24 | }) 25 | 26 | .catch(e => { 27 | console.log(e); 28 | }); 29 | }); 30 | }; 31 | 32 | var cart_increment = (ID, quantity) => { 33 | this.props.dispatch(dispatch => { 34 | dispatch({ type: FETCH_CART_START }); 35 | 36 | api 37 | .UpdateCartPlus(ID, quantity) 38 | 39 | .then(cart => { 40 | console.log('cart quantity updated'); 41 | 42 | dispatch({ type: FETCH_CART_END, payload: cart, gotNew: true }); 43 | }) 44 | 45 | .catch(e => { 46 | console.log(e); 47 | }); 48 | }); 49 | }; 50 | 51 | var cart_edit = (ID, quantity) => { 52 | this.props.dispatch(dispatch => { 53 | dispatch({ type: FETCH_CART_START }); 54 | 55 | api 56 | .UpdateCart(ID, quantity) 57 | 58 | .then(cart => { 59 | console.log('cart quantity updated'); 60 | dispatch({ type: FETCH_CART_END, payload: cart }); 61 | }) 62 | 63 | .catch(e => { 64 | console.log(e); 65 | }); 66 | }); 67 | }; 68 | 69 | var items = this.props.cart.cart.data; 70 | 71 | var products = this.props.products.products; 72 | 73 | return ( 74 |
75 | {items.map(function(item) { 76 | var productArray = products.data.filter(function(product) { 77 | return product.id === item.product_id; 78 | }); 79 | 80 | var product = productArray[0]; 81 | 82 | var background = product.background_colour; 83 | 84 | var TotalPriceHidden = 'hidden'; 85 | 86 | if (item.quantity > 1) { 87 | TotalPriceHidden = ''; 88 | } 89 | 90 | return ( 91 |
92 | 100 |
101 |
102 |

{item.name}

103 |
104 | 105 |
106 |
107 |

Product quantity.

108 |

109 | Change the quantity by using the buttons, or alter the 110 | input directly. 111 |

112 | 121 | { 130 | cart_edit(item.id, event.target.value); 131 | console.log(event.target.value); 132 | }} 133 | /> 134 | 143 |
144 |
145 |
146 |

147 | 148 | Price per item $ 149 | {item.unit_price.amount / 100} 150 | 151 | 152 | 153 | Product subtotal $ 154 | {item.unit_price.amount / 100 * item.quantity} 155 | 156 |

157 |
158 |
159 |
160 | 188 |
189 |
190 | ); 191 | })} 192 |
193 | ); 194 | } 195 | } 196 | 197 | export default connect(mapStateToProps)(CartItems); 198 | -------------------------------------------------------------------------------- /src/components/Categories/Categories.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import ProductImage from '../Products/ProductImage'; 4 | import { push } from 'react-router-redux'; 5 | 6 | import { SET_STYLE } from '../../ducks/styles'; 7 | 8 | function mapStateToProps(state) { 9 | return state; 10 | } 11 | 12 | class Categories extends Component { 13 | render() { 14 | var ToStyles = () => { 15 | this.props.dispatch(dispatch => { 16 | dispatch(push('/styles')); 17 | }); 18 | }; 19 | 20 | var ChangeStyle = name => { 21 | this.props.dispatch(dispatch => { 22 | dispatch({ type: SET_STYLE, style: name }); 23 | }); 24 | 25 | ToStyles(); 26 | }; 27 | 28 | var products = this.props.products.products; 29 | var productData = this.props.products.products.data; 30 | let product; 31 | 32 | try { 33 | product = this.props.categories.categories.included.products[0]; 34 | } catch (err) { 35 | product = null; 36 | } 37 | 38 | if (this.props.categories.categories.data.length > 0) { 39 | return ( 40 |
41 | {this.props.categories.categories.data.map(function(category) { 42 | if (category.relationships.products) { 43 | var CatProductRef = category.relationships.products.data[0]; 44 | 45 | var CatProduct = productData.find(function(product) { 46 | return product.id === CatProductRef.id; 47 | }); 48 | 49 | let background; 50 | 51 | if (CatProduct.background_colour) { 52 | background = CatProduct.background_colour; 53 | } else { 54 | background = '#d9d9d9'; 55 | } 56 | 57 | return ( 58 | { 65 | ChangeStyle(category.name); 66 | }}> 67 |

{ 69 | e.preventDefault(); 70 | }}> 71 | {category.name} 72 | lamps 73 |

74 |
89 | ); 90 | } else { 91 | return ( 92 |
93 |

No products related to your categories

94 |
95 | ); 96 | } 97 | })} 98 |
99 | ); 100 | } else { 101 | return ( 102 |
103 |
104 |

You have no categories

105 |
106 |
107 | ); 108 | } 109 | } 110 | } 111 | 112 | export default connect(mapStateToProps)(Categories); 113 | -------------------------------------------------------------------------------- /src/components/Categories/CategoriesContainer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Categories from './Categories'; 3 | 4 | class CategoriesContainer extends Component { 5 | render() { 6 | return ( 7 |
8 |
9 |

Our collections

10 | 11 |
12 |
13 | ); 14 | } 15 | } 16 | 17 | export default CategoriesContainer; 18 | -------------------------------------------------------------------------------- /src/components/Checkout/CheckoutContainer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import CheckoutForm from './CheckoutForm'; 3 | import CartHeader from '../Cart/CartHeader'; 4 | import MobileNav from '../global/Mobile/MobileNav'; 5 | import Loading from '../global/Loading'; 6 | import { connect } from 'react-redux'; 7 | 8 | function mapStateToProps(state) { 9 | return state; 10 | } 11 | 12 | class CheckoutContainer extends Component { 13 | componentWillMount() { 14 | const script = document.createElement('script'); 15 | 16 | script.src = '../../js/production.min.js'; 17 | script.async = true; 18 | 19 | document.body.appendChild(script); 20 | } 21 | 22 | render() { 23 | if (this.props.payments.processing === false) { 24 | return ( 25 |
26 | 27 | 28 | 29 |
30 | ); 31 | } else { 32 | return ( 33 |
34 | 35 | 36 | 37 |
38 | ); 39 | } 40 | } 41 | } 42 | 43 | export default connect(mapStateToProps)(CheckoutContainer); 44 | -------------------------------------------------------------------------------- /src/components/Checkout/CheckoutItems.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import ProductImage from '../Products/ProductImage'; 4 | 5 | function mapStateToProps(state) { 6 | return state; 7 | } 8 | 9 | class CheckoutItems extends Component { 10 | render() { 11 | var items = this.props.cart.cart.data; 12 | 13 | var products = this.props.products.products; 14 | 15 | return ( 16 |
17 | {items.map(function(item) { 18 | var productArray = products.data.filter(function(product) { 19 | return product.id === item.product_id; 20 | }); 21 | 22 | var product = productArray[0]; 23 | var background = product.background_colour; 24 | 25 | return ( 26 |
27 | 34 |
35 |
36 | {item.name + ' X ' + item.quantity} 37 |
38 |
39 | Product subtotal: 40 | {'$' + item.unit_price.amount / 100} 41 |
42 |
43 |
44 | ); 45 | })} 46 |
47 | ); 48 | } 49 | } 50 | 51 | export default connect(mapStateToProps)(CheckoutItems); 52 | -------------------------------------------------------------------------------- /src/components/Checkout/CheckoutSummary.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import CheckoutItems from './CheckoutItems'; 4 | import Loading from '../global/Loading'; 5 | 6 | import { FETCH_PRODUCTS_START, FETCH_PRODUCTS_END } from '../../ducks/products'; 7 | import { FETCH_CART_START, FETCH_CART_END } from '../../ducks/cart'; 8 | 9 | var api = require('../../moltin.js'); 10 | 11 | function mapStateToProps(state) { 12 | return state; 13 | } 14 | 15 | class CheckoutSummary extends Component { 16 | // a react lifecycle event, read more at http://busypeoples.github.io/post/react-component-lifecycle/ 17 | componentDidMount() { 18 | // check if we already have a moltin products in the store 19 | if (this.props.products.fetched === false) { 20 | // dispatch an action to our redux reducers 21 | this.props.dispatch(dispatch => { 22 | // this action will set a fetching field to true 23 | dispatch({ type: FETCH_PRODUCTS_START }); 24 | 25 | // get the moltin products from the API 26 | api 27 | .GetProducts() 28 | 29 | .then(products => { 30 | /* now that we have the products, this action will set fetching to false and fetched to true, 31 | as well as add the moltin products to the store */ 32 | dispatch({ type: FETCH_PRODUCTS_END, payload: products }); 33 | }); 34 | }); 35 | } 36 | 37 | if (this.props.cart.fetched === false) { 38 | this.props.dispatch(dispatch => { 39 | dispatch({ type: FETCH_CART_START }); 40 | 41 | api 42 | .GetCartItems() 43 | 44 | .then(cart => { 45 | dispatch({ type: FETCH_CART_END, payload: cart }); 46 | }); 47 | }); 48 | } 49 | } 50 | 51 | render() { 52 | if ( 53 | this.props.cart.fetched === true && 54 | this.props.products.fetched === true 55 | ) { 56 | var tax = 57 | this.props.cart.cart.meta.display_price.with_tax.amount - 58 | this.props.cart.cart.meta.display_price.without_tax.amount; 59 | 60 | return ( 61 |
62 |
63 |

64 | Summary 65 | {' '} 66 | of your order, ready for checkout. 67 | 68 |

69 |
70 |
71 | 72 |
73 |
74 |
75 | Subtotal for all products 76 | 77 | {'$' + 78 | this.props.cart.cart.meta.display_price.without_tax.amount / 79 | 100} 80 | 81 |
82 |
83 | Tax{'$' + tax} 84 |
85 |
86 | Shipping$0 87 |
88 |
89 |
90 | Total{' '} 91 | 92 | {'$' + 93 | this.props.cart.cart.meta.display_price.without_tax.amount / 94 | 100} 95 | 96 |
97 |
98 | ); 99 | } 100 | 101 | return ( 102 |
103 | 104 |
105 | ); 106 | } 107 | } 108 | 109 | export default connect(mapStateToProps)(CheckoutSummary); 110 | -------------------------------------------------------------------------------- /src/components/Checkout/OneClickCheckout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withRouter } from 'react-router'; 3 | import { bindActionCreators } from 'redux'; 4 | import { connect } from 'react-redux'; 5 | 6 | import LoadingIndicator from '../global/Loading'; 7 | 8 | import { addToCart } from '../../ducks/cart'; 9 | 10 | class OneClickCheckout extends Component { 11 | async componentDidMount() { 12 | const { match: { params }, history, addToCart } = this.props; 13 | 14 | if (!params.productId) { 15 | history.push('/checkout'); 16 | } 17 | 18 | await addToCart(params.productId, 1); 19 | 20 | history.push('/checkout'); 21 | } 22 | 23 | render() { 24 | return ; 25 | } 26 | } 27 | 28 | const mapDispatchToProps = dispatch => 29 | bindActionCreators( 30 | { 31 | addToCart 32 | }, 33 | dispatch 34 | ); 35 | 36 | export default connect(null, mapDispatchToProps)(withRouter(OneClickCheckout)); 37 | -------------------------------------------------------------------------------- /src/components/Home/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { bindActionCreators } from 'redux'; 3 | import { connect } from 'react-redux'; 4 | 5 | import HomeHeader from './HomeHeader'; 6 | import HomeMainSection from './HomeMainSection'; 7 | import MobileNav from '../global/Mobile/MobileNav'; 8 | import Loading from '../global/Loading'; 9 | 10 | import { GetProducts } from '../../ducks/products'; 11 | import { GetCategories } from '../../ducks/categories'; 12 | import { GetCollections } from '../../ducks/collections'; 13 | 14 | class Home extends Component { 15 | componentWillMount() { 16 | const script = document.createElement('script'); 17 | 18 | script.src = '../../js/production.min.js'; 19 | script.async = false; 20 | 21 | document.body.appendChild(script); 22 | } 23 | 24 | componentDidMount() { 25 | const { products, categories, collections } = this.props; 26 | 27 | if (!products.fetched) { 28 | this.props.GetProducts(); 29 | } 30 | 31 | if (!categories.fetched) { 32 | this.props.GetCategories(); 33 | } 34 | 35 | if (!collections.fetched) { 36 | this.props.GetCollections(); 37 | } 38 | } 39 | 40 | render() { 41 | const { products, collections, categories } = this.props; 42 | 43 | if ( 44 | collections.collections !== null && 45 | products.products !== null && 46 | categories.categories !== null 47 | ) { 48 | return ( 49 |
50 | 51 | 52 | 53 |
54 | ); 55 | } else { 56 | return ( 57 |
58 | 59 | 60 | 61 |
62 | ); 63 | } 64 | } 65 | } 66 | 67 | const mapStateToProps = ({ products, categories, collections }) => ({ 68 | products, 69 | categories, 70 | collections 71 | }); 72 | 73 | const mapDispatchToProps = dispatch => 74 | bindActionCreators( 75 | { 76 | GetProducts, 77 | GetCategories, 78 | GetCollections 79 | }, 80 | dispatch 81 | ); 82 | 83 | export default connect(mapStateToProps, mapDispatchToProps)(Home); 84 | -------------------------------------------------------------------------------- /src/components/Home/HomeHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | import HeaderNav from '../global/HeaderNav'; 5 | import * as Header from '../../assets/img/headers/header.png'; 6 | 7 | var HeaderStyle = { 8 | backgroundImage: `url(${Header})`, 9 | backgroundRepeat: 'no-repeat', 10 | backgroundAttachment: 'scroll', 11 | backgroundPosition: 'center', 12 | backgroundSize: 'cover', 13 | backgroundOrigin: 'border-box' 14 | }; 15 | 16 | const HomeHeader = props => ( 17 |
18 | 19 | 20 |
21 |
22 |

I love carpet. I love desk.

23 | 24 | I love lamp 25 | 26 |
27 |
28 | 29 | 32 |
33 | ); 34 | 35 | export default HomeHeader; 36 | -------------------------------------------------------------------------------- /src/components/Home/HomeMainSection.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TopPicksContainer from './TopPicksContainer'; 3 | import CategoriesContainer from '../Categories/CategoriesContainer'; 4 | 5 | const HomeMainSection = () => ( 6 |
7 | 8 | 9 |
10 | ); 11 | 12 | export default HomeMainSection; 13 | -------------------------------------------------------------------------------- /src/components/Home/TopPicks.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import ProductImage from '../Products/ProductImage'; 4 | 5 | function mapStateToProps(state) { 6 | return state; 7 | } 8 | 9 | class TopPicks extends Component { 10 | render() { 11 | var TopPicksToMap = []; 12 | 13 | var collections = this.props.collections.collections.data; 14 | 15 | var productData = this.props.products.products.data; 16 | 17 | let TopPicks; 18 | 19 | try { 20 | TopPicks = collections.find(collections => { 21 | return collections.slug === 'top_picks'; 22 | }); 23 | } catch (e) { 24 | TopPicks = collections[0]; 25 | } 26 | 27 | let TopPicksProductIDs; 28 | 29 | try { 30 | TopPicksProductIDs = TopPicks.relationships.products.data; 31 | 32 | TopPicksProductIDs.forEach(function(productref) { 33 | var TopPicksProduct = productData.find(function(product) { 34 | return product.id === productref.id; 35 | }); 36 | TopPicksToMap.push(TopPicksProduct); 37 | }); 38 | 39 | var products = this.props.products.products; 40 | 41 | return ( 42 |
43 | {TopPicksToMap.map(function(top_pick) { 44 | let background; 45 | 46 | if (top_pick.background_colour) { 47 | background = top_pick.background_colour; 48 | } else { 49 | background = '#d9d9d9'; 50 | } 51 | 52 | var isNew = null; 53 | 54 | if (top_pick.new === true) { 55 | isNew = 'new'; 56 | } 57 | 58 | return ( 59 | 64 |
67 | 68 |
69 |
70 |
74 |
75 |
{top_pick.name}
76 |
77 | {'$' + top_pick.meta.display_price.with_tax.amount / 100} 78 |
79 |
80 |
81 |
82 | ); 83 | })} 84 |
85 | ); 86 | } catch (err) { 87 | TopPicksProductIDs = null; 88 | return ( 89 |
90 |

You do not have any products attached to a collection.

91 |
92 | ); 93 | } 94 | } 95 | } 96 | 97 | export default connect(mapStateToProps)(TopPicks); 98 | -------------------------------------------------------------------------------- /src/components/Home/TopPicksContainer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import TopPicks from './TopPicks'; 3 | 4 | class TopPicksContainer extends Component { 5 | render() { 6 | return ( 7 |
8 |
9 |

Top picks

10 |
11 | 12 |
13 |
14 |
15 | ); 16 | } 17 | } 18 | 19 | export default TopPicksContainer; 20 | -------------------------------------------------------------------------------- /src/components/Orders/OrderConfirmation.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as WeLoveYou from '../../assets/img/weloveyou.svg'; 3 | 4 | const OrderConfirmation = () => ( 5 |
6 |
7 |
8 |
9 |

Awesome, your order has been placed

10 |

Make sure you check your emails for confirmation.

11 | We Love You 12 |
13 |
14 |
15 |
16 | ); 17 | 18 | export default OrderConfirmation; 19 | -------------------------------------------------------------------------------- /src/components/Orders/OrderConfirmationContainer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import OrderConfirmation from './OrderConfirmation'; 3 | import CartHeader from '../Cart/CartHeader'; 4 | 5 | class OrderConfirmationContainer extends Component { 6 | componentWillMount() { 7 | const script = document.createElement('script'); 8 | 9 | script.src = '../../js/production.min.js'; 10 | script.async = false; 11 | 12 | document.body.appendChild(script); 13 | } 14 | 15 | render() { 16 | return ( 17 |
18 | 19 | 20 |
21 | ); 22 | } 23 | } 24 | 25 | export default OrderConfirmationContainer; 26 | -------------------------------------------------------------------------------- /src/components/Products/AllProducts.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import ProductImage from './ProductImage'; 4 | 5 | const isThereACurrencyPrice = product => { 6 | try { 7 | return ( 8 |
9 | {product.meta.display_price.with_tax.amount / 100} 10 |
11 | ); 12 | } catch (e) { 13 | return
Price not available
; 14 | } 15 | }; 16 | 17 | const AllProducts = props => { 18 | if (props.css !== null && props.products.products.data.length > 0) { 19 | var products = props.products.products; 20 | 21 | return ( 22 |
23 |
24 |
25 |
26 | {products.data.map(function(product) { 27 | let background; 28 | if (product.background_colour) { 29 | background = product.background_colour; 30 | } else { 31 | background = '#d9d9d9'; 32 | } 33 | 34 | return ( 35 | 39 |
42 | 43 |
44 |
58 |
59 |
60 |
61 | ); 62 | } 63 | 64 | return ( 65 |
66 |
67 |
68 |

You do not have any products

69 |
70 |
71 |
72 | ); 73 | }; 74 | 75 | const mapStateToProps = ({ products }) => ({ 76 | products 77 | }); 78 | 79 | export default connect(mapStateToProps)(AllProducts); 80 | -------------------------------------------------------------------------------- /src/components/Products/ProductHeader.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { connect } from 'react-redux'; 4 | 5 | import CartCounter from '../Cart/CartCounter'; 6 | 7 | class ProductHeader extends Component { 8 | componentWillMount() { 9 | const script = document.createElement('script'); 10 | 11 | script.src = '../../js/production.min.js'; 12 | script.async = false; 13 | 14 | document.body.appendChild(script); 15 | } 16 | 17 | render() { 18 | var ID = this.props.router.location.pathname.slice(9, 100); 19 | 20 | var productArray = this.props.products.products.data.filter(function( 21 | product 22 | ) { 23 | return product.id === ID; 24 | }); 25 | 26 | var product = productArray[0]; 27 | 28 | return ( 29 |
30 |
31 | 35 |
36 | 37 | I love lamp 38 | 80 | 109 | 110 |
111 | 114 |
115 |
116 |
117 |

Product details for {product.name}

118 |
119 |
120 |
121 | ); 122 | } 123 | } 124 | 125 | const mapStateToProps = state => state; 126 | 127 | export default connect(mapStateToProps)(ProductHeader); 128 | -------------------------------------------------------------------------------- /src/components/Products/ProductImage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const ProductImage = props => { 4 | let file; 5 | let fileId; 6 | let placeholder = 7 | 'https://placeholdit.imgix.net/~text?txtsize=69&txt=824%C3%971050&w=824&h=1050'; 8 | 9 | var isThereAMainImage = product => { 10 | fileId = props.product.relationships.main_image.data.id; 11 | 12 | file = props.products.included.main_images.find(function(el) { 13 | return fileId === el.id; 14 | }); 15 | 16 | return ( 17 | ( 18 | {props.product.name 23 | ) || placeholder 24 | ); 25 | }; 26 | 27 | var isThereAFile = product => { 28 | try { 29 | fileId = props.product.relationships.files.data[0].id; 30 | file = props.products.included.files.find(function(el) { 31 | return fileId === el.id; 32 | }); 33 | return ( 34 | {props.product.name 39 | ); 40 | } catch (e) { 41 | return placeholder; 42 | } 43 | }; 44 | 45 | try { 46 | return isThereAMainImage(props.product); 47 | } catch (e) { 48 | return isThereAFile(props.product); 49 | } 50 | }; 51 | 52 | export default ProductImage; 53 | -------------------------------------------------------------------------------- /src/components/Products/ProductsContainer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { bindActionCreators } from 'redux'; 3 | import { connect } from 'react-redux'; 4 | 5 | import AllProducts from './AllProducts'; 6 | import ProductsHeader from './ProductsHeader'; 7 | import MobileNav from '../global/Mobile/MobileNav'; 8 | import Loading from '../global/Loading'; 9 | 10 | import { GetProducts } from '../../ducks/products'; 11 | 12 | class ProductsContainer extends Component { 13 | componentWillMount() { 14 | const script = document.createElement('script'); 15 | 16 | script.src = '../../js/production.min.js'; 17 | script.async = false; 18 | 19 | document.body.appendChild(script); 20 | } 21 | 22 | componentDidMount() { 23 | const { fetched } = this.props; 24 | 25 | if (!fetched) { 26 | this.props.GetProducts(); 27 | } 28 | } 29 | 30 | render() { 31 | const { products } = this.props; 32 | 33 | if (products) { 34 | return ( 35 |
36 | 37 | 38 | 39 |
40 | ); 41 | } else { 42 | return ( 43 |
44 | 45 | 46 | 47 |
48 | ); 49 | } 50 | } 51 | } 52 | 53 | const mapStateToProps = ({ 54 | products: { fetching, fetched, error, products } 55 | }) => ({ 56 | fetching, 57 | fetched, 58 | error, 59 | products 60 | }); 61 | 62 | const mapDispatchToProps = dispatch => 63 | bindActionCreators( 64 | { 65 | GetProducts 66 | }, 67 | dispatch 68 | ); 69 | 70 | export default connect(mapStateToProps, mapDispatchToProps)(ProductsContainer); 71 | -------------------------------------------------------------------------------- /src/components/Products/ProductsHeader.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { connect } from 'react-redux'; 4 | 5 | import CartCounter from '../Cart/CartCounter'; 6 | 7 | class ProductsHeader extends Component { 8 | render() { 9 | return ( 10 |
11 |
12 | 16 |
17 | 18 | I love lamp 19 | 61 | 90 | 91 |
92 | 95 |
96 |
97 |
98 |

Products listing

99 |
100 |
101 |
102 | ); 103 | } 104 | } 105 | 106 | const mapStateToProps = state => state; 107 | 108 | export default connect(mapStateToProps)(ProductsHeader); 109 | -------------------------------------------------------------------------------- /src/components/Products/SingleProduct.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import ProductImage from './ProductImage'; 4 | import * as api from '../../moltin'; 5 | 6 | import { UPDATE_QUANTITY } from '../../ducks/product'; 7 | import { 8 | FETCH_CART_START, 9 | FETCH_CART_END, 10 | CART_UPDATED 11 | } from '../../ducks/cart'; 12 | 13 | const mapStateToProps = state => { 14 | return state; 15 | }; 16 | 17 | class SingleProduct extends Component { 18 | render() { 19 | var products = this.props.products.products; 20 | 21 | var ID = this.props.router.location.pathname.slice(9, 100); 22 | 23 | var productArray = this.props.products.products.data.filter(function( 24 | product 25 | ) { 26 | return product.id === ID; 27 | }); 28 | 29 | var product = productArray[0]; 30 | 31 | var updateQuantity = quantity => { 32 | this.props.dispatch(dispatch => { 33 | dispatch({ type: UPDATE_QUANTITY, payload: quantity }); 34 | }); 35 | }; 36 | 37 | var addToCart = id => { 38 | this.props.dispatch(dispatch => { 39 | api 40 | .AddCart(id, this.props.product.quantity) 41 | 42 | .then(cart => { 43 | console.log(cart); 44 | dispatch({ type: CART_UPDATED, gotNew: false }); 45 | }) 46 | 47 | .then(() => { 48 | dispatch({ type: FETCH_CART_START, gotNew: false }); 49 | 50 | api 51 | .GetCartItems() 52 | 53 | .then(cart => { 54 | dispatch({ type: FETCH_CART_END, payload: cart, gotNew: true }); 55 | }); 56 | }) 57 | .catch(e => { 58 | console.log(e); 59 | }); 60 | }); 61 | }; 62 | 63 | var background = product.background_colour; 64 | 65 | function isThereACurrencyPrice() { 66 | try { 67 | return ( 68 |

69 | Unit price 70 | {'$' + product.meta.display_price.with_tax.amount / 100} 71 |

72 | ); 73 | } catch (e) { 74 | return
Price not available
; 75 | } 76 | } 77 | 78 | return ( 79 |
80 |
81 |
82 |
83 |
84 | 89 |
90 |
91 |

{product.name}

92 |

93 | Manufactured By{' '} 94 | 95 | ILoveLamp 96 | 97 |

98 | {isThereACurrencyPrice()} 99 |
100 |

Product details:

101 |

{product.description}

102 |
103 |
104 |
105 |

Product quantity.

106 |

107 | Change the quantity by using the buttons, or alter the 108 | input directly. 109 |

110 | 119 | { 128 | updateQuantity(event.target.value); 129 | }} 130 | /> 131 | 140 |
141 | 151 |
152 |
153 |
154 |
155 |
156 |
157 |

Product details

158 |
159 |
160 |
161 |
Weight
162 |
1.5kg
163 |
164 |
165 |
Finish
166 |
{product.finish}
167 |
168 |
169 |
Material
170 |
{product.material}
171 |
172 |
173 |
Bulb type
174 |
{product.bulb}
175 |
176 |
177 |
Max Watt
178 |
{product.max_watt}
179 |
180 |
181 |
Bulb Qty
182 |
{product.bulb_qty}
183 |
184 |
185 |
SKU
186 |
{product.sku}
187 |
188 |
189 |
190 |
191 |
192 |

Dimensions (cm)

193 |
194 |
195 |
196 |
Height
197 |
156
198 |
199 |
200 |
Width
201 |
80
202 |
203 |
204 |
Depth
205 |
80
206 |
207 |
208 |
209 |
210 |
211 |

Delivery & returns

212 |
213 |
214 |
215 |
Dispatch
216 |
Within 2 weeks
217 |
218 |
219 |
Delivery
220 |
$5.95
221 |
222 |
223 |
224 |

225 | Read the delivery and returns policy. 226 |

227 |
228 |
229 |
230 |
231 |
232 |
233 | ); 234 | } 235 | } 236 | 237 | export default connect(mapStateToProps)(SingleProduct); 238 | -------------------------------------------------------------------------------- /src/components/Products/SingleProductContainer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { bindActionCreators } from 'redux'; 3 | import { connect } from 'react-redux'; 4 | 5 | import SingleProduct from './SingleProduct'; 6 | import CartHeader from '../Cart/CartHeader'; 7 | import ProductHeader from './ProductHeader'; 8 | import Loading from '../global/Loading'; 9 | import MobileNav from '../global/Mobile/MobileNav'; 10 | 11 | import { GetProducts } from '../../ducks/products'; 12 | 13 | class Product extends Component { 14 | componentDidMount() { 15 | const { fetched } = this.props; 16 | 17 | if (!fetched) { 18 | this.props.GetProducts(); 19 | } 20 | } 21 | 22 | render() { 23 | const { products } = this.props; 24 | 25 | if (products) { 26 | return ( 27 |
28 | 29 | 30 | 31 |
32 | ); 33 | } else { 34 | return ( 35 |
36 | 37 | 38 | 39 |
40 | ); 41 | } 42 | } 43 | } 44 | 45 | const mapStateToProps = ({ products: { fetched, products } }) => ({ 46 | fetched, 47 | products 48 | }); 49 | 50 | const mapDispatchToProps = dispatch => 51 | bindActionCreators( 52 | { 53 | GetProducts 54 | }, 55 | dispatch 56 | ); 57 | 58 | export default connect(mapStateToProps, mapDispatchToProps)(Product); 59 | -------------------------------------------------------------------------------- /src/components/Styles/StyleProducts.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import ProductImage from '../Products/ProductImage'; 4 | 5 | function mapStateToProps(state) { 6 | return state; 7 | } 8 | 9 | class StyleProducts extends Component { 10 | render() { 11 | var productsToMap = []; 12 | var categories = this.props.categories.categories.data; 13 | var CurrentStyle = this.props.styles.style; 14 | var CurrentCategory = categories.find(category => { 15 | return category.name === CurrentStyle; 16 | }); 17 | 18 | try { 19 | var CurrentCategoryProductIDs = 20 | CurrentCategory.relationships.products.data; 21 | var Products = this.props.products.products; 22 | var ProductsData = this.props.products.products.data; 23 | 24 | CurrentCategoryProductIDs.forEach(function(productref) { 25 | var Product = ProductsData.find(function(product) { 26 | return product.id === productref.id; 27 | }); 28 | productsToMap.push(Product); 29 | }); 30 | 31 | return ( 32 |
33 | {productsToMap.map(function(product) { 34 | let background; 35 | 36 | if (product.background_colour) { 37 | background = product.background_colour; 38 | } else { 39 | background = '#d9d9d9'; 40 | } 41 | 42 | return ( 43 | 47 |
50 | 51 |
52 |
53 |
57 |
58 |
{product.name}
59 |
60 | {'$' + product.meta.display_price.with_tax.amount / 100} 61 |
62 |
63 |
64 |
65 | ); 66 | })} 67 |
68 | ); 69 | } catch (err) { 70 | return ( 71 |
72 |

Your category has no attached products

73 |
74 | ); 75 | } 76 | } 77 | } 78 | 79 | export default connect(mapStateToProps)(StyleProducts); 80 | -------------------------------------------------------------------------------- /src/components/Styles/StylesContainer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { bindActionCreators } from 'redux'; 3 | import { connect } from 'react-redux'; 4 | 5 | import StylesHeader from './StylesHeader'; 6 | import StylesMenu from './StylesMenu'; 7 | import StylesHeading from './StylesHeading'; 8 | import StyleProducts from './StyleProducts'; 9 | import Loading from '../global/Loading'; 10 | import MobileNav from '../global/Mobile/MobileNav'; 11 | 12 | import { GetProducts } from '../../ducks/products'; 13 | import { GetCategories } from '../../ducks/categories'; 14 | import { setStyle } from '../../ducks/styles'; 15 | 16 | class StylesContainer extends Component { 17 | componentWillMount() { 18 | const script = document.createElement('script'); 19 | 20 | script.src = '../../js/production.min.js'; 21 | script.async = false; 22 | 23 | document.body.appendChild(script); 24 | } 25 | 26 | componentDidMount() { 27 | if (this.props.products.fetched === false) { 28 | this.props.GetProducts(); 29 | } 30 | 31 | if (this.props.categories.fetched === false) { 32 | this.props.GetCategories(); 33 | } 34 | } 35 | 36 | render() { 37 | const { categories, products } = this.props; 38 | 39 | if (categories.fetched === true && products.products) { 40 | if (categories.categories.data.length > 0) { 41 | return ( 42 |
43 | 44 | 45 |
46 |
47 |
48 | 49 |
50 |
51 |
52 |
53 | 54 |
55 |
56 |
57 |
58 | ); 59 | } else { 60 | return ( 61 |
62 | 63 | 64 | 65 |
66 |
67 |
68 | 69 |
70 |
71 |
72 |
73 |

You do not have any categories set up with products

74 |
75 |
76 |
77 |
78 | ); 79 | } 80 | } else { 81 | return ( 82 |
83 | 84 | 85 | 86 |
87 | ); 88 | } 89 | } 90 | } 91 | 92 | const mapStateToProps = ({ categories, products }) => ({ 93 | categories, 94 | products 95 | }); 96 | 97 | const mapDispatchToProps = dispatch => 98 | bindActionCreators( 99 | { 100 | setStyle, 101 | GetProducts, 102 | GetCategories 103 | }, 104 | dispatch 105 | ); 106 | 107 | export default connect(mapStateToProps, mapDispatchToProps)(StylesContainer); 108 | -------------------------------------------------------------------------------- /src/components/Styles/StylesHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { connect } from 'react-redux'; 4 | 5 | import CartCounter from '../Cart/CartCounter'; 6 | 7 | import Modern from '../../assets/img/modern.png'; 8 | import Silver from '../../assets/img/silver.png'; 9 | import Bright from '../../assets/img/bright.png'; 10 | import Unique from '../../assets/img/unique.png'; 11 | 12 | const StylesHeader = ({ style, header }) => { 13 | let Header = null; 14 | 15 | switch (header) { 16 | case 'Modern': 17 | Header = Modern; 18 | break; 19 | case 'Silver': 20 | Header = Silver; 21 | break; 22 | case 'Bright': 23 | Header = Bright; 24 | break; 25 | case 'Unique': 26 | Header = Unique; 27 | break; 28 | default: 29 | Header = null; 30 | } 31 | 32 | return ( 33 |
43 |
44 | 48 |
49 | 50 | I love lamp 51 | 93 | 122 | 123 |
124 | 127 |
128 |
129 |
130 |

131 | {style} 132 | styles 133 |

134 |
135 |
136 |
137 | ); 138 | }; 139 | 140 | const mapStateToProps = ({ styles: { style, header } }) => ({ 141 | style, 142 | header 143 | }); 144 | 145 | export default connect(mapStateToProps)(StylesHeader); 146 | -------------------------------------------------------------------------------- /src/components/Styles/StylesHeading.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | const StylesHeading = ({ style }) => ( 5 |
6 |
7 |

8 | {this.props.styles.style} 9 | styles 10 |

11 |
12 |
13 | ); 14 | 15 | const mapStateToProps = ({ styles: { style } }) => ({ 16 | style 17 | }); 18 | 19 | export default connect(mapStateToProps)(StylesHeading); 20 | -------------------------------------------------------------------------------- /src/components/Styles/StylesMenu.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { bindActionCreators } from 'redux'; 3 | import { connect } from 'react-redux'; 4 | 5 | import { setStyle } from '../../ducks/styles'; 6 | 7 | const StylesMenu = ({ categories, setStyle }) => ( 8 |
9 | {categories.categories.data.map(function(category) { 10 | return ( 11 | setStyle(e.target.name)}> 16 | Display 17 | {category.name} 18 | styles 19 | 20 | ); 21 | })} 22 |
23 | ); 24 | 25 | const mapStateToProps = ({ categories }) => ({ 26 | categories 27 | }); 28 | 29 | const mapDispatchToProps = dispatch => 30 | bindActionCreators( 31 | { 32 | setStyle 33 | }, 34 | dispatch 35 | ); 36 | 37 | export default connect(mapStateToProps, mapDispatchToProps)(StylesMenu); 38 | -------------------------------------------------------------------------------- /src/components/global/Footer.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import MailingList from './MailingList'; 3 | 4 | const Footer = () => ( 5 | 6 | 7 | 8 |
9 |
10 |
11 |
12 |
13 | I love lamp 14 | 55 |
56 |

57 | Do you really love the lamp, or are you just saying it because you 58 | saw it? 59 |

60 |
61 | 121 | 193 |
194 |
195 |
196 |
197 | ); 198 | 199 | export default Footer; 200 | -------------------------------------------------------------------------------- /src/components/global/HeaderNav.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | import CartCounter from '../Cart/CartCounter'; 5 | 6 | const HeaderNav = () => ( 7 |
8 | 12 |
13 | 14 | I love lamp 15 | 63 | 92 | 93 |
94 | 97 |
98 | ); 99 | 100 | export default HeaderNav; 101 | -------------------------------------------------------------------------------- /src/components/global/Loading.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Loading = () => ( 4 |
5 |
6 |
7 |
8 |
9 | 18 |

Loading

19 |
20 |
21 |
22 |
23 |
24 | ); 25 | 26 | export default Loading; 27 | -------------------------------------------------------------------------------- /src/components/global/MailingList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const MailingList = () => ( 4 |
5 |
6 |
7 |

8 | Do you love lamp? 9 |

10 |

11 | Sign up to recieve{' '} 12 | 13 | ILoveLamp 14 | {' '} 15 | product news, promotions and updates. 16 |

17 |
18 | 26 | 29 |
30 |
31 |
32 |
33 | ); 34 | 35 | export default MailingList; 36 | -------------------------------------------------------------------------------- /src/components/global/Mobile/MenuButton.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | function mapStateToProps(state) { 5 | return state; 6 | } 7 | 8 | class MenuButton extends Component { 9 | render() { 10 | let menu_btn_colour; 11 | 12 | const parsedPath = this.props.router.location.pathname.substring(1); 13 | 14 | if ( 15 | ['products', 'checkout', 'cart', 'order-confirmation'].includes( 16 | parsedPath 17 | ) 18 | ) { 19 | menu_btn_colour = ''; 20 | } else if (parsedPath.includes('product')) { 21 | menu_btn_colour = ''; 22 | } else { 23 | menu_btn_colour = 'light'; 24 | } 25 | 26 | return ( 27 | 59 | ); 60 | } 61 | } 62 | 63 | export default connect(mapStateToProps)(MenuButton); 64 | -------------------------------------------------------------------------------- /src/components/global/Mobile/MobileNav.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import MenuButton from './MenuButton'; 3 | import MobileNavMenu from './MobileNavMenu'; 4 | import { connect } from 'react-redux'; 5 | 6 | function mapStatetoProps(state) { 7 | return state; 8 | } 9 | 10 | class Nav extends React.Component { 11 | toggle = () => { 12 | this.setState({ isMenuOpen: !this.state.isMenuOpen }); 13 | }; 14 | 15 | close = () => { 16 | this.setState({ isMenuOpen: false }); 17 | }; 18 | 19 | click = () => { 20 | console.log('You clicked an item'); 21 | }; 22 | 23 | render() { 24 | return ( 25 |
26 | 27 | 28 |
29 |
30 | ); 31 | } 32 | } 33 | 34 | export default connect(mapStatetoProps)(Nav); 35 | -------------------------------------------------------------------------------- /src/components/global/Mobile/MobileNavMenu.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | const NavMenu = () => ( 5 | 15 | ); 16 | 17 | export default NavMenu; 18 | -------------------------------------------------------------------------------- /src/components/global/NotFound.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import CartHeader from '../Cart/CartHeader'; 4 | import MobileNav from '../global/Mobile/MobileNav'; 5 | import Footer from './Footer'; 6 | 7 | const NotFound = () => ( 8 |
9 | 10 | 11 |
12 |
13 |
14 |

404

15 |

Uh oh, the bulb went out!

16 | 17 | See the light 18 | 19 |
20 |
21 |
22 |
23 |
24 | ); 25 | 26 | export default NotFound; 27 | -------------------------------------------------------------------------------- /src/ducks/cart.js: -------------------------------------------------------------------------------- 1 | export const FETCH_CART_START = 'cart/FETCH_CART_START'; 2 | export const FETCH_CART_END = 'cart/FETCH_CART_END'; 3 | export const CART_UPDATED = 'cart/CART_UPDATED'; 4 | 5 | const initialState = { 6 | cart: null, 7 | fetching: false, 8 | fetched: false, 9 | error: null, 10 | empty: true, 11 | newQuantity: false 12 | }; 13 | 14 | export default (state = initialState, action) => { 15 | switch (action.type) { 16 | case FETCH_CART_START: 17 | return { 18 | ...state, 19 | fetching: true, 20 | fetched: false, 21 | newQuantity: action.gotNew 22 | }; 23 | 24 | case FETCH_CART_END: 25 | return { 26 | ...state, 27 | cart: action.payload, 28 | fetched: true, 29 | fetching: false, 30 | newQuantity: action.gotNew 31 | }; 32 | 33 | case CART_UPDATED: 34 | return { 35 | ...state, 36 | newQuantity: action.gotNew 37 | }; 38 | 39 | default: 40 | return { ...state, newQuantity: false }; 41 | } 42 | }; 43 | 44 | export const fetchCartStart = () => ({ 45 | type: FETCH_CART_START 46 | }); 47 | 48 | export const fetchCartEnd = cart => ({ 49 | type: FETCH_CART_END, 50 | payload: cart 51 | }); 52 | 53 | export const GetCartItems = () => (dispatch, getState, api) => { 54 | dispatch(fetchCartStart()); 55 | 56 | return api.GetCartItems().then(cart => dispatch(fetchCartEnd(cart))); 57 | }; 58 | 59 | export const addToCart = (productId, quantity) => (dispatch, getState, api) => { 60 | return api.AddCart(productId, quantity); 61 | }; 62 | -------------------------------------------------------------------------------- /src/ducks/categories.js: -------------------------------------------------------------------------------- 1 | export const FETCH_CATEGORIES_START = 'categories/FETCH_CATEGORIES_START'; 2 | export const FETCH_CATEGORIES_END = 'categories/FETCH_CATEGORIES_END'; 3 | 4 | const initialState = { 5 | fetching: false, 6 | fetched: false, 7 | categories: null, 8 | error: null 9 | }; 10 | 11 | export default (state = initialState, action) => { 12 | switch (action.type) { 13 | case FETCH_CATEGORIES_START: 14 | return { ...state, fetching: true }; 15 | 16 | case FETCH_CATEGORIES_END: 17 | return { 18 | ...state, 19 | fetching: false, 20 | fetched: true, 21 | categories: action.payload 22 | }; 23 | 24 | default: 25 | return { ...state, fetching: false }; 26 | } 27 | }; 28 | 29 | export const fetchCategoriesStart = () => ({ 30 | type: FETCH_CATEGORIES_START 31 | }); 32 | 33 | export const fetchCategoriesEnd = data => ({ 34 | type: FETCH_CATEGORIES_END, 35 | payload: data 36 | }); 37 | 38 | export const GetCategories = () => (dispatch, getState, api) => { 39 | dispatch(fetchCategoriesStart()); 40 | 41 | return api 42 | .GetCategories() 43 | .then(categories => dispatch(fetchCategoriesEnd(categories))); 44 | }; 45 | -------------------------------------------------------------------------------- /src/ducks/checkout.js: -------------------------------------------------------------------------------- 1 | export const SUBMIT_FORM = 'checkout/SUBMIT_FORM'; 2 | 3 | const initialState = { 4 | form: null, 5 | error: null 6 | }; 7 | 8 | export default (state = initialState, action) => { 9 | switch (action.type) { 10 | case SUBMIT_FORM: 11 | return { ...state, form: action.payload }; 12 | 13 | default: 14 | return { ...state }; 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /src/ducks/collections.js: -------------------------------------------------------------------------------- 1 | export const FETCH_COLLECTIONS_START = 'collections/FETCH_COLLECTIONS_START'; 2 | export const FETCH_COLLECTIONS_END = 'collections/FETCH_COLLECTIONS_END'; 3 | 4 | const initialState = { 5 | fetching: false, 6 | fetched: false, 7 | collections: null, 8 | error: null 9 | }; 10 | 11 | export default (state = initialState, action) => { 12 | switch (action.type) { 13 | case FETCH_COLLECTIONS_START: 14 | return { ...state, fetching: true }; 15 | 16 | case FETCH_COLLECTIONS_END: 17 | return { 18 | ...state, 19 | fetching: false, 20 | fetched: true, 21 | collections: action.payload 22 | }; 23 | 24 | default: 25 | return { ...state, fetching: false }; 26 | } 27 | }; 28 | 29 | export const fetchCollectionsStart = () => ({ 30 | type: FETCH_COLLECTIONS_START 31 | }); 32 | 33 | export const fetchCollectionsEnd = data => ({ 34 | type: FETCH_COLLECTIONS_END, 35 | payload: data 36 | }); 37 | 38 | export const GetCollections = () => (dispatch, getState, api) => { 39 | dispatch(fetchCollectionsStart()); 40 | 41 | return api 42 | .GetCollections() 43 | .then(collections => dispatch(fetchCollectionsEnd(collections))); 44 | }; 45 | -------------------------------------------------------------------------------- /src/ducks/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { reducer as formReducer } from 'redux-form'; 3 | import { routerReducer } from 'react-router-redux'; 4 | 5 | import products from './products'; 6 | import product from './product'; 7 | import collections from './collections'; 8 | import cart from './cart'; 9 | import categories from './categories'; 10 | import checkout from './checkout'; 11 | import styles from './styles'; 12 | import payments from './payments'; 13 | 14 | const rootReducer = combineReducers({ 15 | product, 16 | products, 17 | collections, 18 | cart, 19 | categories, 20 | checkout, 21 | styles, 22 | payments, 23 | router: routerReducer, 24 | form: formReducer 25 | }); 26 | 27 | export default rootReducer; 28 | -------------------------------------------------------------------------------- /src/ducks/payments.js: -------------------------------------------------------------------------------- 1 | export const SUBMIT_PAYMENT = 'payments/SUBMIT_PAYMENT'; 2 | export const PAYMENT_COMPLETE = 'payments/PAYMENT_COMPLETE'; 3 | 4 | const initialState = { 5 | processing: false, 6 | complete: false, 7 | error: null 8 | }; 9 | 10 | export default (state = initialState, action) => { 11 | switch (action.type) { 12 | case SUBMIT_PAYMENT: 13 | return { ...state, processing: true, complete: false }; 14 | 15 | case PAYMENT_COMPLETE: 16 | return { ...state, processing: false, complete: true }; 17 | 18 | default: 19 | return { ...state }; 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /src/ducks/product.js: -------------------------------------------------------------------------------- 1 | export const UPDATE_QUANTITY = 'product/UPDATE_QUANTITY'; 2 | 3 | const initialState = { 4 | quantity: 1 5 | }; 6 | 7 | export default (state = initialState, action) => { 8 | switch (action.type) { 9 | case UPDATE_QUANTITY: 10 | return { ...state, quantity: action.payload }; 11 | 12 | default: 13 | return { ...state }; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/ducks/products.js: -------------------------------------------------------------------------------- 1 | export const FETCH_PRODUCTS_START = 'products/FETCH_PRODUCTS_START'; 2 | export const FETCH_PRODUCTS_END = 'products/FETCH_PRODUCTS_END'; 3 | 4 | const initialState = { 5 | fetching: false, 6 | fetched: false, 7 | products: null, 8 | error: null 9 | }; 10 | 11 | export default (state = initialState, action) => { 12 | switch (action.type) { 13 | case FETCH_PRODUCTS_START: 14 | return { ...state, fetching: true }; 15 | 16 | case FETCH_PRODUCTS_END: 17 | return { 18 | ...state, 19 | fetching: false, 20 | fetched: true, 21 | products: action.payload 22 | }; 23 | 24 | default: 25 | return { ...state, fetching: false }; 26 | } 27 | }; 28 | 29 | export const fetchProductsStart = () => ({ 30 | type: FETCH_PRODUCTS_START 31 | }); 32 | 33 | export const fetchProductsEnd = data => ({ 34 | type: FETCH_PRODUCTS_END, 35 | payload: data 36 | }); 37 | 38 | export const GetProducts = resources => (dispatch, getState, api) => { 39 | dispatch(fetchProductsStart()); 40 | 41 | return api 42 | .GetProducts(resources) 43 | .then(products => dispatch(fetchProductsEnd(products))); 44 | }; 45 | -------------------------------------------------------------------------------- /src/ducks/styles.js: -------------------------------------------------------------------------------- 1 | export const SET_STYLE = 'styles/SET_STYLE'; 2 | 3 | const initialState = { 4 | style: 'Bright', 5 | header: 'Bright', 6 | error: null 7 | }; 8 | 9 | export default (state = initialState, action) => { 10 | switch (action.type) { 11 | case SET_STYLE: 12 | return { ...state, style: action.style, header: action.style }; 13 | 14 | default: 15 | return { ...state }; 16 | } 17 | }; 18 | 19 | export const setStyle = style => ({ 20 | type: SET_STYLE, 21 | style, 22 | header: style 23 | }); 24 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { ConnectedRouter } from 'react-router-redux'; 5 | 6 | import store, { history } from './store'; 7 | import App from './components/App'; 8 | 9 | import './index.css'; 10 | 11 | const target = document.getElementById('root'); 12 | 13 | render( 14 | 15 | 16 |
17 | 18 |
19 |
20 |
, 21 | target 22 | ); 23 | -------------------------------------------------------------------------------- /src/moltin.js: -------------------------------------------------------------------------------- 1 | const MoltinGateway = require('@moltin/sdk').gateway; 2 | 3 | let client_id = 'j6hSilXRQfxKohTndUuVrErLcSJWP15P347L6Im0M4'; 4 | 5 | if (process.env.REACT_APP_MOLTIN_CLIENT_ID) { 6 | client_id = process.env.REACT_APP_MOLTIN_CLIENT_ID; 7 | } 8 | 9 | const Moltin = MoltinGateway({ 10 | client_id, 11 | application: 'react-demo-store' 12 | }); 13 | 14 | export const GetProducts = () => 15 | Moltin.Products.With('files, main_images, collections').All(); 16 | 17 | export const GetProduct = ID => Moltin.Products.Get(ID); 18 | 19 | export const GetCategories = () => Moltin.Categories.With('products').All(); 20 | 21 | export const GetCategory = ID => Moltin.Categories.Get(ID); 22 | 23 | export const GetCollections = () => Moltin.Collections.With('products').All(); 24 | 25 | export const GetBrands = () => Moltin.Brands.All(); 26 | 27 | export const GetFile = ID => Moltin.Files.Get(ID); 28 | 29 | export const AddCart = (id, quantity) => Moltin.Cart().AddProduct(id, quantity); 30 | 31 | export const UpdateCartPlus = (ID, quantity) => 32 | Moltin.Cart().UpdateItemQuantity(ID, quantity + 1); 33 | 34 | export const UpdateCartMinus = (ID, quantity) => 35 | Moltin.Cart().UpdateItemQuantity(ID, quantity - 1); 36 | 37 | export const UpdateCart = (ID, quantity) => 38 | Moltin.Cart().UpdateItemQuantity(ID, quantity); 39 | 40 | export const GetCartItems = () => Moltin.Cart().Items(); 41 | 42 | export const Checkout = data => Moltin.Cart().Checkout(data); 43 | 44 | export const GetOrder = ID => Moltin.Orders.Get(ID); 45 | 46 | export const OrderPay = (ID, data) => Moltin.Orders.Payment(ID, data); 47 | 48 | export const DeleteCart = () => Moltin.Cart().Delete(); 49 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | // import the ability to modify browser history within our router 2 | import createHistory from 'history/createBrowserHistory'; 3 | 4 | // import our logger for redux 5 | import { createLogger } from 'redux-logger'; 6 | 7 | // import a library to handle async with redux 8 | import thunk from 'redux-thunk'; 9 | 10 | // import the redux parts needed to start our store 11 | import { createStore, applyMiddleware, compose } from 'redux'; 12 | 13 | // import the middleware for using react router with redux 14 | import { routerMiddleware } from 'react-router-redux'; 15 | 16 | // import the already combined reducers for redux to use 17 | import rootReducer from './ducks'; 18 | 19 | // import moltin API wrapper for use with Redux 20 | import * as api from './moltin'; 21 | 22 | // create and export history for router 23 | export const history = createHistory(); 24 | 25 | // combine the middlewares we're using into a constant so that it can be used by our store 26 | const middleware = [thunk.withExtraArgument(api), routerMiddleware(history)]; 27 | 28 | // declare any enhancers here 29 | const enhancers = []; 30 | 31 | // use Redux devtools if available in development 32 | if (process.env.NODE_ENV === 'development') { 33 | const devToolsExtension = window.devToolsExtension; 34 | 35 | if (typeof devToolsExtension === 'function') { 36 | enhancers.push(devToolsExtension()); 37 | } 38 | 39 | middleware.push(createLogger()); 40 | } 41 | 42 | // compose our middleware 43 | const composedEnhancers = compose(applyMiddleware(...middleware), ...enhancers); 44 | 45 | // create our redux store using our reducers and our middleware, and export it for use in index.js 46 | const store = createStore(rootReducer, composedEnhancers); 47 | 48 | export default store; 49 | -------------------------------------------------------------------------------- /static.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "build/", 3 | "clean_urls": false, 4 | "routes": { 5 | "/**": "index.html" 6 | } 7 | } 8 | --------------------------------------------------------------------------------