├── .babelrc
├── .circleci
├── bin
│ ├── calibre-deploy.sh
│ ├── docker-tags
│ └── should-run-snyk.sh
└── config.yml
├── .dockerignore
├── .editorconfig
├── .env.example
├── .env.prod
├── .eslintignore
├── .github
├── ISSUE_TEMPLATE
│ └── bug_report.md
└── pull_request_template.md
├── .gitignore
├── .graphqlrc
├── .husky
├── .gitignore
└── commit-msg
├── .nvmrc
├── .prettierrc
├── .reaction
└── project-hooks
│ ├── README.md
│ ├── post-build
│ ├── post-project-start
│ ├── post-system-start
│ └── pre-build
├── .snyk
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README.md
├── apiUtils
├── headerLanguage.js
├── localeMiddleware.js
└── redirect.js
├── bin
├── fix-volumes
├── package-link
├── package-unlink
├── package-update
├── setup
├── start
└── wait-for.sh
├── components
├── AccountDropdown
│ ├── AccountDropdown.js
│ └── index.js
├── Breadcrumbs
│ ├── Breadcrumbs.js
│ └── index.js
├── Cart
│ ├── Cart.js
│ ├── CartToggle.js
│ └── index.js
├── CartItems
│ ├── CartItems.js
│ └── index.js
├── CartPopover
│ ├── CartPopover.js
│ └── index.js
├── CatalogGridItem
│ ├── CatalogGridItem.js
│ └── index.js
├── CheckoutActions
│ ├── CheckoutActions.js
│ └── index.js
├── CheckoutButtons
│ ├── CheckoutButtons.js
│ └── index.js
├── CheckoutSummary
│ ├── CheckoutSummary.js
│ └── index.js
├── Divider
│ ├── Divider.js
│ └── index.js
├── Entry
│ ├── ChangePassword.js
│ ├── Entry.js
│ ├── EntryModal.js
│ ├── ForgotPassword.js
│ ├── Login.js
│ ├── ResetPassword.js
│ ├── SignUp.js
│ └── index.js
├── Footer
│ ├── Footer.js
│ ├── Footer.test.js
│ └── index.js
├── Header
│ ├── Header.tsx
│ └── index.ts
├── Layout
│ ├── Layout.js
│ └── index.js
├── Link
│ ├── Link.js
│ └── index.js
├── LocaleDropdown
│ ├── LocaleDropdown.js
│ └── index.js
├── MediaGallery
│ ├── MediaGallery.js
│ └── index.js
├── MediaGalleryItem
│ ├── MediaGalleryItem.js
│ └── index.js
├── MiniCart
│ ├── MiniCart.js
│ └── index.js
├── NavigationDesktop
│ ├── NavigationDesktop.js
│ ├── NavigationItemDesktop.js
│ └── index.js
├── NavigationMobile
│ ├── NavigationItemMobile.js
│ ├── NavigationMobile.js
│ ├── NavigationSubMenuMobile.js
│ ├── NavigationToggleMobile.js
│ └── index.js
├── OrderCard
│ ├── OrderCard.js
│ └── index.js
├── OrderCardFulfillmentGroup
│ ├── OrderCardFulfillmentGroup.js
│ └── index.js
├── OrderCardHeader
│ ├── OrderCardHeader.js
│ └── index.js
├── OrderCardStatusBadge
│ ├── OrderCardStatusBadge.js
│ └── index.js
├── OrderCardSummary
│ ├── OrderCardSummary.js
│ └── index.js
├── OrderFulfillmentGroup
│ ├── OrderFulfillmentGroup.js
│ └── index.js
├── OrderSummary
│ ├── OrderSummary.js
│ └── index.js
├── PageLoading
│ ├── PageLoading.js
│ └── index.js
├── PageSizeSelector
│ ├── PageSizeSelector.js
│ └── index.js
├── PageStepper
│ ├── PageStepper.js
│ └── index.js
├── ProductDetail
│ ├── ProductDetail.js
│ └── index.js
├── ProductDetailAddToCart
│ ├── ProductDetailAddToCart.js
│ └── index.js
├── ProductDetailDescription
│ ├── ProductDetailDescription.js
│ └── index.js
├── ProductDetailOption
│ ├── ProductDetailOption.js
│ ├── index.js
│ └── styles.js
├── ProductDetailOptionsList
│ ├── ProductDetailOptionsList.js
│ └── index.js
├── ProductDetailPrice
│ ├── ProductDetailPrice.js
│ └── index.js
├── ProductDetailTitle
│ ├── ProductDetailTitle.js
│ └── index.js
├── ProductDetailVendor
│ ├── ProductDetailVendor.js
│ └── index.js
├── ProductGrid
│ ├── ProductGrid.js
│ ├── ProductGridEmptyMessage.js
│ └── index.js
├── ProductGridHero
│ ├── ProductGridHero.js
│ └── index.js
├── ProductGridTitle
│ ├── ProductGridTitle.js
│ └── index.js
├── Profile
│ ├── Profile.js
│ ├── ProfileContainer.js
│ ├── index.js
│ └── viewer.gql
├── ProfileAddressBook
│ ├── ProfileAddressBook.js
│ └── index.js
├── ProfileMenu
│ ├── ProfileMenu.js
│ └── index.js
├── ProfileOrders
│ ├── ProfileOrders.js
│ └── index.js
├── ProgressiveImage
│ ├── ProgressiveImage.js
│ └── index.js
├── Select
│ ├── Select.js
│ └── index.js
├── Social
│ ├── FacebookSocial.js
│ ├── TwitterSocial.js
│ └── index.js
├── SortBySelector
│ ├── SortBySelector.js
│ └── index.js
├── StripeCard
│ ├── .gitignore
│ ├── StripeCard.js
│ ├── StripeInput.js
│ ├── hooks
│ │ ├── createStripePaymentIntent.gql
│ │ └── useStripePaymentIntent.js
│ ├── index.js
│ ├── package.json
│ └── provider
│ │ └── StripeWrapper.js
├── VariantItem
│ ├── VariantItem.js
│ └── index.js
└── VariantList
│ ├── VariantList.js
│ └── index.js
├── config.js
├── containers
├── address
│ ├── withAddressBook.js
│ └── withAddressValidation.js
├── cart
│ └── withCart.js
├── catalog
│ ├── catalogItems.gql
│ └── withCatalogItems.js
└── order
│ ├── withOrder.js
│ └── withOrders.js
├── context
├── AuthContext.js
├── CartContext.js
├── ContextProviders.js
├── LocaleContext.js
├── RoutingContext.js
├── ShopContext.js
├── TagsContext.js
└── UIContext.js
├── custom
├── README.md
├── analytics
│ ├── index.js
│ ├── provider.example.js
│ └── segment.js
├── componentsContext.js
├── favicons.js
├── paymentMethods.js
└── reactionTheme.js
├── docker-compose.dev.yml
├── docker-compose.yml
├── docs
├── README.md
├── architecture
│ ├── README.md
│ └── decisions
│ │ └── README.md
├── cart.md
├── components
│ └── breadcrumbs.md
├── page-architecture.md
├── static-assets.md
├── tags.md
├── testing.md
├── theming.md
└── tracking-events.md
├── hocs
├── inject.js
└── withTranslation.js
├── hooks
├── address
│ ├── mutations.gql
│ ├── query.gql
│ ├── useAddAccountAddressBookEntry.js
│ ├── useAddressValidation.js
│ ├── useRemoveAccountAddressBookEntry.js
│ └── useUpdateAccountAddressBookEntry.js
├── availablePaymentMethods
│ ├── availablePaymentMethods.gql
│ └── useAvailablePaymentMethods.js
├── cart
│ ├── fragments.gql
│ ├── mutations.gql
│ ├── queries.gql
│ └── useCart.js
├── globalStores
│ ├── useAuthStore.js
│ ├── useCartStore.js
│ ├── useRoutingStore.js
│ └── useUIStore.js
├── orders
│ ├── fragments.gql
│ ├── placeOrder.gql
│ ├── queries.gql
│ ├── useOrder.js
│ └── useOrders.js
├── shop
│ └── useShop.js
├── useStores.js
├── useTags.js
├── useTranslation.js
└── viewer
│ ├── useViewer.js
│ └── viewer.gql
├── lib
├── accountsServer.js
├── apollo
│ ├── apolloClient.js
│ ├── omitVariableTypenameLink.js
│ └── withApollo.js
├── tracking
│ ├── constants.js
│ ├── dispatch.js
│ ├── track.js
│ ├── trackCartItems.js
│ ├── trackCheckout.js
│ ├── trackCheckoutStep.js
│ ├── trackOrder.js
│ ├── trackProduct.js
│ ├── trackProductClicked.js
│ ├── trackProductListViewed.js
│ └── utils
│ │ ├── getCartItemTrackingData.js
│ │ ├── getCartItemTrackingData.test.js
│ │ ├── getProductListTrackingData.js
│ │ ├── getProductListTrackingData.test.js
│ │ ├── getProductTrackingData.js
│ │ ├── getProductTrackingData.test.js
│ │ ├── getVariantTrackingData.js
│ │ └── getVariantTrackingData.test.js
└── utils
│ ├── SharedPropTypes.js
│ ├── __mocks__
│ └── productData.mock.js
│ ├── calculateRemainderDue.js
│ ├── calculateRemainderDue.test.js
│ ├── cartItemsConnectionToArray.js
│ ├── cartItemsConnectionToArray.test.js
│ ├── decoding.js
│ ├── encoding.js
│ ├── executionEnv.js
│ ├── hashPassword.js
│ ├── isProductBestseller.js
│ ├── isProductBestseller.test.js
│ ├── isProductLowQuantity.js
│ ├── isProductLowQuantity.test.js
│ ├── isProductOnSale.js
│ ├── isProductOnSale.test.js
│ ├── locales.json
│ ├── pageSizes.js
│ ├── pagination.js
│ ├── pagination.test.js
│ ├── priceByCurrencyCode.js
│ ├── priceByCurrencyCode.test.js
│ ├── relayConnectionToArray.js
│ ├── relayConnectionToArray.test.js
│ ├── variantById.js
│ ├── variantById.test.js
│ └── withLocales.js
├── next-env.d.ts
├── next.config.js
├── package.json
├── pages
├── [lang]
│ ├── cart.js
│ ├── cart
│ │ ├── checkout.js
│ │ └── login.js
│ ├── checkout
│ │ └── order.js
│ ├── index.js
│ ├── login.js
│ ├── product
│ │ └── [...slugOrId].js
│ ├── profile
│ │ ├── address.js
│ │ └── orders.js
│ └── tag
│ │ └── [slug].js
├── _app.js
├── _document.js
├── _error.js
└── api
│ ├── detectLanguage.js
│ ├── detectLanguage
│ └── [...slug].js
│ └── sitemap.js
├── public
├── favicons
│ ├── apple-touch-icon.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ ├── manifest.json
│ └── safari-pinned-tab.svg
└── images
│ └── placeholder.gif
├── setupTests.js
├── staticUtils
├── catalog
│ ├── catalogItemProduct.js
│ └── fetchCatalogProduct.js
├── graphQLRequest.js
├── shop
│ ├── fetchPrimaryShop.js
│ └── primaryShop.js
├── tag
│ ├── fetchTag.js
│ └── tag.js
├── tags
│ ├── fetchAllTags.js
│ └── tags.js
└── translations
│ └── fetchTranslations.js
├── translations
├── config.js
├── i18nRouter.js
├── isLocale.js
└── locales
│ ├── de
│ ├── common.js
│ ├── index.js
│ └── productDetail.js
│ ├── en
│ ├── common.js
│ ├── index.js
│ └── productDetail.js
│ └── index.js
├── tsconfig.json
├── types
├── material.d.ts
└── reaction.d.ts
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "next/babel"
4 | ]
5 | }
--------------------------------------------------------------------------------
/.circleci/bin/calibre-deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | URL=$1
6 | LOCATION=$2
7 |
8 | # Run One Off Test
9 | npx calibre@2.0.2 test create $URL --location=$LOCATION
10 |
11 | # Run Snapshot
12 | # California Snapshot Only (Be more generic as we add more site locations to track)
13 | if [ $LOCATION = "California" ]
14 | then
15 | npx calibre@2.0.2 site create-snapshot --site reaction-core-"$(echo $LOCATION | tr '[A-Z]' '[a-z]')"
16 | else
17 | echo "No Snapshot Configured for Location"
18 | fi
19 |
--------------------------------------------------------------------------------
/.circleci/bin/docker-tags:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # outputs each tag we're attaching to this docker image
3 | set -e
4 |
5 | SHA=$1
6 | BRANCH=$2
7 | SHA1=$(git rev-parse --verify "${SHA}")
8 |
9 | # Echo to stderr
10 | echoerr() { echo "$@" 1>&2; }
11 |
12 | # Ensure that git SHA was provided
13 | if [[ -x "${SHA1}" ]]; then
14 | echoerr "Error, no git SHA provided"
15 | exit 1;
16 | fi
17 |
18 | # tag with the branch
19 | if [[ -n "${BRANCH}" ]]; then
20 | echo "${BRANCH}"
21 | fi
22 |
23 | # Tag with each git tag
24 | git show-ref --tags -d | grep "^${SHA1}" | sed -e 's,.* refs/tags/,,' -e 's/\^{}//' 2> /dev/null \
25 | | xargs -I % \
26 | echo "%"
27 |
28 | # Tag with latest if certain conditions are met
29 | if [[ "$BRANCH" == "trunk" ]]; then
30 | # Check to see if we have a valid `vX.X.X` tag and assign to CURRENT_TAG
31 | CURRENT_TAG=$( \
32 | git show-ref --tags -d \
33 | | grep "^${SHA1}" \
34 | | sed -e 's,.* refs/tags/,,' -e 's/\^{}//' \
35 | | grep "^v[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+$" \
36 | | sort \
37 | )
38 |
39 | # Find the highest tagged version number
40 | HIGHEST_TAG=$(git --no-pager tag | grep "^v[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+$" | sort -r | head -n 1)
41 |
42 | # We tag :latest only if
43 | # 1. We have a current tag
44 | # 2. The current tag is equal to the highest tag, OR the highest tag does not exist
45 | if [[ -n "${CURRENT_TAG}" ]]; then
46 | if [[ "${CURRENT_TAG}" == "${HIGHEST_TAG}" ]] || [[ -z "${HIGHEST_TAG}" ]]; then
47 | echo "latest"
48 | fi
49 | fi
50 | fi
51 |
--------------------------------------------------------------------------------
/.circleci/bin/should-run-snyk.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Please Use Google Shell Style: https://google.github.io/styleguide/shell.xml
4 |
5 | # ---- Start unofficial bash strict mode boilerplate
6 | # http://redsymbol.net/articles/unofficial-bash-strict-mode/
7 | set -o errexit # always exit on error
8 | set -o errtrace # trap errors in functions as well
9 | set -o pipefail # don't ignore exit codes when piping output
10 | set -o posix # more strict failures in subshells
11 | # set -x # enable debugging
12 |
13 | IFS=$'\n\t'
14 | # ---- End unofficial bash strict mode boilerplate
15 |
16 | validate_env() {
17 | declare -a missing
18 | for var in "$@"; do
19 | if [[ -z "${!var}" ]]; then
20 | echo "⚠️ ERROR: Missing required environment variable: ${var}" 1>&2
21 | missing+=("${var}")
22 | fi
23 | done
24 | if [[ -n "${missing[*]}" ]]; then
25 | exit 1
26 | fi
27 | }
28 |
29 | main() {
30 | validate_env CIRCLE_COMPARE_URL DOCKER_REPOSITORY
31 | if [[ -z "${CIRCLE_PULL_REQUEST}" ]]; then
32 | echo "NO: Not a PR. Skipping Snyk."
33 | exit
34 | fi
35 | # Determine PR number from pull request link
36 | CIRCLE_PR_NUMBER="${CIRCLE_PR_NUMBER:-${CIRCLE_PULL_REQUEST##*/}}"
37 | PATH="${PATH}:${CIRCLE_WORKING_DIRECTORY}/node_modules/.bin"
38 | if [[ -v CIRCLE_PR_NUMBER ]] && [ -n ${CIRCLE_PR_NUMBER} ]; then
39 | # Get PR from github API
40 | url="https://api.github.com/repos/${DOCKER_REPOSITORY}/pulls/${CIRCLE_PR_NUMBER}"
41 | # Determine target/base branch from API response
42 | TARGET_BRANCH=$(curl --silent --location --fail --show-error "${url}" |
43 | jq -r '.base.ref')
44 | fi
45 | if [[ -z "${TARGET_BRANCH}" || ${TARGET_BRANCH} == "null" ]]; then
46 | echo "NO: Not a PR. Skipping Snyk."
47 | exit
48 | fi
49 | # If target branch does not exist or is trunk, run snyk tests
50 | if [[ ${TARGET_BRANCH} == "trunk" ]] || [[ -z "${TARGET_BRANCH/[ ]*\n/}" ]]; then
51 | echo "YES: always run when targeting trunk"
52 | exit
53 | fi
54 | # If package.json is different from the base branch, run snyk
55 | if git diff "$(basename "${CIRCLE_COMPARE_URL}")" package.json | grep -q diff; then
56 | echo "YES: package.json different. Running Snyk."
57 | exit
58 | fi
59 | echo "NO: package.json identical to target branch. Skipping Snyk."
60 | exit
61 | }
62 |
63 | main "$@"
64 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [*.js]
15 | max_line_length = 120
16 | indent_brace_style = 1TBS
17 | spaces_around_operators = true
18 | quote_type = double
19 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | CANONICAL_URL=http://localhost:4000
2 | BUILD_GRAPHQL_URL=http://localhost:3000/graphql
3 | EXTERNAL_GRAPHQL_URL=http://localhost:3000/graphql
4 | INTERNAL_GRAPHQL_URL=http://api.reaction.localhost:3000/graphql
5 | PORT=4000
6 | SEGMENT_ANALYTICS_SKIP_MINIMIZE=true
7 | SEGMENT_ANALYTICS_WRITE_KEY=ENTER_KEY_HERE
8 | SESSION_MAX_AGE_MS=2592000000
9 | SESSION_SECRET=CHANGEME
10 | STRIPE_PUBLIC_API_KEY=ENTER_STRIPE_PUBLIC_KEY_HERE
--------------------------------------------------------------------------------
/.env.prod:
--------------------------------------------------------------------------------
1 | CANONICAL_URL=http://localhost:4000
2 | BUILD_GRAPHQL_URL=http://localhost:3000/graphql
3 | EXTERNAL_GRAPHQL_URL=http://localhost:3000/graphql
4 | INTERNAL_GRAPHQL_URL=http://api.reaction.localhost:3000/graphql
5 | PORT=4000
6 | SEGMENT_ANALYTICS_SKIP_MINIMIZE=true
7 | SEGMENT_ANALYTICS_WRITE_KEY=ENTER_KEY_HERE
8 | SESSION_MAX_AGE_MS=2592000000
9 | SESSION_SECRET=CHANGEME
10 | STRIPE_PUBLIC_API_KEY=ENTER_STRIPE_PUBLIC_KEY_HERE
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*
2 | node_modules/*
3 | reports/*
4 | /**/*.d.ts
5 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 |
5 | ---
6 |
7 | Type: **breaking|critical|major|minor**
8 |
9 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 |
12 | **To Reproduce**
13 | Steps to reproduce the behavior:
14 | 1. Go to '...'
15 | 2. Click on '....'
16 | 3. Scroll down to '....'
17 | 4. See error
18 |
19 | **Expected behavior**
20 | A clear and concise description of what you expected to happen.
21 |
22 | **Screenshots**
23 | If applicable, add screenshots to help explain your problem.
24 |
25 | **Desktop (please complete the following information):**
26 | - OS: [e.g. iOS]
27 | - Browser [e.g. chrome, safari]
28 | - Version [e.g. 22]
29 |
30 | **Smartphone (please complete the following information):**
31 | - Device: [e.g. iPhone6]
32 | - OS: [e.g. iOS8.1]
33 | - Browser [e.g. stock browser, safari]
34 | - Version [e.g. 22]
35 |
36 | **Additional context**
37 | Add any other context about the problem here.
38 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | Resolves #issueNumber
2 | Impact: **breaking|critical|major|minor**
3 | Type: **feature|bugfix|performance|test|style|refactor|docs|chore**
4 |
5 | ## Issue
6 |
7 | Description of the issue this PR is solving, why it's happening, and how to reproduce it. This may differ from the original ticket as you now have more information at your disposal.
8 |
9 | ## Solution
10 |
11 | Summarize your solution to the problem. Please include short descriptions of any solutions you tested before arriving at your final solution. This will help reviewers know why you decided to solve this problem in this particular way and will speed up the review process.
12 |
13 | If you're solving a UIX related issue, please attach screen-caps or gifs showing how your solution differs from the issue.
14 |
15 | ## Breaking changes
16 |
17 | If you have a breaking changes, list them here, otherwise list none.
18 |
19 | Examples of breaking changes include changing file names, moving files, deleting files, renaming functions or exports, or changes to code which might cause previous versions of Reaction or third-party code not to work as expected.
20 |
21 | Note any work that you did to mitigate the effect of any breaking changes such as creating migrations, deprecation warnings, etc.
22 |
23 | ## Testing
24 |
25 | 1. List the steps needed for testing your change in this section.
26 | 2. Assume that testers already know how to start the app, and do the basic setup tasks.
27 | 3. Be detailed enough that someone can work through it without being too granular
28 |
29 | This project uses [semantic-release](https://semantic-release.gitbook.io/semantic-release/), please use their [commit message format.](https://semantic-release.gitbook.io/semantic-release/#commit-message-format).
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .fileStorage/
3 | .vscode
4 | .idea
5 | .c9
6 | .env*
7 | !.env.example*
8 | !.env.prod*
9 | *.csv
10 | *.dat
11 | *.gz
12 | *.log
13 | *.out
14 | *.pid
15 | *.seed
16 | *.sublime-project
17 | *.sublime-workspace
18 | browser.config.js
19 |
20 | lib-cov
21 | logs
22 | node_modules
23 | npm-debug.log
24 | pids
25 | results
26 | allure-results
27 | package-lock.json
28 |
29 | .reaction/config.json
30 |
31 | .next/*
32 | src/.next/*
33 | build
34 | /reports
35 |
36 | docker-compose.override.yml
37 |
38 | # Yalc
39 | .yalc/
40 | yalc.lock
41 | yalc-packages
42 |
--------------------------------------------------------------------------------
/.graphqlrc:
--------------------------------------------------------------------------------
1 | {
2 | "request": {
3 | "url": "http://localhost:3000/graphql"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx commitlint -e
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 12.14.1
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": false,
6 | "bracketSpacing": true,
7 | "arrowParens": "always"
8 | }
9 |
--------------------------------------------------------------------------------
/.reaction/project-hooks/README.md:
--------------------------------------------------------------------------------
1 | This directory contains project hooks for the Reaction development environment.
2 | They are invoked from the reaction-next build tools which operate across all
3 | projects to aid with orchestration.
4 |
5 | These hooks provide means for developers of an application to ensure that the
6 | application configure itself without higher-level coordination outside of the
7 | project.
8 |
9 | ## Included Hooks
10 |
11 | | Name | Description |
12 | | -------------------- | ---------------------------------------------------- |
13 | | `pre-build` | Invoked before Docker build. |
14 | | `post-build` | Invoked after Docker build and before project start. |
15 | | `post-project-start` | Invoked after project start. |
16 | | `post-system-start` | Invoked after entire system start. |
17 |
18 | More information can be found in the header documentation of each script.
19 |
20 | ## General Best Practices
21 |
22 | ### Check that Services Are Available Before Using Them
23 |
24 | The `post-project-start` and `post-system-start` hooks are called after the
25 | services are started with Docker. Though they have been started, it's possible
26 | that they will not yet be available to perform any script actions. This depends
27 | on the startup time for you application.
28 |
29 | This can lead to race conditions if you try to use a service in a hook script.
30 |
31 | Always check that any service is available before using it.
32 |
33 | Tools like [await](https://github.com/betalo-sweden/await) can help.
34 |
35 | ### Keep Hook Scripts Lightweight
36 |
37 | It can be tempting to add code directly to the hook script to directly perform
38 | a task.
39 |
40 | It is better to create a script in your project and call it from the hook. This
41 | keeps the scripted action reusable and available outside of the hook context.
42 |
--------------------------------------------------------------------------------
/.reaction/project-hooks/post-build:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Post Build Hook
3 | # Invoked by the reaction-next project bootstrapping process.
4 | #
5 | # Invoked after Docker build.
6 | # Perform any actions here that are required after docker-compose build but
7 | # before the project is started.
8 | #
9 | # Important Notes:
10 | #
11 | # - Expect that services are NOT running at this time.
12 | # - Do not assume that this hook script will run from this local directory.
13 | # The $__dir var is provided for convenience and may be used to invoke other
14 | # scripts.
15 | # - It is good practice to keep this script lightweight and invoke setup
16 | # scripts in your project.
17 |
18 | __current_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
19 | __root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
20 | __root_name=$(basename "${__root_dir}")
21 |
22 | echo "${__root_name} post-build script invoked." 2>&1
23 |
--------------------------------------------------------------------------------
/.reaction/project-hooks/post-project-start:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Post Project Start Hook
3 | # Invoked by the reaction-next project bootstrapping process.
4 | #
5 | # Invoked after this service is started. Can be used for project specific
6 | # actions that should be performed after the project is running, like database
7 | # setup, migrations, seeds, etc. Do not depend on other projects in this hook!
8 | #
9 | # Important Notes:
10 | #
11 | # - The hook runs after all Docker Compose services in THIS project are
12 | # started. Though started, there is no guarantee that these services are
13 | # ready (i.e. that they will respond to requests.) It is your responsibility
14 | # to test that services are available before using them to avoid race
15 | # conditions.
16 | # - Do not assume that this hook script will run from this local directory.
17 | # The $__dir var is provided for convenience and may be used to invoke other
18 | # scripts.
19 | # - It is good practice to keep this script lightweight and invoke setup
20 | # scripts in your project.
21 |
22 | __current_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
23 | __root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
24 | __root_name=$(basename "${__root_dir}")
25 |
26 | echo "${__root_name} post-project-start script invoked." 2>&1
27 |
--------------------------------------------------------------------------------
/.reaction/project-hooks/post-system-start:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Post System Start Hook
3 | # Invoked by the reaction-next project bootstrapping process.
4 | #
5 | # Invoked after all services in the system have been started.
6 | #
7 | # Important Notes:
8 | #
9 | # - The hook runs after all Docker Compose services in ALL projects are
10 | # started. Though started, there is no guarantee that these services are
11 | # ready (i.e. that they will respond to requests.) It is your responsibility
12 | # to test that services are available before using them to avoid race
13 | # conditions.
14 | # - Do not assume that this hook script will run from this local directory.
15 | # The $__dir var is provided for convenience and may be used to invoke other
16 | # scripts.
17 | # - It is good practice to keep this script lightweight and invoke setup
18 | # scripts in your project.
19 |
20 | __current_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
21 | __root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
22 | __root_name=$(basename "${__root_dir}")
23 |
24 | echo "${__root_name} post-system-start script invoked." 2>&1
25 |
--------------------------------------------------------------------------------
/.reaction/project-hooks/pre-build:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Pre Build Hook
3 | # Invoked by the reaction-next project bootstrapping process.
4 | #
5 | # Invoked before Docker build.
6 | # Perform any actions here that are required before docker-compose build. For
7 | # example, copying values from .env.example to .env.
8 | #
9 | # Important Notes:
10 | #
11 | # - Expect that services are NOT running at this time.
12 | # - Do not assume that this hook script will run from this local directory.
13 | # The $__dir var is provided for convenience and may be used to invoke other
14 | # scripts.
15 | # - It is good practice to keep this script lightweight and invoke setup
16 | # scripts in your project.
17 |
18 | __current_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
19 | __root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
20 | __root_name=$(basename "${__root_dir}")
21 |
22 | echo "${__root_name} post-project-start script invoked." 2>&1
23 |
24 | "${__root_dir}/bin/setup"
25 |
--------------------------------------------------------------------------------
/.snyk:
--------------------------------------------------------------------------------
1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
2 | version: v1.13.5
3 | patch: {}
4 | # ignores vulnerabilities until expiry date; change duration by modifying expiry date
5 | ignore:
6 |
7 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:12-alpine
2 |
3 | ARG NEXTJS_DOTENV
4 |
5 | ENV NEXTJS_DOTENV=$NEXTJS_DOTENV
6 |
7 | # hadolint ignore=DL3018
8 | RUN apk --no-cache add bash curl less tini vim make
9 | SHELL ["/bin/bash", "-o", "pipefail", "-o", "errexit", "-u", "-c"]
10 |
11 | WORKDIR /usr/local/src/app
12 | ENV PATH=$PATH:/usr/local/src/app/node_modules/.bin
13 |
14 | # Allow yarn/npm to create ./node_modules
15 | RUN chown node:node .
16 |
17 | # Copy specific things so that we can keep the image as small as possible
18 | # without relying on each repo to include a .dockerignore file.
19 | COPY --chown=node:node ./ ./
20 |
21 | USER node
22 |
23 | # Install ALL dependencies. We need devDependencies for the build command.
24 | RUN yarn install --production=false --frozen-lockfile --ignore-scripts --non-interactive --no-cache
25 |
26 | ENV BUILD_ENV=production NODE_ENV=production
27 |
28 | # hadolint ignore=SC2046
29 | RUN export $(grep -v '^#' .env.${NEXTJS_DOTENV:-prod} | xargs -0) && yarn build
30 |
31 | # Install only prod dependencies now that we've built, to make the image smaller
32 | RUN rm -rf node_modules/*
33 | RUN yarn install --production=true --frozen-lockfile --ignore-scripts --non-interactive
34 |
35 | # If any Node flags are needed, they can be set in the NODE_OPTIONS env variable.
36 | CMD ["tini", "--", "yarn", "start"]
37 | LABEL com.reactioncommerce.name="example-storefront"
38 |
--------------------------------------------------------------------------------
/apiUtils/headerLanguage.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Look up the accepted languages in the HTTP headers
3 | *
4 | * @param {Object} req - the current request
5 | * @returns {Array} A list of accepted languages
6 | */
7 | export default function headerLookup(req) {
8 | let found;
9 |
10 | if (typeof req !== "undefined") {
11 | const { headers } = req;
12 | if (!headers) return found;
13 |
14 | const locales = [];
15 | const acceptLanguage = headers["accept-language"];
16 |
17 | if (acceptLanguage) {
18 | const languages = []; let index; let match;
19 | const rgx = /(([a-z]{2})-?([A-Z]{2})?)\s*;?\s*(q=([0-9.]+))?/gi;
20 |
21 | do {
22 | match = rgx.exec(acceptLanguage);
23 | if (match) {
24 | const lng = match[1]; const weight = match[5] || "1"; const priority = Number(weight);
25 | if (lng && !isNaN(priority)) {
26 | languages.push({ lng, priority });
27 | }
28 | }
29 | } while (match);
30 |
31 | languages.sort((langA, langB) => langB.priority - langA.priority);
32 |
33 | for (index = 0; index < languages.length; index += 1) {
34 | locales.push(languages[index].lng);
35 | }
36 |
37 | if (locales.length) found = locales;
38 | }
39 | }
40 |
41 | return found;
42 | }
43 |
--------------------------------------------------------------------------------
/apiUtils/localeMiddleware.js:
--------------------------------------------------------------------------------
1 | import headerLanguage from "./headerLanguage";
2 | import redirect from "./redirect";
3 |
4 | export default (req, res) => {
5 | const {
6 | query: { slug, ...rest }
7 | } = req;
8 |
9 | const fallback = "de";
10 | const allowedLocales = [
11 | { name: "de-DE", locale: "de" },
12 | { name: "de", locale: "de" },
13 | { name: "en-AU", locale: "en" },
14 | { name: "en-IN", locale: "en" },
15 | { name: "en-CA", locale: "en" },
16 | { name: "en-NZ", locale: "en" },
17 | { name: "en-US", locale: "en" },
18 | { name: "en-ZA", locale: "en" },
19 | { name: "en-GB", locale: "en" },
20 | { name: "en", locale: "en" }
21 | ];
22 |
23 | const detections = headerLanguage(req);
24 |
25 | let found;
26 |
27 | if (detections && detections.length) {
28 | detections.forEach((language) => {
29 | if (found || typeof language !== "string") return;
30 |
31 | const lookedUpLocale = allowedLocales.find((allowedLocale) => allowedLocale.name === language);
32 |
33 | if (lookedUpLocale) {
34 | found = lookedUpLocale.locale;
35 | }
36 | });
37 | }
38 |
39 | if (!found) {
40 | found = fallback;
41 | }
42 |
43 | const queryPart = rest ? (`?${Object.keys(rest).map((k) => `${k}=${rest[k]}`).join("&")}`) : "";
44 |
45 | if (slug) {
46 | return redirect(res, 302, `/${found}${slug ? `/${slug.join("/")}` : ""}${queryPart}`);
47 | }
48 |
49 | return redirect(res, 302, `/${found}${queryPart}`);
50 | };
51 |
--------------------------------------------------------------------------------
/apiUtils/redirect.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Redirects to a new given location
3 | *
4 | * @param {Object} res - response
5 | * @param {String} statusCode - HTTP response status code
6 | * @param {String} location - the new location
7 | * @returns {Object} the updated response object
8 | */
9 | export default function redirect(res, statusCode, location) {
10 | if (!res) {
11 | throw new Error("Response object required");
12 | }
13 |
14 | if (!statusCode) {
15 | throw new Error("Status code required");
16 | }
17 |
18 | if (!location) {
19 | throw new Error("Location required");
20 | }
21 |
22 | res.statusCode = statusCode;
23 | res.setHeader("Location", location);
24 | res.end();
25 | }
26 |
--------------------------------------------------------------------------------
/bin/fix-volumes:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Please Use Google Shell Style: https://google.github.io/styleguide/shell.xml
4 |
5 | # ---- Start unofficial bash strict mode boilerplate
6 | # http://redsymbol.net/articles/unofficial-bash-strict-mode/
7 | set -o errexit # always exit on error
8 | set -o errtrace # trap errors in functions as well
9 | set -o pipefail # don't ignore exit codes when piping output
10 | set -o posix # more strict failures in subshells
11 | # set -x # enable debugging
12 |
13 | IFS=$'\n\t'
14 | # ---- End unofficial bash strict mode boilerplate
15 | cd "$(dirname "${BASH_SOURCE[0]}")/.."
16 | docker-compose run --entrypoint=./.reaction/fix-volumes.sh web --force
17 |
--------------------------------------------------------------------------------
/bin/package-unlink:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Please Use Google Shell Style: https://google.github.io/styleguide/shell.xml
4 |
5 | # ---- Start unofficial bash strict mode boilerplate
6 | # http://redsymbol.net/articles/unofficial-bash-strict-mode/
7 | set -o errexit # always exit on error
8 | set -o errtrace # trap errors in functions as well
9 | set -o pipefail # don't ignore exit codes when piping output
10 | set -o posix # more strict failures in subshells
11 | # set -x # enable debugging
12 |
13 | # ---- End unofficial bash strict mode boilerplate
14 |
15 | package_name=$1
16 |
17 | # validate input
18 | IFS='/'
19 | read -a pkgarr <<< "$1"
20 | org_name=${pkgarr[0]}
21 |
22 | #check if the organization name is valid by cross referencing with node_modules
23 | is_package=$(find node_modules -type d -name $org_name)
24 | if [ -z "$is_package" ]; then
25 | echo "$org_name does not exist"
26 | exit 0
27 | fi
28 |
29 | IFS="$(printf "\n\t")"
30 |
31 | # Unlink the yalc dependency, remove the package drectory and npm install
32 | echo "Unlinking package from Example Storefront..."
33 | docker-compose exec web sh -c "cd /usr/local/src/app && yalc remove ${package_name}"
34 | docker-compose exec web sh -c "rm -rf /usr/local/src/app/node_modules/${package_name} && npm i"
35 | docker-compose exec web sh -c "cd /usr/local/src/app && yalc update"
36 |
37 | # Fool nodemon into thinking something has changed so that it restarts.
38 | # Touch first file found in /pages with .js extension
39 | echo "Restarting Example Storefront..."
40 | docker-compose exec web sh -c "touch -c $(ls ./pages/*.js | head -n1)"
41 |
--------------------------------------------------------------------------------
/bin/package-update:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Please Use Google Shell Style: https://google.github.io/styleguide/shell.xml
4 |
5 | # ---- Start unofficial bash strict mode boilerplate
6 | # http://redsymbol.net/articles/unofficial-bash-strict-mode/
7 | set -o errexit # always exit on error
8 | set -o errtrace # trap errors in functions as well
9 | set -o pipefail # don't ignore exit codes when piping output
10 | set -o posix # more strict failures in subshells
11 | # set -x # enable debugging
12 |
13 | IFS="$(printf "\n\t")"
14 | # ---- End unofficial bash strict mode boilerplate
15 |
16 | # Unlink the yalc dependency, remove the package drectory and npm install
17 | echo "Updating package from Example Storefront..."
18 | docker-compose exec web sh -c "cd /usr/local/src/app && yalc update"
19 |
20 | # Fool nodemon into thinking something has changed so that it restarts.
21 | # Touch first file found in /src with .js extension
22 | echo "Restarting Example Storefront..."
23 | docker-compose exec web sh -c "touch -c $(ls ./src/*.js | head -n1)"
24 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | cd "$(dirname "${BASH_SOURCE[0]}")/.."
4 | env_file=./.env
5 | env_example_file=./.env.example
6 |
7 | function main {
8 | set -e
9 |
10 | add_new_env_vars
11 | }
12 |
13 | function add_new_env_vars {
14 | # create .env and set perms if it does not exist
15 | [ ! -f "${env_file}" ] && { touch "${env_file}" ; chmod 0600 "${env_file}" ; }
16 |
17 | export IFS=$'\n'
18 | for var in $(cat "${env_example_file}"); do
19 | key="${var%%=*}" # get var key
20 | var=$(command echo "$var") # generate dynamic values
21 |
22 | # If .env doesn't contain this env key, add it
23 | if ! $(grep -qLE "^$key=" "${env_file}"); then
24 | echo "Adding $key to .env"
25 | echo "$var" >> "${env_file}"
26 | fi
27 | done
28 | }
29 |
30 | main
31 |
--------------------------------------------------------------------------------
/bin/start:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | set -o errexit # always exit on error
4 | set -o pipefail # don't ignore exit codes when piping output
5 | # set -x # enable debugging
6 |
7 | IFS="$(printf "\n\t")"
8 |
9 | cd "$(dirname "$0")/.."
10 | yarn install
11 |
12 | yarn start:dev
--------------------------------------------------------------------------------
/bin/wait-for.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | TIMEOUT=15
4 | QUIET=0
5 |
6 | echoerr() {
7 | if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi
8 | }
9 |
10 | usage() {
11 | exitcode="$1"
12 | cat << USAGE >&2
13 | Usage:
14 | $cmdname host:port [-t timeout] [-- command args]
15 | -q | --quiet Do not output any status messages
16 | -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout
17 | -- COMMAND ARGS Execute command with args after the test finishes
18 | USAGE
19 | exit "$exitcode"
20 | }
21 |
22 | wait_for() {
23 | for i in `seq $TIMEOUT` ; do
24 | nc -z "$HOST" "$PORT" > /dev/null 2>&1
25 |
26 | result=$?
27 | if [ $result -eq 0 ] ; then
28 | if [ $# -gt 0 ] ; then
29 | exec "$@"
30 | fi
31 | exit 0
32 | fi
33 | sleep 1
34 | done
35 | echo "Operation timed out" >&2
36 | exit 1
37 | }
38 |
39 | while [ $# -gt 0 ]
40 | do
41 | case "$1" in
42 | *:* )
43 | HOST=$(printf "%s\n" "$1"| cut -d : -f 1)
44 | PORT=$(printf "%s\n" "$1"| cut -d : -f 2)
45 | shift 1
46 | ;;
47 | -q | --quiet)
48 | QUIET=1
49 | shift 1
50 | ;;
51 | -t)
52 | TIMEOUT="$2"
53 | if [ "$TIMEOUT" = "" ]; then break; fi
54 | shift 2
55 | ;;
56 | --timeout=*)
57 | TIMEOUT="${1#*=}"
58 | shift 1
59 | ;;
60 | --)
61 | shift
62 | break
63 | ;;
64 | --help)
65 | usage 0
66 | ;;
67 | *)
68 | echoerr "Unknown argument: $1"
69 | usage 1
70 | ;;
71 | esac
72 | done
73 |
74 | if [ "$HOST" = "" -o "$PORT" = "" ]; then
75 | echoerr "Error: you need to provide a host and port to test."
76 | usage 2
77 | fi
78 |
79 | wait_for "$@"
80 |
--------------------------------------------------------------------------------
/components/AccountDropdown/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./AccountDropdown";
2 |
--------------------------------------------------------------------------------
/components/Breadcrumbs/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./Breadcrumbs";
2 |
--------------------------------------------------------------------------------
/components/Cart/Cart.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import inject from "hocs/inject";
4 | import { withStyles } from "@material-ui/core/styles";
5 | import Drawer from "@material-ui/core/Drawer";
6 |
7 | const styles = () => ({
8 | cart: {
9 | width: "90vw"
10 | }
11 | });
12 |
13 | class Cart extends Component {
14 | static propTypes = {
15 | classes: PropTypes.object,
16 | uiStore: PropTypes.shape({
17 | closeCart: PropTypes.func
18 | }).isRequired
19 | };
20 |
21 | static defaultProps = {
22 | classes: {}
23 | };
24 |
25 | handleClose = () => {
26 | this.props.uiStore.closeCart();
27 | };
28 |
29 | render() {
30 | const { classes, uiStore } = this.props;
31 | return (
32 |
33 | Cart Component
34 |
35 | );
36 | }
37 | }
38 |
39 | export default withStyles(styles)(inject("uiStore")(Cart));
40 |
--------------------------------------------------------------------------------
/components/Cart/CartToggle.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import IconButton from "@material-ui/core/IconButton";
4 | import CartIcon from "mdi-material-ui/Cart";
5 |
6 | class CartToggle extends Component {
7 | static propTypes = {
8 | onClick: PropTypes.func
9 | };
10 |
11 | render() {
12 | return (
13 |
14 |
15 |
16 | );
17 | }
18 | }
19 |
20 | export default CartToggle;
21 |
--------------------------------------------------------------------------------
/components/Cart/index.js:
--------------------------------------------------------------------------------
1 | export { default as Cart } from "./Cart";
2 | export { default as CartToggle } from "./CartToggle";
3 |
--------------------------------------------------------------------------------
/components/CartItems/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./CartItems";
2 |
--------------------------------------------------------------------------------
/components/CartPopover/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./CartPopover";
2 |
--------------------------------------------------------------------------------
/components/CatalogGridItem/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./CatalogGridItem";
2 |
--------------------------------------------------------------------------------
/components/CheckoutActions/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./CheckoutActions";
2 |
--------------------------------------------------------------------------------
/components/CheckoutButtons/CheckoutButtons.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import Button from "@reactioncommerce/components/Button/v1";
4 | import Router from "translations/i18nRouter";
5 |
6 | export default class CheckoutButtons extends Component {
7 | static propTypes = {
8 | /**
9 | * Set to `true` to prevent the button from calling `onClick` when clicked
10 | */
11 | isDisabled: PropTypes.bool,
12 | /**
13 | * The NextJS route name for the primary checkout button.
14 | */
15 | primaryButtonRoute: PropTypes.string,
16 | /**
17 | * Text to display inside the button
18 | */
19 | primaryButtonText: PropTypes.string,
20 | /**
21 | * className for primary checkout button
22 | */
23 | primaryClassName: PropTypes.string
24 | }
25 |
26 | static defaultProps = {
27 | primaryButtonRoute: "/cart/checkout",
28 | primaryButtonText: "Checkout"
29 | };
30 |
31 | handleOnClick = () => {
32 | const { primaryButtonRoute } = this.props;
33 | Router.push(primaryButtonRoute);
34 | }
35 |
36 | render() {
37 | const {
38 | isDisabled,
39 | primaryClassName,
40 | primaryButtonText
41 | } = this.props;
42 |
43 | return (
44 |
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/components/CheckoutButtons/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./CheckoutButtons";
2 |
--------------------------------------------------------------------------------
/components/CheckoutSummary/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./CheckoutSummary";
2 |
--------------------------------------------------------------------------------
/components/Divider/Divider.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import { withStyles } from "@material-ui/core/styles";
4 | import Typography from "@material-ui/core/Typography";
5 |
6 | const styles = (theme) => ({
7 | container: {
8 | display: "flex",
9 | alignItems: "center"
10 | },
11 | label: {
12 | flex: 0,
13 | flexBasis: "auto",
14 | textTransform: "uppercase",
15 | fontWeight: theme.typography.fontWeightBold,
16 | fontSize: "0.7rem",
17 | paddingRight: theme.spacing(),
18 | paddingLeft: theme.spacing(),
19 | letterSpacing: "0.1rem"
20 | },
21 | item: {
22 | flex: "1 1 auto",
23 | border: 0,
24 | borderTop: "1px solid",
25 | borderColor: theme.palette.reaction.borderColor,
26 | marginTop: theme.spacing(2),
27 | marginBottom: theme.spacing(2)
28 | }
29 | });
30 |
31 | /**
32 | * A divider for variant options
33 | * @export
34 | * @class Divider
35 | */
36 | class Divider extends Component {
37 | static propTypes = {
38 | classes: PropTypes.object.isRequired,
39 | label: PropTypes.string
40 | }
41 |
42 | render() {
43 | const { classes: { container, item, label: labelClass }, label } = this.props;
44 |
45 | return (
46 |
47 |
48 | {!!label && {label}}
49 |
50 |
51 | );
52 | }
53 | }
54 |
55 | export default withStyles(styles)(Divider);
56 |
--------------------------------------------------------------------------------
/components/Divider/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./Divider";
2 |
--------------------------------------------------------------------------------
/components/Entry/EntryModal.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { makeStyles } from "@material-ui/core/styles";
4 | import Modal from "@material-ui/core/Modal";
5 | import useStores from "hooks/useStores";
6 | import Login from "../Entry/Login";
7 | import SignUp from "../Entry/SignUp";
8 | import ChangePassword from "../Entry/ChangePassword";
9 | import ForgotPassword from "../Entry/ForgotPassword";
10 | import ResetPassword from "../Entry/ResetPassword";
11 |
12 | const useStyles = makeStyles((theme) => ({
13 | paper: {
14 | position: "absolute",
15 | width: 380,
16 | backgroundColor: theme.palette.background.paper,
17 | border: "2px solid #000",
18 | boxShadow: theme.shadows[5],
19 | padding: theme.spacing(2, 4, 3),
20 | top: "50%",
21 | left: "50%",
22 | transform: "translate(-50%, -50%)",
23 | display: "flex",
24 | alignItems: "center",
25 | justifyContent: "center"
26 | }
27 | }));
28 |
29 | const EntryModal = ({ onClose, resetToken }) => {
30 | const classes = useStyles();
31 | const { uiStore } = useStores();
32 |
33 | const { entryModal, setEntryModal } = uiStore;
34 |
35 | const openModal = (value) => {
36 | setEntryModal(value);
37 | };
38 |
39 | const closeModal = () => {
40 | setEntryModal(null);
41 | onClose();
42 | };
43 |
44 | // eslint-disable-next-line react/no-multi-comp
45 | const getModalComponent = () => {
46 | let comp = Login;
47 | if (entryModal === "signup") {
48 | comp = SignUp;
49 | } else if (entryModal === "change-password") {
50 | comp = ChangePassword;
51 | } else if (entryModal === "forgot-password") {
52 | comp = ForgotPassword;
53 | } else if (entryModal === "reset-password") {
54 | comp = ResetPassword;
55 | }
56 | return React.createElement(comp, { closeModal, openModal, resetToken });
57 | };
58 |
59 | return (
60 |
61 | {getModalComponent()}
62 |
63 | );
64 | };
65 |
66 | EntryModal.propTypes = {
67 | onClose: PropTypes.func,
68 | resetToken: PropTypes.string
69 | };
70 |
71 | EntryModal.defaultProps = {
72 | onClose: () => null
73 | };
74 |
75 | export default EntryModal;
76 |
--------------------------------------------------------------------------------
/components/Entry/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./Entry";
2 |
--------------------------------------------------------------------------------
/components/Footer/Footer.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { withStyles } from "@material-ui/core/styles";
4 | import Typography from "@material-ui/core/Typography";
5 |
6 | const date = new Date();
7 |
8 | const styles = (theme) => ({
9 | footer: {
10 | alignItems: "center",
11 | display: "flex",
12 | justifyContent: "center",
13 | marginBottom: theme.spacing(2)
14 | }
15 | });
16 |
17 | const Footer = ({ ...props }) => (
18 |
23 | );
24 |
25 | Footer.propTypes = {
26 | classes: PropTypes.object
27 | };
28 |
29 | export default withStyles(styles, { name: "SkFooter" })(Footer);
30 |
--------------------------------------------------------------------------------
/components/Footer/Footer.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, screen } from "@testing-library/react";
3 | import Footer from "./Footer";
4 |
5 | test("Renders the footer", () => {
6 | render();
7 |
8 | const footerText = screen.getByText(/Reaction Commerce/);
9 | expect(footerText).toBeInTheDocument();
10 | });
11 |
--------------------------------------------------------------------------------
/components/Footer/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./Footer";
2 |
--------------------------------------------------------------------------------
/components/Header/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./Header";
2 |
--------------------------------------------------------------------------------
/components/Layout/Layout.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import { withStyles } from "@material-ui/core/styles";
4 | import Header from "components/Header";
5 | import Footer from "components/Footer";
6 |
7 | const styles = (theme) => ({
8 | root: {
9 | minHeight: "100vh"
10 | },
11 | main: {
12 | flex: "1 1 auto",
13 | maxWidth: theme.layout.mainContentMaxWidth,
14 | marginLeft: "auto",
15 | marginRight: "auto"
16 | },
17 | article: {
18 | padding: theme.spacing(3)
19 | }
20 | });
21 |
22 | class Layout extends Component {
23 | static propTypes = {
24 | children: PropTypes.node,
25 | classes: PropTypes.object,
26 | shop: PropTypes.shape({
27 | name: PropTypes.string.isRequired
28 | }),
29 | viewer: PropTypes.object
30 | };
31 |
32 | static defaultProps = {
33 | classes: {}
34 | };
35 |
36 | render() {
37 | const { classes, children, shop, viewer } = this.props;
38 |
39 | return (
40 |
41 |
42 |
43 |
44 | {children}
45 |
46 |
47 |
48 |
49 | );
50 | }
51 | }
52 |
53 | export default withStyles(styles)(Layout);
54 |
--------------------------------------------------------------------------------
/components/Layout/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./Layout";
2 |
--------------------------------------------------------------------------------
/components/Link/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./Link";
2 |
--------------------------------------------------------------------------------
/components/LocaleDropdown/LocaleDropdown.js:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from "react";
2 | import Router, { useRouter } from "next/router";
3 | import { makeStyles } from "@material-ui/core/styles";
4 | import Select from "@material-ui/core/Select";
5 | import MenuItem from "@material-ui/core/MenuItem";
6 | import useStores from "hooks/useStores";
7 | import { locales } from "translations/config";
8 | import useTranslation from "hooks/useTranslation";
9 |
10 | const useStyles = makeStyles((theme) => ({
11 | localeSelect: {
12 | margin: theme.spacing(0, 1)
13 | },
14 | localeSelectText: {
15 | fontSize: "1rem"
16 | }
17 | }));
18 |
19 | const LocaleDropdown = () => {
20 | const classes = useStyles();
21 | const router = useRouter();
22 | const { locale, t } = useTranslation("common"); // eslint-disable-line id-length
23 | const { uiStore } = useStores();
24 |
25 | const changeLanguage = useCallback(
26 | (language) => {
27 | const regex = new RegExp(`^/(${locales.join("|")})`);
28 | uiStore.setLanguage(language);
29 | Router.push(
30 | router.pathname,
31 | router.asPath.replace(regex, `/${language}`)
32 | );
33 | },
34 | [router]
35 | );
36 |
37 | return (
38 |
39 |
47 |
48 | );
49 | };
50 |
51 | export default LocaleDropdown;
52 |
--------------------------------------------------------------------------------
/components/LocaleDropdown/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./LocaleDropdown";
2 |
--------------------------------------------------------------------------------
/components/MediaGallery/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./MediaGallery";
2 |
--------------------------------------------------------------------------------
/components/MediaGalleryItem/MediaGalleryItem.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import ButtonBase from "@material-ui/core/ButtonBase";
4 | import withStyles from "@material-ui/core/styles/withStyles";
5 | import ProgressiveImage from "components/ProgressiveImage";
6 |
7 | const styles = () => ({
8 | root: {
9 | width: "100%"
10 | }
11 | });
12 |
13 | /**
14 | * Product detail media gallery item
15 | * @class ProductDetailMediaGalleryItem
16 | */
17 | class MediaGalleryItem extends Component {
18 | static propTypes = {
19 | /**
20 | * CSS class names
21 | */
22 | classes: PropTypes.object,
23 |
24 | /**
25 | * The 0-based integer position of this item within a group of MediaGalleryItems
26 | */
27 | index: PropTypes.number,
28 |
29 | /**
30 | * Product media
31 | */
32 | media: PropTypes.object,
33 |
34 | /**
35 | * Click callback
36 | * @example (event, media) => {}
37 | */
38 | onClick: PropTypes.func
39 | };
40 |
41 | static defaultProps = {
42 | onClick: () => { }
43 | };
44 |
45 | /**
46 | * Click handler for ButtonBase
47 | * @param {SyntheticEvent} event Event
48 | * @returns {undefined}
49 | */
50 | handleClick = (event) => {
51 | this.props.onClick(event, this.props.media, this.props.index);
52 | };
53 |
54 | render() {
55 | const { classes, media } = this.props;
56 |
57 | // If all props are undefined then skip rendering component
58 | if (!media) return null;
59 |
60 | return (
61 |
62 |
63 |
64 | );
65 | }
66 | }
67 |
68 | export default withStyles(styles)(MediaGalleryItem);
69 |
--------------------------------------------------------------------------------
/components/MediaGalleryItem/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./MediaGalleryItem";
2 |
--------------------------------------------------------------------------------
/components/MiniCart/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./MiniCart";
2 |
--------------------------------------------------------------------------------
/components/NavigationDesktop/NavigationDesktop.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import inject from "hocs/inject";
4 |
5 | import { NavigationItemDesktop } from "components/NavigationDesktop";
6 |
7 | class NavigationDesktop extends Component {
8 | static propTypes = {
9 | classes: PropTypes.object,
10 | navItems: PropTypes.object
11 | };
12 |
13 | static defaultProps = {
14 | classes: {},
15 | navItems: {}
16 | };
17 |
18 | renderNavItem(navItem, index) {
19 | return ;
20 | }
21 |
22 | render() {
23 | const { navItems } = this.props;
24 |
25 | if (navItems && navItems.items) {
26 | return ;
27 | }
28 |
29 | // If navItems.items aren't available, skip rendering of navigation
30 | return null;
31 | }
32 | }
33 |
34 | export default inject("navItems")(NavigationDesktop);
35 |
--------------------------------------------------------------------------------
/components/NavigationDesktop/index.js:
--------------------------------------------------------------------------------
1 | export { default as NavigationDesktop } from "./NavigationDesktop";
2 | export { default as NavigationItemDesktop } from "./NavigationItemDesktop";
3 |
--------------------------------------------------------------------------------
/components/NavigationMobile/NavigationSubMenuMobile.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import Divider from "@material-ui/core/Divider";
4 | import Toolbar from "@material-ui/core/Toolbar";
5 | import IconButton from "@material-ui/core/IconButton";
6 | import Typography from "@material-ui/core/Typography";
7 | import MenuList from "@material-ui/core/MenuList";
8 | import ChevronLeftIcon from "mdi-material-ui/ChevronLeft";
9 | import { withStyles } from "@material-ui/core/styles";
10 | import NavigationItemMobile from "./NavigationItemMobile";
11 |
12 | const styles = () => ({
13 | root: {
14 | display: "flex",
15 | flexDirection: "column",
16 | height: "100vh"
17 | },
18 | header: {
19 | flex: "0 0 auto"
20 | },
21 | toolbarTitle: {
22 | position: "absolute",
23 | width: "100%",
24 | textAlign: "center"
25 | },
26 | menu: {
27 | flex: "1 1 auto",
28 | overflowY: "auto"
29 | }
30 | });
31 |
32 | class NavigationSubMenuMobile extends Component {
33 | static propTypes = {
34 | classes: PropTypes.object,
35 | navItem: PropTypes.object,
36 | onBackButtonClick: PropTypes.func,
37 | routingStore: PropTypes.object
38 | };
39 |
40 | static defaultProps = {
41 | classes: {},
42 | navItem: {},
43 | onBackButtonClick() {},
44 | routingStore: {}
45 | };
46 |
47 | state = { isSubNavOpen: false };
48 |
49 | get hasSubNavItems() {
50 | const { navItem: { items } } = this.props;
51 | return Array.isArray(items) && items.length > 0;
52 | }
53 |
54 | render() {
55 | const { classes, navItem } = this.props;
56 |
57 | if (!navItem) {
58 | return null;
59 | }
60 |
61 | return (
62 |
63 |
64 |
65 | {navItem.navigationItem.data.contentForLanguage}
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | {this.hasSubNavItems &&
74 |
75 | {navItem.items.map((item, index) => (
76 |
77 | ))}
78 |
79 | }
80 |
81 |
82 | );
83 | }
84 | }
85 |
86 | export default withStyles(styles)(NavigationSubMenuMobile);
87 |
--------------------------------------------------------------------------------
/components/NavigationMobile/NavigationToggleMobile.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import IconButton from "@material-ui/core/IconButton";
4 | import MenuIcon from "mdi-material-ui/Menu";
5 |
6 | class NavigationToggleMobile extends Component {
7 | static propTypes = {
8 | onClick: PropTypes.func
9 | };
10 |
11 | render() {
12 | return (
13 |
14 |
15 |
16 | );
17 | }
18 | }
19 |
20 | export default NavigationToggleMobile;
21 |
--------------------------------------------------------------------------------
/components/NavigationMobile/index.js:
--------------------------------------------------------------------------------
1 | export { default as NavigationMobile } from "./NavigationMobile";
2 | export { default as NavigationItemMobile } from "./NavigationItemMobile";
3 | export { default as NavigationToggleMobile } from "./NavigationToggleMobile";
4 |
--------------------------------------------------------------------------------
/components/OrderCard/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./OrderCard";
2 |
--------------------------------------------------------------------------------
/components/OrderCardFulfillmentGroup/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./OrderCardFulfillmentGroup";
2 |
--------------------------------------------------------------------------------
/components/OrderCardHeader/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./OrderCardHeader";
2 |
--------------------------------------------------------------------------------
/components/OrderCardStatusBadge/OrderCardStatusBadge.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import { withStyles } from "@material-ui/core/styles";
4 | import Chip from "@material-ui/core/Chip";
5 |
6 | const styles = (theme) => ({
7 | orderStatusNew: {
8 | backgroundColor: `${theme.palette.reaction.darkBlue300}`,
9 | color: "white",
10 | fontWeight: "800"
11 | },
12 | orderStatusCanceled: {
13 | backgroundColor: `${theme.palette.reaction.red300}`,
14 | color: "white",
15 | fontWeight: "800"
16 | },
17 | orderStatusProcessing: {
18 | backgroundColor: `${theme.palette.reaction.darkBlue300}`,
19 | color: "white",
20 | fontWeight: "800"
21 | },
22 | orderStatusShipped: {
23 | backgroundColor: `${theme.palette.reaction.reactionBlue}`,
24 | color: "white",
25 | fontWeight: "800"
26 | }
27 | });
28 |
29 | class OrderCardStatusBadge extends Component {
30 | static propTypes = {
31 | classes: PropTypes.object,
32 | displayStatus: PropTypes.string.isRequired,
33 | status: PropTypes.string.isRequired
34 | };
35 |
36 | render() {
37 | const { classes, displayStatus, status } = this.props;
38 | let classess;
39 |
40 | if (status === "coreOrderWorkflow/canceled") {
41 | classess = classes.orderStatusCanceled;
42 | }
43 |
44 | if (status === "new") {
45 | classess = classes.orderStatusNew;
46 | }
47 |
48 | if (status === "coreOrderWorkflow/processing") {
49 | classess = classes.orderStatusProcessing;
50 | }
51 |
52 | if (status === "coreOrderWorkflow/completed") {
53 | classess = classes.orderStatusShipped;
54 | }
55 |
56 | return ;
57 | }
58 | }
59 |
60 | export default withStyles(styles, { withTheme: true })(OrderCardStatusBadge);
61 |
--------------------------------------------------------------------------------
/components/OrderCardStatusBadge/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./OrderCardStatusBadge";
2 |
--------------------------------------------------------------------------------
/components/OrderCardSummary/OrderCardSummary.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import CartSummary from "@reactioncommerce/components/CartSummary/v1";
4 |
5 | class OrderCardSummary extends Component {
6 | static propTypes = {
7 | classes: PropTypes.object,
8 | summary: PropTypes.shape({
9 | fulfillmentTotal: PropTypes.shape({
10 | displayAmount: PropTypes.string
11 | }),
12 | itemTotal: PropTypes.shape({
13 | displayAmount: PropTypes.string
14 | }),
15 | surchargeTotal: PropTypes.shape({
16 | displayAmount: PropTypes.string
17 | }),
18 | taxTotal: PropTypes.shape({
19 | displayAmount: PropTypes.string
20 | }),
21 | total: PropTypes.shape({
22 | displayAmount: PropTypes.string
23 | })
24 | })
25 | }
26 |
27 | render() {
28 | const { summary } = this.props;
29 |
30 | if (summary) {
31 | const {
32 | fulfillmentTotal,
33 | itemTotal,
34 | surchargeTotal,
35 | taxTotal,
36 | total
37 | } = summary;
38 |
39 | return (
40 |
48 | );
49 | }
50 |
51 | return null;
52 | }
53 | }
54 |
55 | export default OrderCardSummary;
56 |
--------------------------------------------------------------------------------
/components/OrderCardSummary/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./OrderCardSummary";
2 |
--------------------------------------------------------------------------------
/components/OrderFulfillmentGroup/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./OrderFulfillmentGroup";
2 |
--------------------------------------------------------------------------------
/components/OrderSummary/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./OrderSummary";
2 |
--------------------------------------------------------------------------------
/components/PageLoading/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./PageLoading";
2 |
--------------------------------------------------------------------------------
/components/PageSizeSelector/PageSizeSelector.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import Select from "components/Select";
4 | import { PAGE_SIZES } from "lib/utils/pageSizes";
5 | import useTranslation from "hooks/useTranslation";
6 |
7 | /**
8 | * Renders the page size selector
9 | *
10 | * @param {Integer} pageSize - the page size
11 | * @param {Function} onChange - the onChange page size handler
12 | * @returns {React.Component} The page size selector
13 | */
14 | function PageSizeSelector({ pageSize, onChange }) {
15 | const { t } = useTranslation("common"); // eslint-disable-line id-length
16 |
17 | return (
18 | ` tags may need to be updated as routes change. Add all page specific `