├── .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 |
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 | [](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 |
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 |
--------------------------------------------------------------------------------
/src/assets/img/footer/i-love.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/img/footer/we-love.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
20 |
--------------------------------------------------------------------------------
/src/assets/img/logo/ill-short-dark.svg:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/src/assets/img/logo/ill-short-white.svg:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/src/assets/img/logo/ill-white.svg:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------
/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 |
39 |
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 |
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 |
25 | Cart (
26 |
27 | The cart contains
28 | {quantity}
29 | items.
30 |
31 | )
32 |
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 |
28 |
68 |
69 |
70 |
97 |
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 |
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 |
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 |
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 |

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 |
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 |
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 |
23 | ) ||
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 |
39 | );
40 | } catch (e) {
41 | return
;
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 |
40 | );
41 | } else {
42 | return (
43 |
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 |
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 |
90 |
91 |
{product.name}
92 |
93 | Manufactured By{' '}
94 |
95 | ILoveLamp
96 |
97 |
98 | {isThereACurrencyPrice()}
99 |
100 |
Product details:
101 |
{product.description}
102 |
103 |
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 |
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 |
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 |
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 |
51 |
56 |
57 |
58 | );
59 | } else {
60 | return (
61 |
62 |
63 |
64 |
65 |
66 |
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 |
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 |
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 |
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 |
16 |

22 |
62 |
63 |
64 |
91 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------