diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +node_modules diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..4be9699 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,77 @@ +{ + "extends": [ + "airbnb-typescript", + "prettier", + "prettier/react", + "plugin:import/errors", + "plugin:import/typescript" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "./tsconfig.json" + }, + "plugins": ["@typescript-eslint", "import", "react", "prettier"], + "rules": { + "@typescript-eslint/camelcase": "off", + "@typescript-eslint/indent": "off", + "@typescript-eslint/quotes": "off", + "comma-spacing": "off", + "import/no-cycle": "off", + "import/no-extraneous-dependencies": [ + "error", + { + "devDependencies": [ + "**/stories.tsx", + "**/test.ts", + "**/*.test.ts", + "**/test.tsx", + "**/tests.tsx", + "config/**/*", + "*.config.js", + "src/@next/pages/baseStory.ts", + "cypress/**/*" + ] + } + ], + "import/no-internal-modules": "off", + "import/no-named-as-default": "off", + "import/order": "error", + "import/prefer-default-export": "off", + "no-nested-ternary": "off", + "prettier/prettier": ["error"], + "react/jsx-one-expression-per-line": "off", + "react/jsx-props-no-spreading": "off", + "react/jsx-wrap-multilines": "off", + "react/prop-types": "off", + "sort-imports": "off", + "no-underscore-dangle": "off", + "react/state-in-constructor": "off", + "react/static-property-placement": "off", + + "sort-keys": "off", + "no-prototype-builtins": "off", + "no-shadow": "off", + "jsx-a11y/click-events-have-key-events": "off", + "consistent-return": "off", + "react/no-this-in-sfc": "off", + "array-callback-return": "off", + "no-plusplus": "off", + "react/no-did-update-set-state": "off", + "jsx-a11y/no-static-element-interactions": "off", + "react/destructuring-assignment": "off", + "react/button-has-type": "off", + "@typescript-eslint/no-use-before-define": "off", + "no-useless-escape": "off", + "jsx-a11y/no-noninteractive-element-interactions": "off", + "react/no-array-index-key": "off", + "no-param-reassign": "off", + "no-empty-pattern": "off", + "no-restricted-globals": "off", + "@typescript-eslint/no-unused-vars": "off" + }, + "settings": { + "import/resolver": { + "typescript": {} + } + } +} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..b9efc0d --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Restrict Test Environment Deployment Workflows to be Approved by Cloud Team +.github/workflows/test-env* @mirumee/saleor-cloud \ No newline at end of file diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..7a63f2e --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hello+community@mirumee.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..536ecfb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,22 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + +### What I'm trying to achieve +… + +### Steps to reproduce the problem +1. +2. + +### What I expected to happen +… + +### Screenshots + + +**System information** +Operating system: +Browser: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..c2a5d72 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +### What I'm trying to achieve +… + +### Describe a proposed solution +... + +### Other solutions I've tried and won't work +… + +### Screenshots or mockups + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..ac1be93 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,25 @@ +I want to merge this change because... + + + +### Screenshots + + + +### Pull Request Checklist + + + +1. [ ] All visible strings are translated with proper context. +1. [ ] All data-formatting is locale-aware (dates, numbers, and so on). +1. [ ] The changes are tested. +1. [ ] The code is documented (docstrings, project documentation). +1. [ ] Changes are mentioned in the changelog. + +### Test Environment Config + + + +API_URI=https://master.staging.saleor.rocks/graphql/ diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000..4584c57 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,60 @@ +# Configuration for probot-stale - https://github.com/probot/stale + +# Number of days of inactivity before an Issue or Pull Request becomes stale +daysUntilStale: 60 + +# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. +# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. +daysUntilClose: 7 + +# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) +onlyLabels: [] + +# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable +exemptLabels: + - bug + - blocker + - backlog + +# Set to true to ignore issues in a project (defaults to false) +exemptProjects: false + +# Set to true to ignore issues in a milestone (defaults to false) +exemptMilestones: false + +# Set to true to ignore issues with an assignee (defaults to false) +exemptAssignees: false + +# Label to use when marking as stale +staleLabel: stale + +# Comment to post when marking as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. + +# Comment to post when removing the stale label. +# unmarkComment: > +# Your comment here. + +# Comment to post when closing a stale Issue or Pull Request. +# closeComment: > +# Your comment here. + +# Limit the number of actions per hour, from 1-30. Default is 30 +limitPerRun: 30 +# Limit to only `issues` or `pulls` +# only: issues + +# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': +# pulls: +# daysUntilStale: 30 +# markComment: > +# This pull request has been automatically marked as stale because it has not had +# recent activity. It will be closed if no further activity occurs. Thank you +# for your contributions. + +# issues: +# exemptLabels: +# - confirmed diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..f6fce67 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,54 @@ +name: "CodeQL" + +on: + push: + branches: [master, ] + pull_request: + # The branches below must be a subset of the branches above + branches: [master] + schedule: + - cron: '0 22 * * 4' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + # Override language selection by uncommenting this and choosing your languages + # with: + # languages: go, javascript, csharp, python, cpp, java + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/test-env-cleanup.yml b/.github/workflows/test-env-cleanup.yml new file mode 100644 index 0000000..4ead051 --- /dev/null +++ b/.github/workflows/test-env-cleanup.yml @@ -0,0 +1,37 @@ +name: TEST-ENV-CLEANUP +# Remove test instance for closed pull requests + +on: + pull_request: + types: [closed] + branches: ["*"] + +jobs: + cleanup: + runs-on: ubuntu-latest + steps: + - uses: rlespinasse/github-slug-action@2.0.0 + - name: Set domain + # Set test instance domain based on branch name slug + run: | + echo "::set-env name=domain::${{ env.GITHUB_HEAD_REF_SLUG_URL }}.storefront.saleor.rocks" + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_DEFAULT_REGION }} + + - name: Remove S3 directory + run: aws s3 rm --recursive s3://${{ secrets.AWS_TEST_DEPLOYMENT_BUCKET }}/${{ env.domain }}/ + + - name: Invalidate cache + run: aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_TEST_CF_DIST_ID }} --paths "/${{ env.domain }}/*" + + - name: Mark deployment as deactivated + uses: bobheadxi/deployments@v0.4.2 + with: + step: deactivate-env + token: ${{ secrets.GITHUB_TOKEN }} + env: ${{ env.GITHUB_HEAD_REF_SLUG_URL }} diff --git a/.github/workflows/test-env-deploy.yml b/.github/workflows/test-env-deploy.yml new file mode 100644 index 0000000..70c6222 --- /dev/null +++ b/.github/workflows/test-env-deploy.yml @@ -0,0 +1,110 @@ +name: TEST-ENV-DEPLOYMENT +# Build and deploy test instance for every pull request + +on: + pull_request: + # trigger on "edited" to update instance when configuration changes in PR description + types: [opened, reopened, synchronize] + branches: ["*"] + +jobs: + deploy: + if: github.event.pull_request.head.repo.full_name == 'mirumee/saleor-storefront' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: rlespinasse/github-slug-action@2.0.0 + + - name: Start deployment + uses: bobheadxi/deployments@v0.4.2 + id: deployment + with: + step: start + token: ${{ secrets.GITHUB_TOKEN }} + env: ${{ env.GITHUB_HEAD_REF_SLUG_URL }} + ref: ${{ github.head_ref }} + + - name: Start storybook deployment + uses: bobheadxi/deployments@v0.4.2 + id: storybook-deployment + with: + step: start + token: ${{ secrets.GITHUB_TOKEN }} + env: storybook ${{ env.GITHUB_HEAD_REF_SLUG_URL }} + ref: ${{ github.head_ref }} + + - name: Cache node modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-qa-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-qa-${{ env.cache-name }}- + ${{ runner.os }}-qa- + ${{ runner.os }}- + + - name: Install deps + run: | + npm ci + + - name: Get custom API_URI + id: api_uri + # Search for API_URI in PR description + env: + pull_request_body: ${{ github.event.pull_request.body }} + prefix: API_URI= + pattern: (http|https)://[a-zA-Z0-9.-]+/graphql/? + run: | + echo "::set-output name=custom_api_uri::$(echo $pull_request_body | grep -Eo "$prefix$pattern" | sed s/$prefix// | head -n 1)" + + - name: Run build + env: + # Use custom API_URI or the default one + API_URI: ${{ steps.api_uri.outputs.custom_api_uri || 'https://master.staging.saleor.rocks/graphql/' }} + run: | + npm run build + + - name: Run build storybook + run: | + npm run build-storybook + + - name: Set domain + # Set test instance domain based on branch name slug + run: | + echo "::set-env name=domain::${{ env.GITHUB_HEAD_REF_SLUG_URL }}.storefront.saleor.rocks" + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_DEFAULT_REGION }} + + - name: Deploy to S3 + run: aws s3 sync ./dist s3://${{ secrets.AWS_TEST_DEPLOYMENT_BUCKET }}/${{ env.domain }} + + - name: Invalidate cache + run: aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_TEST_CF_DIST_ID }} --paths "/${{ env.domain }}/*" + + - name: Update deployment status + uses: bobheadxi/deployments@v0.4.2 + if: always() + with: + step: finish + token: ${{ secrets.GITHUB_TOKEN }} + status: ${{ job.status }} + env_url: https://${{ env.domain }}/ + deployment_id: ${{ steps.deployment.outputs.deployment_id }} + + - name: Update storybook deployment status + uses: bobheadxi/deployments@v0.4.2 + if: always() + with: + step: finish + token: ${{ secrets.GITHUB_TOKEN }} + status: ${{ job.status }} + env_url: https://${{ env.domain }}/storybook/index.html + deployment_id: ${{ steps.storybook-deployment.outputs.deployment_id }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..5fc2599 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,98 @@ +name: QA + +on: + pull_request: + branches: [ '*' ] + +jobs: + + check-lock: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + + - name: Validate lock file + run: | + npx lockfile-lint --path package-lock.json --allowed-hosts npm yarn + + tsc-and-linters: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Cache node modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + # npm cache files are stored in `~/.npm` on Linux/macOS + path: ~/.npm + key: ${{ runner.os }}-qa-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-qa-${{ env.cache-name }}- + ${{ runner.os }}-qa- + ${{ runner.os }}- + + - name: Install deps + run: | + npm ci + + - name: Run tsc + run: | + npm run tsc + + - name: Run linters + run: | + npm run lint + git diff --exit-code ./src + + jest-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Cache node modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + # npm cache files are stored in `~/.npm` on Linux/macOS + path: ~/.npm + key: ${{ runner.os }}-qa-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-qa-${{ env.cache-name }}- + ${{ runner.os }}-qa- + ${{ runner.os }}- + + - name: Install deps + run: | + npm ci + + - name: Run jest + run: | + npm run test + + cypress-run: + runs-on: ubuntu-16.04 + steps: + - name: Checkout + uses: actions/checkout@v1 + - name: Cypress run + uses: cypress-io/github-action@v2 + env: + API_URI: ${{ secrets.API_URI }} + APP_MOUNT_URI: ${{ secrets.APP_MOUNT_URI }} + CYPRESS_API_URI: ${{ secrets.CYPRESS_API_URI }} + CYPRESS_USER_NAME: ${{ secrets.CYPRESS_USER_NAME }} + CYPRESS_USER_PASSWORD: ${{ secrets.CYPRESS_USER_PASSWORD }} + with: + build: npm run build + start: npx http-server -a localhost -p 3000 dist --proxy http://localhost:3000/\? + wait-on: http://localhost:3000/ + wait-on-timeout: 120 + - uses: actions/upload-artifact@v1 + if: always() + with: + name: cypress-videos + path: cypress/videos diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1ec68d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +.* +!.dockerignore +!.storybook +!.circle +!.eslintrc +!.gitignore +!.github +!.babelrc +!.plop +!.jest +!.linguirc +!.prettierrc +!.prettierignore +!.tx +dist +node_modules +src/locales/_build + +cypress/videos +cypress/screenshots +cypress/fixtures/example.json diff --git a/.jest/config.json b/.jest/config.json new file mode 100644 index 0000000..e8ec080 --- /dev/null +++ b/.jest/config.json @@ -0,0 +1,33 @@ +{ + "rootDir": "../", + "setupFiles": ["/.jest/register-context.js"], + "//": "Please remove 'react-syntax-highlighter/dist/esm/(.*)' from moduleNameMapper when storybook is upgraded to v.6.0.0 or higher - there is a fix https://github.com/storybookjs/storybook/issues/9470", + "moduleNameMapper": { + "@temp/([^\\.]*)$": "/src/$1", + "@styles/([^\\.]*)$": "/src/@next/globalStyles/$1", + "@styles": "/src/@next/globalStyles/index.ts", + "@app/([^\\.]*)$": "/src/@next/$1", + "@sdk/([^\\.]*)$": "/src/@sdk/$1", + "@utils/([^\\.]*)$": "/src/@next/utils/$1", + "images/([^\\.]*.*)$": "/src/images/$1", + "@hooks": "/src/@next/hooks/index.ts", + "@components/atoms": "/src/@next/components/atoms/index.ts", + "@components/molecules": "/src/@next/components/molecules/index.ts", + "@components/organisms": "/src/@next/components/organisms/index.ts", + "@components/views": "/src/@next/components/views/index.ts", + "@components/containers": "/src/@next/components/containers/index.ts", + "@components/templates": "/src/@next/components/templates/index.ts", + "@types": "/src/@next/types/index.ts", + "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/__mocks__/fileMock.js", + "react-syntax-highlighter/dist/esm/(.*)": "react-syntax-highlighter/dist/cjs/$1" + }, + "setupFilesAfterEnv": ["/.jest/setupTests.js"], + "transform": { + "^.+\\.(j|t)sx?$": "babel-jest", + "^.+\\.mdx$": "@storybook/addon-docs/jest-transform-mdx", + "^.+\\.svg$": "jest-svg-transformer" + }, + "transformIgnorePatterns": [ + "/node_modules/(?!register-service-worker).+\\.js$" + ] +} diff --git a/.jest/register-context.js b/.jest/register-context.js new file mode 100644 index 0000000..f2dcd55 --- /dev/null +++ b/.jest/register-context.js @@ -0,0 +1,3 @@ +const registerRequireContextHook = require("babel-plugin-require-context-hook/register"); + +registerRequireContextHook(); diff --git a/.jest/setupTests.js b/.jest/setupTests.js new file mode 100644 index 0000000..6cffe71 --- /dev/null +++ b/.jest/setupTests.js @@ -0,0 +1,11 @@ +import { defaultTheme } from "@styles"; +import { configure } from "enzyme"; +import Adapter from "enzyme-adapter-react-16"; +import { ThemeConsumer } from "styled-components"; + +// set default theme for enzyme renderer +ThemeConsumer._currentValue = defaultTheme; +configure({ adapter: new Adapter() }); + +// silence all console.errors in tests +console.error = jest.fn(); diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..94f5760 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +gqlTypes \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0024943 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "arrowParens": "avoid", + "trailingComma": "es5" +} diff --git a/.storybook/.babelrc b/.storybook/.babelrc new file mode 100644 index 0000000..4e78b80 --- /dev/null +++ b/.storybook/.babelrc @@ -0,0 +1,9 @@ +{ + "env": { + "test": { + "presets": [["@babel/preset-env", { "modules": "commonjs" }]], + "plugins": ["require-context-hook"] + } + }, + "presets": ["@babel/preset-env", "@babel/preset-react"] +} diff --git a/.storybook/OutlineDecorator.jsx b/.storybook/OutlineDecorator.jsx new file mode 100644 index 0000000..44e77fc --- /dev/null +++ b/.storybook/OutlineDecorator.jsx @@ -0,0 +1,12 @@ +import React from "react"; + +import { GlobalStyle } from "../src/@next/globalStyles"; +import * as S from "./styles"; + +export const OutLineDecorator = storyFn => ( + + {storyFn()} + + + +); diff --git a/.storybook/__tests__/test.ts b/.storybook/__tests__/test.ts new file mode 100644 index 0000000..9a5a5e4 --- /dev/null +++ b/.storybook/__tests__/test.ts @@ -0,0 +1,22 @@ +import initStoryshots from "@storybook/addon-storyshots"; +import { multiSnapshotWithOptions } from "@storybook/addon-storyshots/dist/test-bodies"; +import styleSheetSerializer from "jest-styled-components/src/styleSheetSerializer"; +import { addSerializer } from "jest-specific-snapshot"; + +jest.mock("react-dom", () => ({ + createPortal: node => node, + findDOMNode: () => {}, +})); + +(global as any).matchMedia = () => ({ + addListener: jest.fn(), + matches: true, + removeListener: jest.fn(), +}); + +addSerializer(styleSheetSerializer); + +initStoryshots({ + framework: "react", + test: multiSnapshotWithOptions({}), +}); diff --git a/.storybook/addons.js b/.storybook/addons.js new file mode 100644 index 0000000..aada1f9 --- /dev/null +++ b/.storybook/addons.js @@ -0,0 +1,7 @@ +import "@storybook/addon-options/register"; +import "@storybook/addon-knobs/register"; +import "@storybook/addon-storysource/register"; +import "@storybook/addon-viewport/register"; +import "storybook-styled-components/register"; +import "@storybook/addon-actions/register"; +import "@storybook/addon-docs/register"; diff --git a/.storybook/main.js b/.storybook/main.js new file mode 100644 index 0000000..87f9a88 --- /dev/null +++ b/.storybook/main.js @@ -0,0 +1,81 @@ +const path = require("path"); +const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin"); +const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); + +module.exports = { + stories: ["../src/**/stories.tsx"], + addons: [ + "@storybook/addon-actions", + "@storybook/addon-storysource", + "@storybook/addon-knobs", + { + name: "@storybook/addon-docs", + options: { + configureJSX: true, + babelOptions: {}, + sourceLoaderOptions: null, + }, + }, + ], + + webpackFinal: async (config, { configType }) => { + config.module.rules.push({ + test: /\.(ts|tsx)$/, + use: [ + { + loader: "babel-loader", + options: { + configFile: "./babel.config.js", + }, + }, + { + loader: require.resolve("react-docgen-typescript-loader"), + options: { + // Providing the path to tsconfig.json so that stories can + // display types from outside each individual story. + tsconfigPath: path.resolve(__dirname, "../tsconfig.json"), + }, + }, + ], + }); + + config.module.rules.push({ + test: /\.(scss|css)$/, + use: [ + "style-loader", + { + loader: "css-loader", + options: { sourceMap: true }, + }, + { loader: "sass-loader" }, + ], + }); + + config.module.rules.push({ + test: /stories\.tsx?$/, + loaders: [require.resolve("@storybook/addon-storysource/loader")], + enforce: "pre", + }); + + config.resolve.extensions.push(".ts", ".tsx"); + config.resolve.plugins = [ + new TsconfigPathsPlugin({ + configFile: path.resolve(__dirname, "../tsconfig.json"), + }), + ]; + + config.resolve.modules = [ + ...(config.resolve.modules || []), + path.resolve("./"), + ]; + + config.plugins.push( + new ForkTsCheckerWebpackPlugin({ + eslint: true, + exclude: "node_modules", + }) + ); + + return config; + }, +}; diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html new file mode 100644 index 0000000..81a3c5b --- /dev/null +++ b/.storybook/preview-head.html @@ -0,0 +1,5 @@ + diff --git a/.storybook/preview.js b/.storybook/preview.js new file mode 100644 index 0000000..b846e8b --- /dev/null +++ b/.storybook/preview.js @@ -0,0 +1,43 @@ +import { configure, addDecorator, addParameters } from "@storybook/react"; +import { withOptions } from "@storybook/addon-options"; +import { withKnobs } from "@storybook/addon-knobs"; +import { INITIAL_VIEWPORTS } from "@storybook/addon-viewport"; +import { withThemes } from "storybook-styled-components"; +import { DocsPage, DocsContainer } from "@storybook/addon-docs/blocks"; + +import { OutLineDecorator } from "./OutlineDecorator"; +// themes +import { defaultTheme } from "../src/@next/globalStyles"; + +const themes = { + Default: defaultTheme, +}; + +withOptions({ + name: "Saleor", + url: "https://github.com/mirumee/saleor-storefront", + goFullScreen: false, + sidebarAnimations: true, +}); +addParameters({ viewport: { viewports: INITIAL_VIEWPORTS } }); +addParameters({ + docs: { + container: DocsContainer, + page: DocsPage, + }, +}); +addDecorator(withKnobs); +addDecorator(OutLineDecorator); +addDecorator(withThemes(themes)); + +const req = require.context( + "../src/@next/components", + true, + /stories\.(tsx|mdx)$/ +); + +function loadStories() { + req.keys().forEach(filename => req(filename)); +} + +configure(loadStories, module); diff --git a/.storybook/styles.js b/.storybook/styles.js new file mode 100644 index 0000000..d7bec85 --- /dev/null +++ b/.storybook/styles.js @@ -0,0 +1,17 @@ +import styled from "styled-components"; + +export const Wrapper = styled.div` + position: relative; + margin: 20px; +`; + +export const Outline = styled.div` + position: absolute; + width: 100%; + height: 100%; + border: 3px solid rgba(0, 0, 0, 0.2); + top: 0; + left: 0; + pointer-events: none; + box-sizing: border-box; +`; diff --git a/.tx/config b/.tx/config new file mode 100644 index 0000000..d5d5d83 --- /dev/null +++ b/.tx/config @@ -0,0 +1,10 @@ +[main] +host = https://www.transifex.com + +[saleor-1.storefront] +file_filter = locale/.json +minimum_perc = 1 +source_file = locale/defaultMessages.json +source_lang = en +type = STRUCTURED_JSON + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e700fe4 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,169 @@ +# Changelog + +All notable, unreleased changes to this project will be documented in this file. For the released changes, please visit the [Releases](https://github.com/mirumee/saleor-storefront/releases) page. + +## [Unreleased] + +## 2.11.0 + +- Add product hyperlink in cart page - #745 by @konstantinoschristomanos +- Add Cypress tags to all of the buttons (also speed up tests) - #718 by @krzysztofwolski +- Automatically choose variant attributes from url in ProductVariantPicker - #708 by @AlicjaSzu +- Use SDK as a standalone package - #724 by @dominik-zeglen +- Use auth API from SDK - #727 by @orzechdev +- Fix `CartRow` tests - #749 by @dominik-zeglen +- Add prettier to precommit - #766 by @dominik-zeglen +- Do not use prettier on generated files - #773 by @dominik-zeglen +- Add Eslint - #776 by @dominik-zeglen +- Fix storybook config - #789 by @dominik-zeglen +- Update SK to newest version - #795 by @dominik-zeglen +- Use intl - #782 by @przlada +- Download invoice for order - #790 by @orzechdev +- Do not throw error if unsupported payment gateway found - #819 by @dominik-zeglen +- Fix tsconfig aliases - #824 by @orzechdev +- Set billing address in first checkout step - #822 by @orzechdev +- Persist payment gateways for the whole checkout - #828 by @orzechdev +- Add test tags to address book - #847 by @dominik-zeglen +- Refresh user data if mutation fails - #854 by @dominik-zeglen +- Fix product list data overfetching - #855 by @dominik-zeglen +- Add Adyen payment gateway - #845 by @orzechdev +- Fix crash on null price range - #875 by @orzechdev +- Update collection products query - #879 by @orzechdev +- Fix checkout refreshing - #865 by @orzechdev +- Add purchase availability to product details page - #878 by @orzechdev +- Require payment recreate when payment price is wrong - #892 by @orzechdev +- Handle JWT token refreshing and verifying - #883 by @orzechdev +- Fix cart sidebar style - #897 by @orzechdev +- Refactor variant picker components and open sidebar after adding product to cart - #809 by @krzysztofwolski +- Fix checkout address view - #909 by @konstantinoschristomanos +- Support for static URL - #721 by @marianoeramirez and @dominik-zeglen +- Fix search crashing when displaying item with no category - #928 by @mmarkusik +- Fix generating site map - #915 by @rboixaderg + + +## 2.10.4 + +- Fix build errors introduced in version 2.10.3 - by @dominik-zeglen + +## 2.10.3 + +- Stop storing plain text passwords in localStorage - by @dominik-zeglen + +## 2.10.2 + +- Fix fetching `quantityAvailable` field - #738 by @AlicjaSzu + +## 2.10.1 + +- Replace stockQuantity field with quantityAvailable - #723 by @AlicjaSzu +- Regenerate types - #712 by @dominik-zeglen + +## 2.10.0 + +- Account confirmation mechanism - #565 by @tomaszszymanski129 +- Add missing product attributes on product page - #536 by @orzechdev +- Change register mutation to accountRegister - #549 by @tomaszszymanski129 +- Add `ProductVariantPicker` component supporting multiple product variant attributes - #550 by @orzechdev +- Fix not working storefront when no data in saleor database exist - #551 by @orzechdev +- Make checkout working without shipping if it is not required - #571 by @orzechdev +- Add ability to apply a promo code in checkout - #582 by @orzechdev +- Refactor product list - #591 by @orzechdev +- Add chips of selected filter attributes values and fix product list filtering - #602 by @orzechdev +- Refactor FilterAttribute to AttributeValuesChecklist - #610 by @orzechdev +- Clear cache on logout - #623 by @orzechdev +- Add missing Cypress functional tests - #624 by @mateuszkula +- Add lighthouse config - #627 by @mateuszkula +- Load Stripe asynchronously - #629 by @orzechdev +- Fix crash address book on logout - #630 by @orzechdev +- Preload css for Inter font - #631 by @orzechdev +- Use sdk for fetching shop details - #632 by @mateuszkula +- Preconnect to Graphql API_URI - #634 by @mateuszkula +- Upgrade typescript to 3.8.2 - #635 by @mateuszkula +- Remove linguijs usage - #637 by @mateuszkula +- Remove old storybook - #638 by @mateuszkula +- Add docs to storybook - #614 by @orzechdev +- Create new UI for product page - #605 by @mateuszkula +- Use new pricing on product page with TaxedMoney component to display prices - #584 by @orzechdev +- Update GraphQL schema - #567 by @orzechdev +- Completely recreate checkout and cart, with new checkout nad cart SDK - #639 by @orzechdev, @mateuszkula +- Hide payment options - #678 by @orzechdev +- Fix unhandled JWT token expiration - #696 by @orzechdev +- Regenerate types - #712 by @dominik-zeglen +- Replace stockQuantity field with quantityAvailable - #723 by @AlicjaSzu + +## 0.7.0 + +- Fix login and registration overlay not showing - #322 by @mateuszkula +- Add new design for 404 page - #183 by @mateuszkula +- Add Sitemap generator - #342 by @bogdal +- Add Cypress tests - #333 by @AlicjaSzu +- Add rich-text content renderer - #361 by @AlicjaSzu +- Add rich-text content renderer for pages - #426 @ChanceLeachman +- Add TextField and ErrorMessage components - #373 by @AlicjaSzu +- Add CreditCardForm component - #369 by @AlicjaSzu +- Display filters when no product was found - #319 by @aldomonteiro +- Add ServiceWorker provider - #352 by @bogdal +- Add ButtonLink atom component - #392 by @AlicjaSzu +- Add `lingui` - #382 by @AlicjaSzu +- Add FormFooter molecule component - #393 by @AlicjaSzu +- Add Overlay component - #402 by @AlicjaSzu +- Add Modal component - #391 by @AlicjaSzu +- Add the Credential Management API support - #409 by @bogdal +- Add size picker component - #425 by @bogdal +- Replace `BACKEND_URL` in favor of `API_URI` - #474 by @bogdal +- Adapt `checkout.availablePaymentGateways` structure to the new schema - #483 by @bogdal +- Replace product/variant `price` fields with appropriate `pricing` fields - #483 by @bogdal +- Add Stripe integration - #486 by @bogdal +- Add `PlaceholderImage` component and show it in case of missing thumbnail - #489 by @xit4 + +## 0.6.0 + +- Fix items number in cart based on total sum of quantities - #286 by @bogdandjukic +- Add new styles for inputs and labels - #311 by @AlicjaSzu +- Create custom Select component and add it to ShippingAddress form - ##312 by @AlicjaSzu +- Add schema.org data to homepage and product detail page - #316 by @koradon +- Add link for creating account for anonymous users - #317 by @mateuszkula +- Add react-alert library = #320 by @AlicjaSzu +- CreditCard component refactor = #323 by @AlicjaSzu +- Move App component to seperate module = #327 by @AlicjaSzu +- Update Footer, Breadcrumbs and Table styles = #332 by @AlicjaSzu + +## 0.5.1 + +- Fix image caching - #271 by @timuric +- Fix images cors - #288 by @piotrgrundas + +## 0.5.0 + +- Add stock quantity check without checkout in cart page - #254 by @piotrgrundas +- Add ability to chose payment method, add dummy payment method, improve error handling on checkout shipping address update - #255 by @piotrgrundas +- Add order details page - #262 by @piotrgrundas +- Add order confirmation page - #263 by @piotrgrundas +- Fix checkout composition - #264 by @piotrgrundas +- Add ability to select user stored addresses, update copying shipping address to billing - #265 by @piotrgrundas +- Fix rendering order statuses and order line prices - #281 by @maarcingebala + +## 0.4.0 + +- Handle quantity API errors in cart - #199 by @piotrgrundas +- Fix sticky footer, adjust cart overlay to the mockups, fix error if no shipping method found - #205 by @piotrgrundas +- Disable ability to continue to the billing step without shipement chosen - #211 by @piotrgrundas +- Set max width of images in product description as 100% - #220 by @jxltom +- Move checkout to a separate module, create checkout after user provides a valid email - #223 by @piotrgrundas +- Update checkout review page styles - #239 by @piotrgrundas +- Add syncing checkout after user logs in - #243 by @piotrgrundas +- Create checkout for logged in users without checkout upon adding to cart - #250 by @piotrgrundas + +## 0.3.0 + +- Hide filters and sorting when there are no search results; add trending products to empty search and categories pages - #165 by @piotrgrundas +- Add fetching menus from API - #170 by @piotrgrundas +- Add "Add to cart" indicator - #173 by @piotrgrundas +- Fix product page tablet view - #181 by @piotrgrundas +- Add collection view, fix cursor pagination for categories, update storefront to use new thumbnail structure - #178 by @piotrgrundas +- Minor UX improvements - #182 by @piotrgrundas +- Fix product titles breaking the homepage carousel - #184 by @piotrgrundas +- Fix resolving URLs that include numbers - #185 by @piotrgrundas +- Add OpenGraph and Meta tags - #191 by @piotrgrundas +- Add `tslint` check on CI; add the ability to change cart quantity - #194 by @piotrgrundas +- Update placeholder for missing image - #198 by @piotrgrundas diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bf12098 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM node:10 as builder +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +ARG API_URI +ARG SENTRY_DSN +ARG SENTRY_APM +ARG DEMO_MODE +ARG GTM_ID +ENV API_URI ${API_URI:-http://localhost:8000/graphql/} +RUN API_URI=${API_URI} npm run build + +FROM nginx:stable +WORKDIR /app +COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf +COPY --from=builder /app/dist/ /app/ diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000..f019d54 --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,10 @@ +FROM node:10 +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +ARG API_URI +ENV API_URI ${API_URI:-http://localhost:8000/graphql/} + +EXPOSE 3000 +CMD npm start -- --host 0.0.0.0 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..054607b --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2018, Mirumee Labs +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2744559 --- /dev/null +++ b/README.md @@ -0,0 +1,186 @@ +# Saleor Storefront + +![1 copy 2x](https://user-images.githubusercontent.com/5421321/47798207-30aeea00-dd28-11e8-9398-3d8426836a83.png) + +_**Note:** This project is beta quality. We don't advise using it in production._ + +A GraphQL-powered, PWA, single-page application storefront for [Saleor](https://github.com/mirumee/saleor/). + +## Features + +- Headless ecommerce storefront built with [GraphQL](https://graphql.org/), [Apollo Client](https://www.apollographql.com/client), [React](https://reactjs.org/) and [Typescript](https://www.typescriptlang.org/) +- Offline mode (beta) +- Saleor GraphQL API integration +- Single-page application experience +- [Braintree Payment Gateway](https://www.braintreepayments.com/) integration + +## Demo + +See the [public demo](http://demo.saleor.io) of Saleor Storefront! + +Or launch the demo on a free Heroku instance. + +[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) + +## Getting Started + +These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. + +### Prerequisites + +- Node.js 10.0+ +- A running instance of Saleor. + + To run the storefront, you have to set the `API_URI` environment variable to point to the Saleor GraphQL API. If you are running Saleor locally with the default settings, set `API_URI` to: `http://localhost:8000/graphql/`. + +### Installing + +Clone the repository: + +``` +git clone https://github.com/mirumee/saleor-storefront.git +``` + +Enter the project directory: + +``` +cd saleor-storefront +``` + +#### Using stable release + +To use the official stable release, checkout to a release tag: + +``` +$ git checkout 2.10.4 +``` + +See the list of all releases here: https://github.com/mirumee/saleor-storefront/releases/ + +#### Using development version + +If you want to use the latest development version, checkout to the `master` branch: + +``` +$ git checkout master +``` + +Install NPM dependencies: + +``` +npm i +``` + +Run the development server: + +``` +npm start +``` + +Go to `http://localhost:3000` to access the storefront. + +## Cypress tests + +If you want to run [Cypress](https://www.cypress.io/) tests, make sure that all dependecies (including `Cypress`) are installed by running the install command. + +``` +npm i +``` + +Following environment variables are required to be set in order to be able to run tests properly: + +- `API_URI` - GraphQL API address. +- `STATIC_URL` - static files destination url, eg. S3 bucket +- `CYPRESS_USER_NAME` - username (email) for `Storefront` user. +- `CYPRESS_USER_PASSWORD` - for the user mentioned above. + +If you are runninng the Storefront from the perspective of `Docker` container, then you can run tests using following commands: + +Headless mode: + +``` +cy:run +``` + +Cypress UI mode: + +``` +cy:open +``` + +If you want to run tests against your local development environment then use following commands: + +Headless mode: + +``` +test:e2e:run +``` + +Cypress UI mode: + +``` +test:e2e:dev +``` + +## Creating new components + +All new components should follow Atomic Design Guidelines and be placed in `src/@next/components` directory. + +Files structure can be generated using `plop`: + +``` +npm run generate +``` + +## Modifying the Storefront + +[From Spectrum Post](https://spectrum.chat/saleor/saleor-storefront/modifying-the-storefront~c1955dbf-a421-4fb6-b99e-937dd2642b23) + +### Important Files + +- **saleor-storefront/config/webpack/config.base.js** - Base webpack config file. + - Can change name of the app (displayed when installed on mobile) +- **saleor-storefront/src/index.html** - Main template file that contains the
+ - Can change title of storefront here +- **saleor-storefront/src/index.tsx** - Main entry point file. Render's the component, apollo-client, and others to the root div in index.html file above. +- **saleor-storefront/src/core/config.ts** - Controls number of products shown per page, support email, gateway providers, social media, and some meta. + - Can change support email + - Can change products shown per page + - Can change gateway providers + - Can change social media links that are displayed in the footer + - Can change some meta options +- **saleor-storefront/src/images/** - Holds all the images for logo, cart, favicon, etc. + - Can change storefront logo, favicon, or add new images here. +- **saleor-storefront/src/globalStyles/scss/variables.scss** - Contains base styles like colors, font size, container width, media breakpoints and more. +- **saleor-storefront/src/@next/globalStyles/** - Contains more base styles, themes, media, and constants. +- **saleor-storefront/src/views/** - This folder controls the views, or what is displayed for each page. Most views have a file named "Page.tsx" that controls the layout of the page and a file named "View.tsx" that calls the query and renders the component with the data. + - Can add another view to storefront here. Requires adding a route (see routes below). +- saleor-storefront/src/@next/pages/ - Second spot for modifying/adding different pages. This is the recommended directory to add new pages. +- **saleor-storefront/src/app/routes/** - This folder contains the paths as well as holds the component. Here is where you add a new path and route. + 1. Export a new path in paths.ts + 2. Inside AppRoutes.tsx import your new view (see views above) and create a new route with path={paths.newPath} and component={newViewPage} + 3. To link to your new view import { Link } from "react-router-dom" and use new path you created in paths.ts (make sure to import it) +- **saleor-storefront/src/app/App.tsx** - This is main component that renders the , (explained below),