├── .babelrc ├── .env-sample ├── .eslintrc.json ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── validate-workflow.yml ├── .gitignore ├── .lobby.yml ├── .nvmrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app.json ├── components ├── error.tsx ├── form.tsx ├── header.tsx ├── innerHeader.tsx └── loading.tsx ├── context └── session.tsx ├── jest.config.js ├── jest.setup.ts ├── lib ├── auth.ts ├── db.ts ├── dbs │ ├── firebase.ts │ └── mysql.ts └── hooks.ts ├── next-env.d.ts ├── package-lock.json ├── package.json ├── pages ├── _app.tsx ├── _document.tsx ├── api │ ├── auth.ts │ ├── load.ts │ ├── logout.ts │ ├── orders │ │ └── [orderId] │ │ │ ├── index.ts │ │ │ └── shipping_products.ts │ ├── products │ │ ├── [pid].ts │ │ ├── index.ts │ │ └── list.ts │ ├── removeUser.ts │ └── uninstall.ts ├── index.tsx ├── orders │ └── [orderId] │ │ ├── index.tsx │ │ └── modal.tsx ├── productAppExtension │ └── [productId] │ │ └── index.tsx └── products │ ├── [pid].tsx │ └── index.tsx ├── sample-firebase-keys.json ├── scripts ├── bcSdk.js └── db.js ├── test ├── mocks │ └── hooks.ts ├── pages │ ├── __snapshots__ │ │ └── index.spec.tsx.snap │ ├── index.spec.tsx │ ├── productAppExtension │ │ ├── __snapshots__ │ │ │ └── index.spec.tsx.snap │ │ └── index.spec.tsx │ └── products │ │ ├── [pid].spec.tsx │ │ ├── __snapshots__ │ │ ├── [pid].spec.tsx.snap │ │ └── index.spec.tsx.snap │ │ └── index.spec.tsx └── utils.tsx ├── tsconfig.json └── types ├── auth.ts ├── data.ts ├── db.ts ├── error.ts ├── index.ts └── order.ts /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"], 3 | "plugins": [["styled-components", { "ssr": true }]] 4 | } 5 | -------------------------------------------------------------------------------- /.env-sample: -------------------------------------------------------------------------------- 1 | # Get the Client ID and Secret from the Developer Portal 2 | # https://developer.bigcommerce.com/api-docs/apps/quick-start#register-a-draft-app 3 | 4 | CLIENT_ID={app client id} 5 | CLIENT_SECRET={app secret} 6 | 7 | # Test locally with ngrok 8 | # https://developer.bigcommerce.com/api-docs/apps/guide/development#testing-locally-with-ngrok 9 | 10 | AUTH_CALLBACK=https://{ngrok_url}/api/auth 11 | 12 | # Replace jwt key with a 32+ random character secret 13 | 14 | JWT_KEY={SECRET} 15 | 16 | # Specify the type of database 17 | DB_TYPE=firebase 18 | 19 | # If using firebase, enter your config here 20 | 21 | FIRE_API_KEY={firebase key} 22 | FIRE_DOMAIN={firebase domain} 23 | FIRE_PROJECT_ID={firebase project id} 24 | 25 | # If using mysql, You can use a database URL or enter multiple configuration variables. Comment out the variables you don't use by adding the `#` character to the beginning of the line. 26 | 27 | # DATABASE_URL={mysql://db_address} 28 | 29 | MYSQL_HOST={mysql host} 30 | MYSQL_DATABASE={mysql database name} 31 | MYSQL_USERNAME={mysql username} 32 | MYSQL_PASSWORD={mysql password} 33 | MYSQL_PORT={mysql port *optional*} 34 | 35 | # Most users do not need to change this 36 | ENVIRONMENT=bigcommerce.com 37 | LOGIN_URL=login.${ENVIRONMENT} 38 | API_URL=api.${ENVIRONMENT} 39 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": ["@typescript-eslint"], 4 | "extends": [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/eslint-recommended", 7 | "plugin:@typescript-eslint/recommended", 8 | "plugin:import/errors", 9 | "plugin:import/warnings", 10 | "plugin:import/typescript", 11 | "plugin:react/recommended", 12 | "plugin:react-hooks/recommended" 13 | ], 14 | "env": { 15 | "es6": true, 16 | "node": true 17 | }, 18 | "settings": { 19 | "react": { 20 | "version": "detect" 21 | }, 22 | "import/resolver": { 23 | "typescript": {} 24 | } 25 | }, 26 | "rules": { 27 | "@typescript-eslint/explicit-function-return-type": "off", 28 | "@typescript-eslint/no-explicit-any": "off", 29 | "@typescript-eslint/no-unused-vars": ["error", { "ignoreRestSiblings": true }], 30 | "@typescript-eslint/no-use-before-define": "off", 31 | "@typescript-eslint/explicit-function-return-type": "off", 32 | "@typescript-eslint/explicit-module-boundary-types": "off", 33 | "no-console": ["error", { "allow": ["warn", "error"] }], 34 | "react/display-name": "off", 35 | "react/prop-types": "off", 36 | "react/react-in-jsx-scope": "off", 37 | "newline-before-return": "error", 38 | "sort-imports": [ 39 | "error", 40 | { 41 | "ignoreCase": true, 42 | "ignoreDeclarationSort": true 43 | } 44 | ], 45 | "import/named": "off", 46 | "import/no-named-as-default-member": "off", 47 | "import/order": [ 48 | "error", 49 | { 50 | "alphabetize": { 51 | "order": "asc", 52 | "caseInsensitive": true 53 | }, 54 | "newlines-between": "ignore", 55 | "groups": [["builtin", "external"], "internal", "parent", "sibling", "index"] 56 | } 57 | ] 58 | }, 59 | "globals": { 60 | "React": "writable" 61 | }, 62 | "ignorePatterns": ["scripts/*"] 63 | } 64 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## What? 2 | A description about what this pull request implements and its purpose. Try to be detailed and describe any technical details to simplify the job of the reviewer and the individual on production support. 3 | 4 | ## Why? 5 | ... 6 | 7 | ## Testing / Proof 8 | ... 9 | 10 | @bigcommerce/api-client-developers 11 | -------------------------------------------------------------------------------- /.github/workflows/validate-workflow.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [main] 9 | pull_request: 10 | branches: [main] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [18.x, 20.x] 20 | 21 | steps: 22 | - name: Checkout code 23 | uses: actions/checkout@v3 24 | 25 | - name: Use Node.js ${{ matrix.node }} 26 | uses: actions/setup-node@v3 27 | with: 28 | node-version: ${{ matrix.node-version }} 29 | 30 | - name: Install Dependencies 31 | run: | 32 | npm i -g npm@8.13.1 33 | npm ci 34 | 35 | - name: Run Build 36 | run: npm run build 37 | 38 | - name: Lint Check 39 | run: npm run lint --if-present 40 | 41 | - name: Validate tests 42 | run: npm run test --if-present 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /.lobby.yml: -------------------------------------------------------------------------------- 1 | targets: 2 | production: 3 | heroku_server_name: 'go-sample-app-production' 4 | private_key_path: '/etc/deploy_keys/deploy_herokuapp.key' 5 | 6 | staging: 7 | heroku_server_name: 'go-sample-app-staging' 8 | private_key_path: '/etc/deploy_keys/deploy_herokuapp.key' 9 | 10 | integration: 11 | heroku_server_name: 'go-sample-app-integration' 12 | private_key_path: '/etc/deploy_keys/deploy_herokuapp.key' 13 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | All notable changes to this project will be documented in this file. See Conventional Commits for commit guidelines 2 | 3 | ### 1.0.0 4 | 5 | - Initial public release 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | 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. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | 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. 28 | 29 | 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. 30 | 31 | ## Scope 32 | 33 | 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. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at engineering@bigcommerce.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. 38 | 39 | 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. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Sample App NodeJS 2 | Thanks for showing interest in contributing! 3 | 4 | The following is a set of guidelines for contributing to the Sample App NodeJS. These are just guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 5 | 6 | #### Table of Contents 7 | 8 | [API Documentation](https://developer.bigcommerce.com/api) 9 | 10 | How Can I Contribute? 11 | * [Pull Requests](#pull-requests) 12 | * [Issues / Bugs](#issues--bugs) 13 | * [Other Ways to Contribute](#other-ways-to-contribute) 14 | 15 | Styleguides 16 | * [Git Commit Messages](#git-commit-messages) 17 | 18 | ## Pull Requests 19 | 20 | First ensure that your feature isn't already being developed or considered (see open PRs and issues). 21 | If it is, please consider contributing to those initiatives. 22 | 23 | All PRs require test coverage to be accepted when applicable. 24 | 25 | ## Issues / Bugs 26 | 27 | * Please include a clear, specific title and replicable description. 28 | 29 | * Please include your environment, OS, and any exceptions/backtraces that occur. The more 30 | information that is given, the more likely we can debug and fix the issue. 31 | 32 | **If you find a security bug, please do not post as an issue. Send directly to security@bigcommerce.com 33 | instead.** 34 | 35 | ## Other Ways to Contribute 36 | 37 | * Consider contributing to sample-app-nodejs documentation on the wiki, reporting bugs, contributing to test coverage, 38 | or helping spread the word about sample-app-nodejs. 39 | 40 | ## Git Commit Messages 41 | 42 | * Use the present tense ("Add feature" not "Added feature") 43 | * Use the imperative mood ("Move cursor to..." not "Moves cursor to...") 44 | * Limit the first line to 72 characters or less 45 | * Reference pull requests and external links liberally 46 | 47 | Thank you again for your interest in contributing to sample-app-nodejs! 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 BigCommerce 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NextJS Sample App 2 | 3 | This starter app includes all the files necessary to get started with a basic, hello world app. This app uses NextJS, BigDesign, Typescript, and React. 4 | 5 | ## Running the app in development 6 | 7 | To get the app running locally, follow these instructions: 8 | 9 | 1. [Use Node 18+ and NPM 8+](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm#checking-your-version-of-npm-and-node-js). To check the running versions, use the following commands: 10 | 11 | ```shell 12 | node -v 13 | npm -v 14 | ``` 15 | 16 | 2. Clone and/or fork the repo and install npm packages: 17 | 18 | ```shell 19 | git clone git@github.com:bigcommerce/sample-app-nodejs.git my-bigcommerce-app 20 | cd my-bigcommerce-app 21 | npm install 22 | ``` 23 | 24 | 3. To expose your app server using an HTTP tunnel, install [ngrok](https://www.npmjs.com/package/ngrok#usage) globally, then start the ngrok service. 25 | 26 | Starting a local HTTP tunnel with ngrok requires you to create an [ngrok account](https://dashboard.ngrok.com/signup) and add your [ngrok authtoken](https://dashboard.ngrok.com/get-started/your-authtoken) to the ngrok config file. 27 | 28 | ```shell 29 | ngrok config add-authtoken $YOUR_AUTHTOKEN 30 | ``` 31 | 32 | You can use `npm` to install ngrok: 33 | 34 | ```shell 35 | npm install -g ngrok 36 | ``` 37 | 38 | Alternatively, MacOS users can use the [homebrew](https://brew.sh/) package manager: 39 | 40 | ```shell 41 | brew install ngrok 42 | ``` 43 | 44 | Start ngrok on port `3000` to expose the default Next.js server: 45 | 46 | ```shell 47 | ngrok http 3000 48 | ``` 49 | 50 | 4. Use the BigCommerce [Developer Portal](https://devtools.bigcommerce.com) to [register a draft app](https://developer.bigcommerce.com/api-docs/apps/quick-start#register-the-app). For steps 5-7, enter callbacks as `'https://{ngrok_url}/api/{auth || load || uninstall}'`. Get the `ngrok_url` from the ngrok terminal session. 51 | 52 | ```shell 53 | https://12345.ngrok-free.app/api/auth # auth callback 54 | https://12345.ngrok-free.app/api/load # load callback 55 | https://12345.ngrok-free.app/api/uninstall # uninstall callback 56 | ``` 57 | 58 | 5. Copy `.env-sample` to `.env`. 59 | 60 | ```shell 61 | cp .env-sample .env 62 | ``` 63 | 64 | 6. In the `.env` file, replace the `CLIENT_ID` and `CLIENT_SECRET` variables with the API account credentials in the app profile. To locate the credentials, find the app's profile in the [Developer Portal](https://devtools.bigcommerce.com/my/apps), then click **View Client ID**. 65 | 66 | 7. In the `.env` file, update the `AUTH_CALLBACK` variable with the auth callback URL from step 4. 67 | 68 | 8. In the `.env` file, enter a secret `JWT_KEY`. To support HS256 encryption, the JWT key must be at least 32 random characters (256 bits). 69 | 70 | 9. **Configure the data store.** This project was written to use [Firebase](https://firebase.google.com/) or [MySQL](https://www.mysql.com/) 71 | 72 | In the `.env` file, specify the `DB_TYPE`. 73 | 74 | If using Firebase, copy the contents of your Service Account JSON key file into the `sample-firebase-keys.json` file. This file can be generated by: 75 | 1. Creating a new project in Firebase 76 | 2. Adding a Cloud Firestore 77 | 3. And generating a new Private Key under Project Settings > Service Accounts 78 | See the [Firebase quickstart (Google)](https://firebase.google.com/docs/firestore/quickstart) for more detailed information. 79 | 80 | If using MySQL, supply the `MYSQL_` config keys listed in the `.env` file, then do the initial database migration by running the following npm script: `npm run db:setup` 81 | 82 | 10. Start your dev environment in a dedicated terminal session, **separate from `ngrok`**. 83 | 84 | ```shell 85 | npm run dev 86 | ``` 87 | 88 | > If you relaunch `ngrok`, update the callbacks in steps 4 and 7 with the new `ngrok_url`. You can learn more about [persisting ngrok tunnels longer (ngrok)](https://ngrok.com/docs/getting-started/#step-3-connect-your-agent-to-your-ngrok-account). 89 | 90 | 11. Consult our developer documentation to [install and launch the app](https://developer.bigcommerce.com/api-docs/apps/quick-start#install-the-app). 91 | 92 | ## Production builds 93 | 94 | In production, you must build and run optimized version of the code. Use the following commands to get started: 95 | 96 | > When you run the `start` script, specify a port number using the `-p` flag. Otherwise, the process will fail. 97 | 98 | ```shell 99 | npm run build 100 | npm run start -p 3000 101 | ``` 102 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BigCommerce Sample App", 3 | "description": "A sample NodeJS app installable on BigCommerce stores", 4 | "repository": "https://github.com/bigcommerce/sample-app-nodejs", 5 | "keywords": ["Node", "Heroku"], 6 | "addons": ["cleardb:ignite"], 7 | "env": { 8 | "CLIENT_ID": { 9 | "description": "The client id of your app, provided by the BigCommerce developer portal." 10 | }, 11 | "CLIENT_SECRET":{ 12 | "description": "The client secret of your app, provided by the BigCommerce developer portal." 13 | }, 14 | "AUTH_CALLBACK":{ 15 | "description": "The fully qualified authentication endpoint provided by this app. Replace the app name with the app name chosen above.", 16 | "value": "https://.herokuapp.com/api/auth" 17 | }, 18 | "DB_TYPE":{ 19 | "description": "Which type of database we are using. Options are 'mysql' or 'firebase'. Can safely leave unchanged.", 20 | "value": "mysql" 21 | }, 22 | "JWT_KEY":{ 23 | "description": "Key the app will use for signing JWT. Can safely leave unchanged.", 24 | "generator": "secret" 25 | } 26 | }, 27 | "scripts": { 28 | "postdeploy": "npm run db:setup" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /components/error.tsx: -------------------------------------------------------------------------------- 1 | import { H3, Panel } from '@bigcommerce/big-design'; 2 | import { ErrorMessageProps, ErrorProps } from '../types'; 3 | 4 | const ErrorContent = ({ message }: Pick) => ( 5 | <> 6 |

Failed to load

7 | {message} 8 | 9 | ) 10 | 11 | const ErrorMessage = ({ error, renderPanel = true }: ErrorMessageProps) => { 12 | if (renderPanel) { 13 | return ( 14 | 15 | 16 | 17 | ) 18 | } 19 | 20 | return 21 | }; 22 | 23 | export default ErrorMessage; 24 | -------------------------------------------------------------------------------- /components/form.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Checkbox, Flex, FormGroup, Input, Panel, Select, Form as StyledForm, Textarea } from '@bigcommerce/big-design'; 2 | import { ChangeEvent, FormEvent, useState } from 'react'; 3 | import { FormData, StringKeyValue } from '../types'; 4 | 5 | interface FormProps { 6 | formData: FormData; 7 | onCancel(): void; 8 | onSubmit(form: FormData): void; 9 | } 10 | 11 | const FormErrors = { 12 | name: 'Product name is required', 13 | price: 'Default price is required', 14 | }; 15 | 16 | const Form = ({ formData, onCancel, onSubmit }: FormProps) => { 17 | const { description, isVisible, name, price, type } = formData; 18 | const [form, setForm] = useState({ description, isVisible, name, price, type }); 19 | const [errors, setErrors] = useState({}); 20 | 21 | const handleChange = (event: ChangeEvent) => { 22 | const { name: formName, value } = event.target || {}; 23 | setForm(prevForm => ({ ...prevForm, [formName]: value })); 24 | 25 | // Add error if it exists in FormErrors and the input is empty, otherwise remove from errors 26 | !value && FormErrors[formName] 27 | ? setErrors(prevErrors => ({ ...prevErrors, [formName]: FormErrors[formName] })) 28 | : setErrors(({ [formName]: removed, ...prevErrors }) => ({ ...prevErrors })); 29 | }; 30 | 31 | const handleSelectChange = (value: string) => { 32 | setForm(prevForm => ({ ...prevForm, type: value })); 33 | }; 34 | 35 | const handleCheckboxChange = (event: ChangeEvent) => { 36 | const { checked, name: formName } = event.target || {}; 37 | setForm(prevForm => ({ ...prevForm, [formName]: checked })); 38 | }; 39 | 40 | const handleSubmit = (event: FormEvent) => { 41 | event.preventDefault(); 42 | 43 | // If there are errors, do not submit the form 44 | const hasErrors = Object.keys(errors).length > 0; 45 | if (hasErrors) return; 46 | 47 | onSubmit(form); 48 | }; 49 | 50 | return ( 51 | 52 | 53 | 54 | 62 | 63 | 64 | 89 | 90 | 91 | 97 | 98 | 99 | 100 | 101 | {/* Using description for demo purposes. Consider using a wysiwig instead (e.g. TinyMCE) */} 102 | 254 | 255 | 256 | 257 | 258 |
261 | 271 | 281 |
282 | 283 | `; 284 | -------------------------------------------------------------------------------- /test/pages/products/__snapshots__/index.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Product List renders correctly 1`] = ` 4 |
8 |
12 |
15 |

18 | 5 Products 19 |

20 |
21 |
24 | 155 |
156 |
157 | 161 | 164 | 165 | 175 | 185 | 195 | 205 | 206 | 207 | 210 | 213 | 226 | 236 | 241 | 295 | 296 | 299 | 312 | 322 | 327 | 381 | 382 | 385 | 398 | 408 | 413 | 467 | 468 | 471 | 484 | 494 | 499 | 553 | 554 | 557 | 570 | 580 | 585 | 639 | 640 | 641 |
169 |
172 | Product name 173 |
174 |
179 |
182 | Stock 183 |
184 |
189 |
192 | Price 193 |
194 |
199 |
202 | Action 203 |
204 |
216 | 219 | 222 | Product 0 223 | 224 | 225 | 229 |

233 | 0 234 |

235 |
239 | $10.00 240 | 244 |
247 | 281 |
285 |
293 |
294 |
302 | 305 | 308 | Product 1 309 | 310 | 311 | 315 |

319 | 0 320 |

321 |
325 | $20.00 326 | 330 |
333 | 367 |
371 |
379 |
380 |
388 | 391 | 394 | Product 2 395 | 396 | 397 | 401 |

405 | 0 406 |

407 |
411 | $30.00 412 | 416 |
419 | 453 |
457 |
465 |
466 |
474 | 477 | 480 | Product 3 481 | 482 | 483 | 487 |

491 | 0 492 |

493 |
497 | $40.00 498 | 502 |
505 | 539 |
543 |
551 |
552 |
560 | 563 | 566 | Product 4 567 | 568 | 569 | 573 |

577 | 0 578 |

579 |
583 | $50.00 584 | 588 |
591 | 625 |
629 |
637 |
638 |
642 |
643 | `; 644 | -------------------------------------------------------------------------------- /test/pages/products/index.spec.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/router"; 2 | import { ROW_NUMBERS } from '@mocks/hooks'; 3 | import Products from '@pages/products/index'; 4 | import { render, screen } from '@test/utils'; 5 | 6 | jest.mock('@lib/hooks', () => require('@mocks/hooks')); 7 | jest.mock('next/router', () => ({ 8 | useRouter: jest.fn(), 9 | })); 10 | 11 | describe('Product List', () => { 12 | test('renders correctly', async () => { 13 | const router = { }; 14 | useRouter.mockReturnValue(router); 15 | 16 | const { container } = render(); 17 | // Wait for table to be rendered 18 | await screen.findByRole('table'); 19 | 20 | expect(container.firstChild).toMatchSnapshot(); 21 | }); 22 | 23 | test('renders a table with correct number of rows', async () => { 24 | const router = { }; 25 | useRouter.mockReturnValue(router); 26 | 27 | render(); 28 | // Wait for table to be rendered 29 | const productsTable = await screen.findByRole('table'); 30 | const rowsLength = screen.getAllByRole('row').length - 1; // Rows - 1 (table header) 31 | 32 | expect(productsTable).toBeDefined(); 33 | expect(rowsLength).toEqual(ROW_NUMBERS); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/utils.tsx: -------------------------------------------------------------------------------- 1 | import { theme } from '@bigcommerce/big-design-theme'; 2 | import { render as defaultRender, RenderOptions } from '@testing-library/react'; 3 | import React, { ReactElement } from 'react'; 4 | import { ThemeProvider } from 'styled-components'; 5 | 6 | const Provider = ({ children }) => ( 7 | 8 | {children} 9 | 10 | ); 11 | 12 | const customRender = (ui: ReactElement, options: RenderOptions = {}) => ( 13 | defaultRender(ui, { wrapper: Provider, ...options }) 14 | ); 15 | 16 | // re-export everything 17 | // eslint-disable-next-line import/export 18 | export * from '@testing-library/react'; 19 | 20 | // override render method 21 | // eslint-disable-next-line import/export 22 | export { customRender as render }; 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "baseUrl": ".", 11 | "skipLibCheck": true, 12 | "strict": false, 13 | "forceConsistentCasingInFileNames": true, 14 | "noEmit": true, 15 | "esModuleInterop": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "jsx": "preserve", 21 | "paths": { 22 | "@components/*": [ 23 | "components/*" 24 | ], 25 | "@lib/*": [ 26 | "lib/*" 27 | ], 28 | "@mocks/*": [ 29 | "test/mocks/*" 30 | ], 31 | "@pages/*": [ 32 | "pages/*" 33 | ], 34 | "@test/utils": [ 35 | "test/utils" 36 | ], 37 | "@types": [ 38 | "types" 39 | ] 40 | }, 41 | "incremental": true 42 | }, 43 | "include": [ 44 | "next-env.d.ts", 45 | "**/*.ts", 46 | "**/*.tsx" 47 | ], 48 | "exclude": [ 49 | "node_modules", 50 | "scripts" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /types/auth.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | email: string; 3 | id: number; 4 | username?: string; 5 | } 6 | 7 | export interface SessionProps { 8 | access_token?: string; 9 | context: string; 10 | owner?: User; 11 | scope?: string; 12 | store_hash?: string; 13 | sub?: string; 14 | timestamp?: number; 15 | user: User; 16 | } 17 | 18 | export interface SessionContextProps { 19 | accessToken: string; 20 | storeHash: string; 21 | user: User; 22 | } 23 | 24 | export interface QueryParams { 25 | [key: string]: string | string[]; 26 | } 27 | 28 | export interface ApiConfig { 29 | apiUrl?: string; 30 | loginUrl?: string; 31 | } 32 | -------------------------------------------------------------------------------- /types/data.ts: -------------------------------------------------------------------------------- 1 | export interface FormData { 2 | description: string; 3 | isVisible: boolean; 4 | name: string; 5 | price: number; 6 | type: string; 7 | } 8 | 9 | export interface TableItem { 10 | id: number; 11 | name: string; 12 | price: number; 13 | stock: number; 14 | } 15 | 16 | export interface ListItem extends FormData { 17 | id: number; 18 | } 19 | 20 | export interface StringKeyValue { 21 | [key: string]: string; 22 | } 23 | -------------------------------------------------------------------------------- /types/db.ts: -------------------------------------------------------------------------------- 1 | import { SessionProps } from './index'; 2 | 3 | export interface StoreData { 4 | accessToken?: string; 5 | scope?: string; 6 | storeHash: string; 7 | } 8 | 9 | export interface UserData { 10 | email: string; 11 | username?: string; 12 | } 13 | 14 | export interface Db { 15 | hasStoreUser(storeHash: string, userId: string): Promise | boolean; 16 | setUser(session: SessionProps): Promise; 17 | setStore(session: SessionProps): Promise; 18 | setStoreUser(session: SessionProps): Promise; 19 | getStoreToken(storeId: string): Promise | null; 20 | deleteStore(session: SessionProps): Promise; 21 | deleteUser(session: SessionProps): Promise; 22 | } 23 | -------------------------------------------------------------------------------- /types/error.ts: -------------------------------------------------------------------------------- 1 | export interface ErrorProps extends Error { 2 | status?: number; 3 | } 4 | 5 | export interface ErrorMessageProps { 6 | error?: ErrorProps; 7 | renderPanel?: boolean; 8 | } 9 | -------------------------------------------------------------------------------- /types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth'; 2 | export * from './data'; 3 | export * from './db'; 4 | export * from './error'; 5 | export * from './order'; 6 | -------------------------------------------------------------------------------- /types/order.ts: -------------------------------------------------------------------------------- 1 | export interface BillingAddress { 2 | // Additional fields exist, type as needed 3 | [key: string]: unknown; 4 | first_name: string; 5 | last_name: string; 6 | street_1: string; 7 | street_2: string; 8 | city: string; 9 | state: string; 10 | zip: string; 11 | country: string; 12 | } 13 | 14 | export interface ShippingAddress { 15 | [key: string]: unknown; 16 | first_name: string; 17 | last_name: string; 18 | street_1: string; 19 | street_2: string; 20 | city: string; 21 | state: string; 22 | zip: string; 23 | country: string; 24 | } 25 | 26 | export interface OrderProduct { 27 | [key: string]: unknown; 28 | id: number; 29 | name: string; 30 | quantity: number; 31 | order_address_id: number; 32 | } 33 | 34 | export interface Order { 35 | // Additional fields exist, type as needed 36 | [key: string]: unknown; 37 | billing_address: BillingAddress; 38 | currency_code: string; 39 | customer_locale: string; 40 | discount_amount: string; 41 | id: number; 42 | items_total: number; 43 | order_source: string; 44 | payment_status: string; 45 | status: string; 46 | subtotal_ex_tax: string; 47 | shipping_cost_ex_tax: string; 48 | total_inc_tax: string; 49 | total_tax: string; 50 | } 51 | 52 | export interface ShippingAndProductsInfo { 53 | shipping_addresses: ShippingAddress[]; 54 | products: OrderProduct[]; 55 | } 56 | --------------------------------------------------------------------------------